Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

What version is your Java code?

Write and compile code targeting different Java versions

  • Print
  • Feedback

Page 5 of 5

public interface IJREVersion
{
    /** 'true' iff the current runtime version is 1.2 or later */
    boolean JRE_1_2_PLUS = _JREVersion._JRE_1_2_PLUS;
    /** 'true' iff the current runtime version is 1.3 or later */
    boolean JRE_1_3_PLUS = _JREVersion._JRE_1_3_PLUS;
    /** 'true' iff the current runtime version is 1.4 or later */
    boolean JRE_1_4_PLUS = _JREVersion._JRE_1_4_PLUS;
    
    /*
     * Use a dummy nested class to fake a static initializer for the outer
     * interface (I want IJREVersion as an interface and not a class so that
     * all JRE_XXX constants could be imported via "implements").
     */
    abstract class _JREVersion
    {
        private static final boolean _JRE_1_2_PLUS, _JRE_1_3_PLUS, JRE_1_4_PLUS;
        
        static
        {
            _JRE_1_2_PLUS = ((SecurityManager.class.getModifiers () & 0x0400) == 0);
            boolean temp = false;            
            if (_JRE_1_2_PLUS)
            {
                try
                {
                    StrictMath.abs (1.0);
                    temp = true;
                }
                catch (Error ignore) {}
            }
            _JRE_1_3_PLUS = temp;
            
            if (temp)
            {
                temp = false;
                try
                {    
                    " ".subSequence (0, 0);
                    temp = true;
                }
                catch (NoSuchMethodError ignore) {}
            }
            _JRE_1_4_PLUS = temp;
        }
    } // End of nested class
} // End of interface


(My comments in the downloadable version of IJREVersion explain why I chose not to rely on java.version and other system properties.)

The java.net.URL API has suffered from poor design since Java's beginning and has undergone substantial changes in just about every major J2SE release so far. But thanks to runtime version switching, the above code parses a URL in the same fashion in all Java versions 1.1 through 1.4 (except certain esoteric edge cases). Although the above code is just a demo, it shows how you can deal with such situations in general.

At this point you might be wondering, "URL.getPath() and URL.getQuery() are not available prior to J2SE 1.3. Wouldn't this cause class linkage errors in earlier versions?" As it turns out, in most cases this will not be a problem. If you read the JVM and Java language specifications where they describe class loading and linking, you will see that in general, a JVM is not allowed to complain about a nonexistent method or class unless it is actually needed at runtime. (The process of checking symbolic references from a given class to other classes and interfaces it might depend on is called resolution. Resolution can be eager or lazy, but the specifications require that all errors resulting from this process be thrown only at a point where a symbolic reference is actively used.)

So, in J2SDK 1.2, the code never executes the getPath()/getQuery() part and everything works out nicely. This works even if you declare instances of unavailable classes as long as you don't initialize them. In general, just referring to a class in code will not necessarily require the class's definition at runtime. Certain constructs (calling a class method, referencing a field, creating an array with the class as the component type, and a few others) that do require the definition are known as active uses of a class. You should carefully steer around them as you code various version-specific chunks of your classes. In fact, this is how I coded IJREVersion itself.

Dynamically loading J2SE version-specific class versions

The above IJREVersion solution is great when you need it. But as dear as it might be to any former C/C++ programmer's heart, occasionally it just won't work. This happens, for instance, when you need to subclass a class only available in a J2SE version that is more recent than your base version. To load a class, a JVM always needs to load its superclass first, and, if it is not found, it will cause loading errors. And of course you can't put the extends declaration in a version-dependent if/then block. There are other, more obscure, cases: There appears to be a verification bug in all Sun-compatible JVMs that is an exception to the lazy resolution rule. When an astore instruction embedded inside a try/catch block references a nonexistent class, loading of the current class fails with a NoClassDefFoundError even if the instruction never executes. You can prove that this failure is caused by faulty verification by adding the class in question to the bootstrap classpath (bootstrap classes are not verified and the error goes away). At the source code level, this problem typically exhibits itself when you assign an instance of the nonexistent class to a variable or when the nonexistent class is a thrown exception.

In such a case, a more radical approach is guaranteed to work:

  1. Factor out the necessary methods into a version-neutral interface
  2. Implement this API in several version-specific and independent classes (it is safe to use nested classes here too)
  3. At runtime, instantiate the API implementation via a Factory class that uses IJREVersion internally to figure out which version to load


This approach is illustrated by the following reimplementation of URLTest1:

public class URLTest2
{
    public static void main (String [] args)
        throws MalformedURLException
    {
        URL url = new URL (args [0]);
        
        // This is where the version check takes place: 
        final IURLParser parser = URLParserFactory.newURLParser ();
        System.out.println ("got IURLParser implementation: " + parser);
        
        final String [] split = parser.splitURL (url);
        
        System.out.println ("path: " + split [0]);
        System.out.println ("ref: " + split [1]);
        System.out.println ("query: " + split [2]);
    }
} // End of class
public interface IURLParser
{
    String [] splitURL (URL url); 
} // End of interface
public abstract class URLParserFactory implements IJREVersion
{
    /**
     * Creates and returns a new instance of {@link IURLParser} implementation
     */
    public static IURLParser newURLParser ()
    {
        if (JRE_1_3_PLUS)
            return new JRE13URLParser ();
        else
            return new JRE11URLParser ();
    }    
        
    /*
     * The pre J2SE 1.3 implementation of IURLParser
     */
    private static class JRE11URLParser implements IURLParser
    {
        public String [] splitURL (final URL url)
        {
            final String path, reference, query;
            reference = url.getRef ();
            
            final String file = url.getFile ();
            
            final int qindex = file.indexOf ('?');
            final int pindex = file.indexOf ('#');
            
            if (qindex >= 0)
            {
                path = file.substring (0, qindex);
                
                if (pindex >= 0)
                    query = file.substring (qindex + 1, pindex);
                else
                {
                    if (qindex < file.length () - 1)
                        query = file.substring (qindex + 1);
                    else
                        query = "";
                }
            }
            else
            {
                query = null;
                
                if (pindex >= 0)
                    path = file.substring (pindex);
                else
                    path = file;
            }
            
            return new String [] {path, reference, query};
        }
        
    } // End of nested class
    
    /*
     * The Java 1.3+ implementation of IURLParser
     */
    private static class JRE13URLParser implements IURLParser
    {
        public String [] splitURL (final URL url)
        {
            return new String [] {url.getPath (), url.getRef (), url.getQuery ()};
        }
        
    } // End of nested class
    
} // End of class


Although the code above does not use Class.forName(), it effectively does the same kind of dynamic loading of different IURLParser implementations on an as-needed basis. This Factory approach ensures lazy resolution of all dynamic class dependencies and will work even when inline preprocessor-like usage of JRE_XXX constants (as in URLTest1) won't.

If you use a combination of this idea with correct -target and -bootclasspath compiler options, you will have a robust Java application supported on the largest possible set of J2SE platforms.

Code for version variation

Whew! Congratulations if you got this far. I have delved into Java details that are definitely not pretty. Coding for several Java versions in the same application is not common or enjoyable. But when it is a requirement or design choice, the ideas I outlined will serve you in good stead. Additionally, setting explicit compilation targets and knowing how a compiler locates class definitions will help you avoid accidentally building backwards-incompatible classes.

About the author

Vladimir Roubtsov has programmed in a variety of languages for more than 13 years, including Java since 1995. Currently, he develops enterprise software as a senior engineer for Trilogy in Austin, Texas.
  • Print
  • Feedback

Resources