 |
How to do Conditional Compilation with Java
Posted by schaefa on January 20, 2005 at 04:50 PM | Comments (18)
These instructions are a way to do conditional compilation with Java like the C/C++ #ifdef. In Java there is no preprocessor and so we need to work around this missing feature. This work around is using Ant's copy and filter feature to create the source with the different code in it. In this example I wanted to accomplish the following:
- The Java classes must be valid Java code so that any editor/IDE can read it as regular Java code
- The conditional compilation must also work outside Java methods so a constant boolean if statement does not work
- The selection of the version to compile to must be settable when the build of the project is started
- The compilation must work properly without cleaning the project first
1. Add into your Java classes a start tag (like //[ifdef]) and an end tag (like //[enddef]) around the code in question. To include code for version 1:
//[ifdef]
import java.sql.ParameterMetaData;
//[enddef]
or
//[ifdef]
public byte[] getBytes(String parameterName)
throws SQLException {
...
}
//[enddef]
The // at the beginning of the tag ensures that this is still valid Java source code.
To exclude code:
/* //[endef]
public byte[] getBytes(String parameterName) {
...
}
//[ifdef] */
2. To swap the used code for version 2 the start and end tag just have to be set to /* respectively */. In this case the included code is excluded and the excluded code in included.
3. Add a copy-n-filter task in your Ant build script. There the source coded will be copied away and the filter tags are replace with the actual value. For version 1 the start / end tag will vanish and for version 2 they will be substituted with /* and */. Attention: the source code is copied into two different root directory one for version 1 and the other for version 2. Because Ant tries to avoid copies if the file does not have changed this can lead to having the wrong version of code in your target directory. This is an Ant script snippet:
<copy todir="${src.dir}/${version}">
<fileset dir="${src.dir}/java">
<include name="**/*.java"/>
</fileset>
<filterset begintoken="//[" endtoken="]">
<filter token="ifdef" value="${ifdef.token}"/>
<filter token="enddef" value="${enddef.token}"/>
</filterset>
</copy>
4. Set the start / end tag token. Note that the start / end tag token are set at the end to their default value only when not already set.
<condition property="ifdef.token" value="/*">
<equals arg1="${compile.to.version.1}" arg2="false"/>
</condition>
<condition property="enddef.token" value="*/">
<equals arg1="${compile.to.version.1}" arg2="false"/>
</condition>
<!-- If not set already make sure that they are defined but empty -->
<property name="ifdef.token" value=""/>
<property name="enddef.token" value=""/>
If the Ant script is started without compile.to.version.1 set to false it will compile the code to version 1. If the parameter is set to false it will compile to version 2. This can be accomplished this way:
ant -Dcompile.to.version.1=false build
5. Make sure that you compile from the right directory:
<javac ...
>
<src path="${src.dir}/${version}"/>
</javac>
I hope this will help you in the future – Andy
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Very interesting Andreas,
I had not seen this before; Thanks!
I noticed how it requires Ant:
Perhaps this will now make it clear to Sun that it is high time to include an official macro language for Java. If only to make the source consistent across all builders. Make no mistake; if Sun doesn't do this: The community will!
PS And with this, we will finally dump all of this AOP cra... nonsense.
Posted by: cajo on January 20, 2005 at 08:04 PM
-
Given that we have Annotations we could define some annotations like so:
@Retention (SOURCE)
@Target (???) // ALL possible values
public @interface ifdef {
String value();
}
@Retention (SOURCE)
@Target (???) // ALL possible values
public @interface endif {}
And use them like so:
import java.util.logging.*;
public class Example {
private static final Logger LOGGER = Logger.global;
public byte[] getBytes(String parameterName)
throws SQLException {
@ifdef (DEBUG)
LOGGER.info("Some complex str concat based debug message goes here");
@endif
}
And build a compiler plugin to deal with these annotations. However, @Target of the annotations does not contain meaningful values to place them ***ANYWHERE*** in the source code of the compilation units! This approach will eliminate addition of new keywords or syntax to the language.
However, I wouldn't think it would be prudent to add this one to the language spec. First of all, the idea of annotations itself might kill the long forgotten WORA - Write Once Run Anywhere philosophy. But then again, the community could build a compiler plug-in if they need that sort of functionality.
Posted by: chakrayadavalli on January 20, 2005 at 09:51 PM
-
If you´re using Ant as macro language you could also do replacements via task or filter.
Posted by: jhm on January 21, 2005 at 12:22 AM
-
I recommend using VPP instead.
Posted by: erikhatcher on January 21, 2005 at 02:21 AM
-
How would a macro language be better than Ant or AOP?
Posted by: monika_krug on January 21, 2005 at 04:04 AM
-
Monika,
Ant is a great build tool, I use it exclusively. However it is not the only build tool out there. Adding macro language support to specific builders reduces code portablity.
When you consider what it actually does; AOP is simply a degenerate subset of the functionality provided by a macro language. It's a hack-around, so to speak. Worse, it is performed on bytecode, rather than source code, which makes post process inspection unpleasant.
What would be best is an official Java macro language specification, and preprocessor. Otherwise very shortly there will be many variants of macro languages.
John
Posted by: cajo on January 21, 2005 at 04:56 AM
-
I see no burning need for a preprocessor or macro language in Java, since many of the things that make them indispensible to C and C++, such as differing word sizes or byte order among platforms, are not a factor in Java.
Moreover, if I have a class file that I know was compiled from a particular source file, I like the fact that I can look at the source file and know exactly how the class will behave without also needing to know what preprocessor flags were set at compile time. To my mind, there are numerous more elegant ways to solve the problems that might be solved by a Java preprocessor.
That said, however, there's always the old standby, using public final static boolean flags, a la:
public final static boolean DEBUG = true;
...
if(DEBUG) {
// do something
}
with the assumption that the compiler will optimize out everything in the if block since it can never be reached, and if it doesn't the code doesn't execute anyway.
Posted by: dglasser on January 21, 2005 at 07:04 AM
-
with the assumption that the compiler will optimize out everything in the if block since it can never be reached, and if it doesn't the code doesn't execute anyway.
I should have said, "with the assumption that, if DEBUG is false, the compiler will optimize out..." etc.
Posted by: dglasser on January 21, 2005 at 07:06 AM
-
Preprocessors are seldom the best answer for Java. The example code could have been restructured to be more maintainable without a preprocessor. With that said, there are other preprocessors available, like the ones here.
Posted by: coxcu on January 21, 2005 at 07:28 AM
-
The Java+ preprocessor looks like it has some potential Curt. Is Brad any relation, or is that just a surprising coincidence?
If we are discussing solely conditional compilation, then I agree with Dave above, the if(DEBUG) Java language approach is cleaner.
I was thinking more about the algorighmic meta-programming features of a preprocessor; where macros actually generate source code. Brad alludes to these types of features in his Future Plans section.
Posted by: cajo on January 21, 2005 at 08:42 AM
-
Some thoughts to the comments
1) Conditional compilation or a macro language is not a replacement of AOP. With the macro language one can add/replace or remove code anywhere whereas in AOP one can only add code add the edge of a method call or can conpletely replace the entire method. On the other hand AOP can inject code into bytecode and some of them can do this add runtime. Also AOP can inject code on the caller as well as on the method side meaning that I can inject code that is for a particular group of classes using a certain method. Finally with the AOP descriptor language I can inject code very easily without chaning a lot of source code fast and easy.
2) The if(DEBUG) {} trick is only working within a method and therefore I can add/replace/remove import statements, member and method declarations or entire methods.
3) Conditional compilation is useful when one has to support different JDK versions. For example one could not provide code for the JDBC of JDK 1.3 and JDK 1.4 within the same class. Therefore conditional compilation helps to migrate to the next JDK and use their feature and still being able to provide a backwards compatible version.
4) I want to emphasis again that one of the goal for this approach was to keep the source code w/o the conditional compilation a valid Java class to keep the support from editors and IDEs. So some of the other solutions do not meet this requirement.
Have fun and thanks for your comments - Andy
Posted by: schaefa on January 21, 2005 at 09:07 AM
-
Aren't pre-processors just a fiddle for a bad design in the first place? Abstract classes? Factories? Inheritance? Packages? OO, eh?
Posted by: rob005 on January 21, 2005 at 10:55 AM
-
Every tool is great if used wisely. For pre-processors helps to deal with code that needs different variations depending on JDK versions (like different JDBC version) or specification versions (JCA 1.0 to 1.5). AOP does not work here because it cannot change the method signature.
Of course my solution is not the best but it works, is elegant (meaning it does not invalidate the Java code) and only needs Ant / Maven to build with.
-Andy
Posted by: schaefa on January 21, 2005 at 11:33 AM
-
Conditional compiilation is must have. To implement such few conditional statemens in Java, SUN could do in one day. If you use preprocessor from company XY, in few years you can get into troubles, if company XY goes down and you don't have updated preprocessor.
"Aren't pre-processors just a fiddle for a bad design in the first place? Abstract classes? Factories? Inheritance? Packages? OO, eh?"
No rob005, get real life!
Gorazd
Posted by: gorazd_praprotn on January 22, 2005 at 05:35 AM
-
imho, conditional compilation is not necessary, and is not nice
if you really want to do this then that hack that this blog suggests is not the way to go and I'm quite annoyed that it has even been suggested - if you come to a new project where someone has implemented there own conditional compilation scheme by using a trick like this then it could be a real nightmare, especially if they are no longer around
Posted by: asjf on January 24, 2005 at 03:31 AM
-
Nice implmentation Andy...thank you. I've often thought and wondered why to myself conditional compilation is not part of the core J2SE.
Conditional compliation in some cases is necessary, or at the very least, nice to have. Say for example you have a static initializer that takes a lot of time whenever the ClassLoader references the class, directly or indirectly. Some builds, you might not need that static initialization, and not want to spend the extra resources upon it; conditional compilation for that build cold solve such.
Yes I know, one could argue to simply make an interface and two implementations of the class, or a similiar implementation with two subclasses of an abstract parent class. But then you're adapting the design to fit the limitations of Java.
Posted by: phlogistic on January 24, 2005 at 12:19 PM
-
Andy,
Can you please post the complete build.xml ? I'm having trouble implementing even a simple example. Of course I have little experience with Ant.
Thanks in Advance
Posted by: raviies on January 04, 2007 at 11:49 AM
-
Andreas,
This tip was *awesome*. I understand people that see conditional compiling as something that should not be necessary on a WORA world, since it can be handled by techniques such as inclusion or exclusion of different implementations of classes in the build process.
However, I had a problem with a J2ME game that had to be split in "light" and "full" editions due to size limits in some mobile devices (and having an extra class to handle the excluded code would kill the savings offered by the light edition). In that case, conditional compiling hits the bullseye!
Thank you very much for sharing it.
Posted by: chester_br on June 16, 2007 at 08:00 PM
|