开发者

How do I build a Java project with Java 6 against Java 1.4 libraries?

I've got a project that was originally written for Java 1.4, but I only have Java 6 on my Mac and I cannot install Java 1.4.

Normally, I'd use a line like this to compile:

javac -source=1.4 -target=1.4 MyClass.java

However, MyClass.java implements the java.sql.ResultSet interface, which added several new methods in Java 6, so I开发者_JS百科 get compile errors like:

MyClass is not abstract and does not override abstract method
updateNClob(java.lang.String,java.io.Reader) in java.sql.ResultSet

I cannot simply implement the missing methods because many use generics, which are not available in Java 1.4.

It seems a solution would be to obtain and compile against the Java 1.4 JARs. So, I've got a few questions:

  1. Is there a better way?
  2. How do I specify to my Java 1.6 javac that I'd like to use the 1.4 JARs instead of the Java 6 JARs?
  3. Will this even work, and if so, will the project run on Java 1.4 as well as Java 6?
  4. How do I do this in Maven?

Thanks!


Your situation seems to be quite contrived. I'll try to simplify matters. At first, I am going to ignore your question about Maven.

So let me first state some facts:

-source=1.4 means: Dear compiler, please accept only language constructs --- not library features --- which were available with javac of JDK 1.4.

-target=1.4 means: Dear compiler, please write class files in a binary file format which is compatible with a JRE 1.4.

I gather that you are interested in load-time compatibility with JDK 1.4, i.e. you want that the class files produced in your setup can be loaded by JDK 1.4. Is that right?

Do you also want to support source compatibility? I.e. do you want to allow others to compile your code on a JDK 1.4?

If the answer to the last question is yes, I would try to install JDK 1.4 on OS X. It supports multiple installed JDKs. So I am pretty sure it is possible. If that is no option use:

-source=1.4 -target=1.4 -bootclasspath=[path/to/1.4.jar]

Note, do not use -Xbootclasspath. This changes the boot classpath of the jvm executing javac.

If the answer to the above question is no. You can dispose of -source=1.4 allowing you to use generics and other Java 5 enhancement in your code. But you still have to provide binary compatibility by using:

-target=1.4  -bootclasspath=[path/to/1.4.jar]

Another option would be to use Retroweaver.

After re-reading your question, I'd like add that you have to get hold of JDK 1.4 variant of the jdbc class files. Otherwise you'll run into the compiler errors you've shown in your question.


Unless you are a JDBC vendor, it is unwise to implement interfaces like this one.

Consider using a proxy to maintain compatibility across JVM versions.


Migrating to a proxy is accomplished as follows. Consider this ResultSet implementation:

public class ResultSetFoo implements ResultSet {

  public String getString(int columnIndex) throws SQLException {
    return "foobar";
  }

  // other Java 1.4 methods

This would be changed so no classes implement ResultSet:

public class ResultBar {

  public String getString(int columnIndex) throws SQLException {
    return "foobar";
  }

  // other method signatures matching the 1.4 ResultSet, as before

You would then need to build a mapping of methods between the two types at runtime (a primitive form of duck-typing:)

  private static final Map RESULT_SET_DUCK = initResultSet();

  private static Map initResultSet() {
    Map map = new HashMap();
    Method[] methods = ResultSet.class.getMethods();
    for (int i = 0; i < methods.length; i++) {
      try {
        Method match =
            ResultBar.class.getMethod(methods[i].getName(),
                methods[i].getParameterTypes());
        map.put(methods[i], match);
      } catch (SecurityException e) {
        throw new IllegalStateException(e);
      } catch (NoSuchMethodException e) {
        // OK; not supported in 1.4
      }
    }
    return map;
  }

This allows you to invoke the ResultBar type by proxy:

  /** Create a java.sql.ResultSet proxy */
  public static ResultSet proxy(final ResultBar duck) {
    class Handler implements InvocationHandler {
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
        Method proxiedMethod = (Method) RESULT_SET_DUCK.get(method);
        if (proxiedMethod == null) {
          throw new UnsupportedOperationException("TODO: method detail");
        } else {
          return invoke(proxiedMethod, duck, args);
        }
      }

      private Object invoke(Method m, Object target, Object[] args)
          throws Throwable {
        try {
          return m.invoke(target, args);
        } catch (InvocationTargetException e) {
          throw e.getCause();
        }
      }
    }

    return (ResultSet) Proxy.newProxyInstance(null, RSET, new Handler());
  }

Such implementations should allow code compiled in one JVM to be used in future JVMs even if new methods are added. Existing method signatures are unlikely to change because it is one thing to make database vendors do some work; something else to make all API consumers change.

You may need to change how class instances are created. You can no longer use a constructor directly:

ResultSet nonPortable = new ResultSetFoo();
//becomes...
ResultSet portable = proxy(new ResultBar());

If you're already employing a factory/builder/etc. pattern this bit is easy.

Although reflection is relatively cheap in the latest JVMs it is less so in older versions; this may have a detrimental effect on performance.


How do I specify to my Java 1.6 javac that I'd like to use the 1.4 JARs instead of the Java 6 JARs?

In Win. & *nix it would be by specifying the bootclasspath option. See javac: Cross-Compilation Options for more details.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