Java conditional compilation: how to prevent code chunks from being compiled?
Asked Answered
L

9

59

My project requires Java 1.6 for compilation and running. Now I have a requirement to make it working with Java 1.5 (from the marketing side). I want to replace method body (return type and arguments remain the same) to make it compiling with Java 1.5 without errors.

Details: I have an utility class called OS which encapsulates all OS-specific things. It has a method

public static void openFile(java.io.File file) throws java.io.IOException {
  // open the file using java.awt.Desktop
  ...
}

to open files like with double-click (start Windows command or open Mac OS X command equivalent). Since it cannot be compiled with Java 1.5, I want to exclude it during compilation and replace by another method which calls run32dll for Windows or open for Mac OS X using Runtime.exec.

Question: How can I do that? Can annotations help here?

Note: I use ant, and I can make two java files OS4J5.java and OS4J6.java which will contain the OS class with the desired code for Java 1.5 and 1.6 and copy one of them to OS.java before compiling (or an ugly way - replace the content of OS.java conditionally depending on java version) but I don't want to do that, if there is another way.

Elaborating more: in C I could use ifdef, ifndef, in Python there is no compilation and I could check a feature using hasattr or something else, in Common Lisp I could use #+feature. Is there something similar for Java?

Found this post but it doesn't seem to be helpful.

Any help is greatly appreciated. kh.

Levitical answered 24/12, 2010 at 11:44 Comment(5)
Compile both OS4J5 and OS4J6 into .class files and then write a custom class loader for picking which one based on the runtime version?Landaulet
@JUST I can't compile both with Java 1.5, since it doesn't support java.awt.Desktop and I will get an error on OS4J6.java.Levitical
Compile both with 1.6. The .class files are backward-compatible as long as you don't use 1.6-only APIs.Landaulet
@JUST I made a simple test java file (without 1.6-specific code) which I compiled using 1.6 and I couldn't run it with 1.5: Exception in thread "main" java.lang.UnsupportedClassVersionError: Bad version number in .class file.Levitical
Preprocessors do a LOT MORE than conditional code compilation. I feel a little disheartened that folk mark preprocessor questions as duplicates for: a topic on "conditional compilation" topic.Aptitude
G
46

Nope there isn't any support for conditional compilation in Java.

The usual plan is to hide the OS specific bits of your app behind an Interface and then detect the OS type at runtime and load the implementation using Class.forName(String).

In your case there no reason why you can't compile the both OS* (and infact your whole app) using Java 1.6 with -source 1.5 -target 1.5 then in a the factory method for getting hold of OS classes (which would now be an interface) detect that java.awt.Desktop class is available and load the correct version.

Something like:

 public interface OS {
     void openFile(java.io.File file) throws java.io.IOException;
 }

 public class OSFactory {
     public static OS create(){
         try{
             Class.forName("java.awt.Desktop");
             return new OSJ6();
         }catch(Exception e){
             //fall back
             return new OSJ5();
         }
     }
 }
Greengage answered 24/12, 2010 at 11:47 Comment(1)
Conditional compilation can be semi-done: see https://mcmap.net/q/331164/-conditional-java-compilationVanzandt
R
19

Hiding two implementation classes behind an interface like Gareth proposed is probably the best way to go.

That said, you can introduce a kind of conditional compilation using the replace task in ant build scripts. The trick is to use comments in your code which are opened/closed by a textual replacement just before compiling the source, like:

/*{{ Block visible when compiling for Java 6: IFDEF6

public static void openFile(java.io.File file) throws java.io.IOException {
  // open the file using java.awt.Desktop
  ...

/*}} end of Java 6 code. */

/*{{ Block visible when compiling for Java 5: IFDEF5

  // open the file using alternative methods
  ...

/*}} end of Java 5 code. */

now in ant, when you compile for Java 6, replace "IFDEF6" with "*/", giving:

/*{{ Block visible when compiling for Java 6: */

