MokaByte 68 - 9mbre 2002 
Implement Multi-version Support for Your Java Products
di
Zhao Yonghong
This article introduces Multiversion, a quite helpful open-source multi-version tool, which can parse conditional directives, preprocess code, and generate bytecode for Java project

Can your Java code always "write once, run everywhere" in Java's continuing evolution? There're the multiple JDK releases (JDK1.0.2, JDK1.1.8, JDK1.2.2, JDK1.3.1 and JDK1.4.1) and the multiple Java 2 platforms (Java 2 Micro Edition [J2ME], Java 2 Standard Edition [J2SE], and Java 2 Enterprise Edition [J2EE]). How to maintain a cross-edition, cross-configuration and cross-profile Java project for all Java platforms without cross-JDK portability problems? This article presents such a particularly attractive solution for maintaining flexible Java code by taking advantage of Multiversion (a version preprocessor and compiler tool for Java).

 

Conditional Compilation
Conditional compilation is valuable for debugging code, code optimization, platform portability, performance profiling, backward compatibility, different user interface, and revision control. It includes or excludes portions of conditional directive-based code when compiling the code. Although a good Java compiler (such as Sun's javac) optimizes implicitly away the statically unreachable code block by static final boolean constant as the condition for a regular if statement, Java does not have any form of the C #ifdef or #if directives to perform conditional compilation. Multiversion reinforces Java by constructing such a preprocessor to offer less maintenance overhead by encapsulating the future work at the time it is best understood and much better coding style in terms of both maintainability and readability. It should greatly reduce the errors and burden of maintenance if Java programmer doesn't clutter up his files with too much conditional compilation sections.

 

Multiversion Configuration
All aspects of Multiversion are user configurable. Configuration is accomplished through using a simple INI configuration file to centralize version control. For example, a configuration file would be the following:


### This is a sample of Multiversion's version information file ###

#[Multiversion_SETUP] //The reserved setup block of multiversion which can be omitted.
#Source_File_Suffix_List=java,jsp //The file suffix list of code, separated by comma.
#directivePrefix=//# //The prefix of conditional directive. Although the prefix choice is arbitrary, //# is preferable for Java compatible syntax.
#codeMaskPrefix=/*// //The mask prefix of conditinal code block.
#codeMaskSuffix=//*/ //The mask suffix of conditinal code block.
#rebuildAll=false //Whether preprocess all source files or only the latest modified files.

### You can modify and append other version information block according the module below.
#[VersionName] //The version name which will be released.
#CONSTANT_LIST= //The list of preprocess constants, separated by comma. It's used to define compile-time constants for version conditional complication.
#SOURCE_PATH= //The absolute path which contains all source files.
#SOURCE_LIST= //The relative path list which contains some source subdirectories, separated by comma
#OUTPUT_PATH= //The absolute path which will contain all output files. The preprocessed source files is at its src subdirectory, and the compiled Java class files is at its classes subdirectory.
#JDK_PATH= //The JDK absolute installed path, which used to locate javac program. Multiversion will try to use System.getProperty("java.home") to locate, if it's not defined.
#CLASS_PATH= //The user class path. Multiple path entries are separated by semi-colons, and Multiversion will append silently the "OUTPUT_PATH/classes" path.

[Multiversion_JDK1.1.X]
CONSTANT_LIST=JDK1.1.X,DEBUG,LOG
SOURCE_PATH=src
SOURCE_LIST=com\hxtt\tool\multiversion
OUTPUT_PATH=output\JDK1.1.X
JDK_PATH=\jdk1.1.8
CLASS_PATH=\jdk1.1.8\lib\classes.zip;.\classes

[Multiversion_JDK1.2.X]
CONSTANT_LIST=JDK1.2.X,RELEASE,LOG
SOURCE_PATH=src
SOURCE_LIST=com\hxtt\tool\multiversion
OUTPUT_PATH=output\JDK1.2.X
JDK_PATH=\jdk1.2.2
CLASS_PATH=\jdk1.2.2\lib;.\classes


You don't have to change a single line of code to release multiple versions after you have defined version information. Just use "java com.hxtt.tool.multiversion.Multiversion iniFile versionName" to build your specific version. The tool takes two inputs: the name of the INI configuration file containing version information and the name of the specific version, for example, "java com.hxtt.tool.multiversion.Multiversion sample.ini Multiversion_JDK1.1.X". Note: Multiversion can be useful for things other than Java source code, you can use preprocessor statements inside JSP, CPP, XML, and so on.

 

Conditional Directives
Let's see some C conditional expression samples:

#if defined (HAVE_ALLOCA_H) || (defined(sparc) && (defined(sun) || (!defined(USG) && !defined(SVR4) && !defined(__svr4__))))
#if !defined (__GNUC__) && defined (_AIX)
#ifndef HAVE_GETOPT_LONG
#ifdef _WIN32
#else
#else if !defined (__GNUC__) && defined (_AIX)
#endif

In Multiversion, directive prefix choice is arbitrary through a configurable parameter. Multiversion supports all the conditional expression samples shown above if you choose # as the directive prefix. Considering Java language compatibility, //# is preferable as directive prefix. Conditional directives are used to include or exclude blocks of code from a source file, based on the result of a conditional expression, or a preprocess constant. Utilized wisely, conditional compilation can save you considerable time and effort in having inclusion/exclusion/variations of member variables, code block, entire methods, class constructors, or even entire classes. If you wish to tell specially Multiversion whether include or exclude some source files in a specific version, you can use:

//#if condintinalExpression1
//#INCLUDE
//#else if condintinalExpression2
//#NOT INCLUDE
//#endif

Version Preprocessing and Version Compilation
Multiversion takes the version information from configuration file, parses all conditional expressions, and generates preprocessed code. Then we need a make-like utility to compile all preprocessed source code into jvm bytecode. Sun provides one package for creating javac compiler object. You can use the code given below:

//#if defined (JDK1.2.X) || defined (JDK1.3.X) || defined (JDK1.4.X)
com.sun.tools.javac.Main javac = new com.sun.tools.javac.Main();//Note: You should include \jdk1.X.X\lib\tools.jar in your classpath.
//#else if defined(JDK1.1.X)
/*//
sun.tools.javac.Main javac = new sun.tools.javac.Main();//Note: You should include \jdk1.1.X\lib\classes.zip in your classpath.
//*/
//#endif

String[] args = new String[] {"-d", System.getProperty("user.dir"),filename};//Compile method takes the familiar command-line input arguments.
int status = javac.compile(args);// A call to compile() with some command-line arguments will return a status code indicating either success or a problem.
object)

