views:

1970

answers:

15

I maintain a Java Swing application.

For backwards compatibility with java 5 (for Apple machines), we maintain two codebases, 1 using features from Java 6, another without those features.

The code is largely the same, except for 3-4 classes that uses Java 6 features.

I wish to just maintain 1 codebase. Is there a way during compilation, to get the Java 5 compiler to 'ignore' some parts of my code?

I do not wish to simply comment/uncomment parts of my code, depending on the version of my java compiler.

+4  A: 

I think the best approach here is probably to use build scripts. You can have all your code in one location, and by choosing which files to include, and which not to include, you can choose what version of your code to compile. Note that this may not help if you need finer-grained control than per file.

chessguy
+1  A: 

Not really, but there are workarounds. See http://forums.sun.com/thread.jspa?threadID=154106&messageID=447625

That said, you should stick with at least having one file version for Java 5 and one for Java 6, and include them via a build or make as appropriate. Sticking it all in one big file and trying to get the compiler for 5 to ignore stuff it doesn't understand isn't a good solution.

HTH

-- nikki --

Nikki9696
+1  A: 

This will make all the Java purists cringe (which is fun, heh heh) but i would use the C preprocessor, put #ifdefs in my source. A makefile, rakefile, or whatever controls your build, would have to run cpp to make a temporary files to feed the compiler. I have no idea if ant could be made to do this.

While stackoverflow looks like it'll be the place for all answers, you could wehn no one's looking mosey on over to http://www.javaranch.com for Java wisdom. I imagine this question has been dealt with there, prolly a long time ago.

DarenW
A: 

There is no pre-compiler in Java. Thus, no way to do a #ifdef like in C. Build scripts would be the best way.

Burkhard
He's suggesting to use s C compiler to run the actual C pre-processor, which would output modified source files, then run the Java compiler on those. Most if not all C compilers have a switch to run the pre-processor only.
Dave Costa
A: 

You can get conditional compile, but not very nicely - javac will ignore unreachable code. Thus if you structured your code properly, you can get the compiler to ignore parts of your code. To use this properly, you would also need to pass the correct arguments to javac so it doesn't report unreachable code as errors, and refuse to compile :-)

freespace
+2  A: 

Keep one "master" source root that builds under JDK 5. Add a second parallel source root that has to build under JDK 6 or higher. (There should be no overlap, i.e. no classes present in both.) Use an interface to define the entry point between the two, and a tiny bit of reflection.

For example:

---%<--- main/RandomClass.java
// ...
if (...is JDK 6+...) {
    try {
        JDK6Interface i = (JDK6Interface)
            Class.forName("JDK6Impl").newInstance();
        i.browseDesktop(...);
    } catch (Exception x) {
        // fall back...
    }
}
---%<--- main/JDK6Interface.java
public interface JDK6Interface {
    void browseDesktop(URI uri);
}
---%<--- jdk6/JDK6Impl.java
public class JDK6Impl implements JDK6Interface {
    public void browseDesktop(URI uri) {
        java.awt.Desktop.getDesktop().browse(uri);
    }
}
---%<---

You could configure these as separate projects in an IDE using different JDKs, etc. The point is that the main root can be compiled independently and it is very clear what you can use in which root, whereas if you try to compile different parts of a single root separately it is too easy to accidentally "leak" usage of JDK 6 into the wrong files.

Rather than using Class.forName like this, you can also use some kind of service registration system - java.util.ServiceLoader (if main could use JDK 6 and you wanted optional support for JDK 7!), NetBeans Lookup, Spring, etc. etc.

The same technique can be used to create support for an optional library rather than a newer JDK.

Jesse Glick
+2  A: 

You can probably refactor your code so that conditional compile really isn't needed, just conditional classloading. Something like this:

public interface Opener{

public void open(File f);

 public static class Util{
        public Opener getOpener(){
          if(System.getProperty("java.version").beginsWith("1.5")){
           return new Java5Opener();
          }
          try{ 
            return new Java6Opener();
           }catch(Throwable t){
            return new Java5Opener();
           }
        }
 }

}

This could be a lot of effort depending on how many version-specific pieces of code you have.

Steve g
+5  A: 

Assuming that the classes have similar functionality with 1.5 vs. 6.0 differences in implementation you could merge them into one class. Then, without editing the source to comment/uncomment, you can rely on the optimization that the compiler always do. If an if expression is always false, the code in the if statement will not be included in the compilation.