public static void openFile(java.io.File file) throws java.io.IOException {
  // open the file using java.awt.Desktop
  ...

/*}} end of Java 6 code. */

/*{{ Block visible when compiling for Java 5, IFDEF5

public static void openFile(java.io.File file) throws java.io.IOException {
  // open the file using alternative methods
  ...

/*}} end of Java 5 code. */

and when compiling for Java 5, replace "IFDEF5". Note that you need to be careful to use // comments inside the /*{{, /*}} blocks.

Representational answered 24/12, 2010 at 12:35 Comment(1)
yep, this is a useful trick, I use it to "turn off" standard out in production builds for security reasons (i.e. it's insufficient to do this based on runtime conditions).Poultice
S
6

You can make the calls using reflection and compile the code with Java 5.

e.g.

Class clazz = Class.forName("java.package.ClassNotFoundInJavav5");
Method method = clazz.getMethod("methodNotFoundInJava5", Class1.class);
method.invoke(args1);

You can catch any exceptions and fall back to something which works on Java 5.

Schizophrenia answered 24/12, 2010 at 11:53 Comment(0)
B
6

The Ant script introduced below gives nice and clean trick.

link: https://weblogs.java.net/blog/schaefa/archive/2005/01/how_to_do_condi.html

in example,

//[ifdef]
public byte[] getBytes(String parameterName)
        throws SQLException {
    ...
}
//[enddef]

with Ant script

        <filterset begintoken="//[" endtoken="]">
            <filter token="ifdef" value="${ifdef.token}"/>
            <filter token="enddef" value="${enddef.token}"/>
        </filterset>

please go to link above for more detail.

Baalbek answered 27/12, 2013 at 8:41 Comment(2)
The link is broken.Topeka
Conditional compilation checks for a condition. Where's the condition in a lonely //[ifdef] ?Ligni
M
6

In java 9 it's possible to create multi-release jar files. Essentially it means that you make multiple versions of the same java file.

When you compile them, you compile each version of the java file with the required jdk version. Next you need to pack them in a structure that looks like this:

+ com
  + mypackage
    + Main.class
    + Utils.class
+ META-INF
  + versions
    + 9
      + com
        + mypackage
          + Utils.class

In the example above, the main part of the code is compiled in java 8, but for java 9 there is an additional (but different) version of the Utils class.

When you run this code on the java 8 JVM it won't even check for classes in the META-INF folder. But in java 9 it will, and will find and use the more recent version of the class.

Moro answered 20/6, 2017 at 14:32 Comment(0)
M
5

if you don't want conditionally enabled code blocks in your application then a preprocessor is only way, you could take a look at java-comment-preprocessor which can be used for both maven and ant projects
p.s.
also I have made some example how to use preprocessing with Maven to build JEP-238 multi-version JAR without duplication of sources

Mott answered 21/3, 2015 at 11:58 Comment(0)
M
4

I'm not such a great Java expert, but it seems that conditional compilation in Java is supported and easy to do. Please read:

http://www.javapractices.com/topic/TopicAction.do?Id=64

Quoting the gist:

The conditional compilation practice is used to optionally remove chunks of code from the compiled version of a class. It uses the fact that compilers will ignore any unreachable branches of code. To implement conditional compilation,

  • define a static final boolean value as a non-private member of some class
  • place code which is to be conditionally compiled in an if block which evaluates the boolean
  • set the value of the boolean to false to cause the compiler to ignore the if block; otherwise, keep its value as true

Of course this lets us to "compile out" chunks of code inside any method. To remove class members, methods or even entire classes (maybe leaving only a stub) you would still need a pre-processor.

Mousey answered 27/5, 2016 at 23:21 Comment(2)
Method what you specified here works only when you have code that is compilable.Hysterical
This is of use in certain circumstances. However it's of no use in the case 1. compiling out between multiple methods and declarations, and 2. (as pointed out by @LXSoft) of currently unworking code.Ligni
R
0

Java Primitive Specializations Generator supports conditional compilation:

   /* if Windows compilingFor */
   start();
   /* elif Mac compilingFor */
   open();
   /* endif */

This tool has Maven and Gradle plugins.

Romy answered 8/4, 2019 at 18:34 Comment(0)
N
0

hi I have got similar problem when I have shared library between Java SDK abd Android and in both environments are used the graphics so basically my code must to work with both java.awt.Graphics and android.graphics.Canvas, but I don't want to duplicate almost any code. My solution is to use wrapper, so I access to graphisc API indirectl way, and I can change a couple of imports, to import the wrapper I want to compile the projects. The projects have some cone shaded and some are separate, but there is no duplicating anything except of couple of wrappers etc. I think it is the best what I can do.

Nkvd answered 13/1, 2021 at 21:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.