I would never recommend the above approach to compile code since there is no API documentation for those tools, and there is also no guarantee that those tools won't be changed again. The commendatory way is using Runtime.exec(command) to complete a javac task in a separate process, since both of the list of preprocessed source files and destination directory for your compiled classes can be provided.


Process proc= Runtime.getRuntime().exec(command);// Gets a Process object for managing the javac subprocess.
InputStream stderr = proc.getErrorStream();//Gets the error stream of the javac subprocess.
BufferedReader br = new BufferedReader(new InputStreamReader(stderr));
String line = null;
while ( (line = br.readLine()) != null){
log(line);//Logs the output of the javac subprocess
}

//Waits for the javac subprocess to complete,
// and returns the exit value for the javac subprocess. Normally,
//
an exit value of 0 indicates success; any nonzero value
// indicates an error.
int exitVal = proc.waitFor();

By using conditional compilation constants to control version generation, you can deliver a cleaner production release to multiple Java platforms, and usually reduce the size of the compiled application and consequently the amount of required memory which result in a better class loading performance.

Conclusion
I don't wish Sun to add conditional compilation support to Java Language Specification like Visual J++. Let's keep Java as simple and straightforward as possible. On the other hand, I expect a few simple condition compile operators can be feasibly built on top of Java source using IDE tools so that "write multi-version once, run specific version somewhere".


References
1. Sun, The Java Language Specification
2. Elliot Berk, JLex: A Lexical Analyzer Generator for Java
3. Scott E. Hudson, CUP: A Parser Generator for Java

 

Risorse
Scarica qui i sorgenti e la documentazione javadoc presentati nell'articolo

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it