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.
// */
// #endifString[]
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