You can make a static variable in one of your classes to determine which version you want to run:

public static final boolean COMPILED_IN_JAVA_6 = false;

And then have the affected classes check that static variable and put the different sections of code in a simple if statement

if (VersionUtil.COMPILED_IN_JAVA_6) {
  // Java 6 stuff goes here
} else {
  // Java 1.5 stuff goes here
}

Then when you want to compile the other version you just have to change that one variable and recompile. It might make the java file larger but it will consolidate your code and eliminate any code duplication that you have. Your editor may complain about unreachable code or whatever but the compiler should blissfully ignore it.

18Rabbit
Is that a documented behaviour of Java compilers?
DrJokepu
This answer seems incorrect. I did a quick Java app that used java.io.Console, and the above approach. The compiler failed in 1.5 with an error like the following:Test.java:8: cannot find symbol: method console() location: class java.lang.System
noahz
I could see how you could compile in 1.6 and exclude certain runtime features when running in a 1.5 JVM. Of course, you would also need to re-compile into 1.5 JVM bytecode format to avoid the "invalid minor version 49.0" error. For that approach, this solution could work.
noahz
This answer *is* incorrect. The Java 5 compiler will fail to compile the "Java 6 stuff goes here" block, so you haven't fixed the problem. A better solution would be to have two implementations of an interface, one for Java 5 and one for Java 6. Your build tool can then include the right implementation at compile time. Ant could do this easily by using a build property.
Cameron Skinner
A: 

The public static final solution mentioned above has one additional benefit the author didn't mention--as I understand it, the compiler will recognize it at compile time and compile out any code that is within an if statement that refers to that final variable.

So I think that's the exact solution you were looking for.

Bill K
A: 

A simple solution could be:

  • Place the divergent classes outside of your normal classpath.
  • Write a simple custom classloader and install it in main as your default.
  • For all classes apart from the 5/6 ones the cassloader can defer to its parent (the normal system classloader)
  • For the 5/6 ones (which should be the only ones that cannot be found by the parent) it can decide which to use via the 'os.name' property or one of your own.
Garth Gilmour
+1  A: 

It depends on what Java 6 features you want to use. For a simple thing like adding row sorters to JTables, you can actually test at runtime:

private static final double javaVersion =
         Double.parseDouble(System.getProperty("java.version").substring(0, 3));
private static final boolean supportsRowSorter =
         (javaVersion >= 1.6);

//...

if (supportsRowSorter) {
    myTable.setAutoCreateRowSorter(true);
} else {
    // not supported
}

This code must be compiled with Java 6, but can be run with any version (no new classes are referenced).

EDIT: to be more correct, it will work with any version since 1.3 (according to this page).

Michael Myers
+4  A: 

The suggestions about using custom class loaders and dynamically commented code are a bit incredulous when it comes to maintenance and the preservation of the sanity of whichever poor soul picks up the project after you shuffle to pastures new.

The solution is easy. Pull the affected classes out into two separate, independent projects - make sure the package names are the same, and just compile into jars that you can then consume in your main project. If you keep the package names the same, and the method signatures the same, no problems - just drop whichever version of the jar you need into your deployment script. I would assume you run separate build scripts or have separate targets in the same script - ant and maven can both easily handle conditionally grabbing files and copying them.

iAn
Independent projects are overkill, separate source folders and a custom build script will do just fine.Use source folders for src/main, with the common codebase, and, say, src/java5 and src/java6 for features dependent on each version.
Leonel
+1  A: 

You can do all of your compiling exclusively on Java6 and then use System.getProperty("java.version") to conditionally run either the Java5 or the Java6 code path.

You can have Java6-only code in a class and the class will run fine on Java5 as long as the Java6-only code path is not executed.

This is a trick that is used to write applets that will run on the ancient MSJVM all the way up to brand-new Java Plug-in JVMs.

shadit
A: 

I wanted to vote up the answer that was already accepted, as it helped me solve my conditional compilation issues and kept me from asking the identical question. Thanks.

Jonathan Beerhalter
A: 

You can use reflection API. put all your 1.5 code in one class and 1.6 api in another. In your ant script create two targets one for 1.5 that won't compile the 1.6 class and one for 1.6 that won't compile the class for 1.5. in your code check your java version and load the appropriate class using reflection that way javac won't complain about missing functions. This is how i can compile my MRJ(Mac Runtime for Java) applications on windows.