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
|