Home   |   Archivio   |   Chi siamo   |   Contatti
Zhao Yonghong
Conditional Compilation Multiversion
Configuration Conditional Directives
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.

    // */

// #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

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