Chapter 8. JDK 1.4.2 Backwards Compatibility

Despite the fact that it has been a while since the release of JDK 5, there are projects that still require backwards compatibility with JDK 1.4.2. This is relatively common, and the reasons for it are diverse. To deal with this type of requirement, JBoss AOP provides two different solutions. By using one of them, you can use all features JBoss AOP provides, including annotation-related features, and still keep your code backwards compatible with JDK1.4.2.

8.1. JBoss Retro

The first solution to achieve JDK 1.4.2 backwards compatibility is to use the JBoss Retro tool. JBoss Retro converts JDK 5 compiled bytecodes into bytecodes that can be run using a JDK 1.4.2 virtual machine.

This tool acts as a bytecode processor, and using it is very simple. You just need to write your code using JDK 5 constructs and features, and then compile it, using JDK 5. Next, process the generated bytecodes using JBoss Retro tool (just like you do when you use aopc compiler), and now your bytecodes are ready to run using a JDK 1.4.2 virtual machine. Notice this solution allows not only the use of JBoss AOP annotations, but of most JDK 5 features and new API operations.

The simplest way to run JBoss Retro is using the ant task. You just need to declare it:

<taskdef name="retro" classname="org.jboss.ant.tasks.retro.Retro" classpathref="jboss.retro.classpath"/>
      

And use it as in the following example:

<retro compilerclasspathref="jboss.retro.classpath" destdir=".">
   <classpath refid="jboss.retro.classpath"/>
   <classpath path="."/>
   <src path="."/>
</retro>

This task takes the following parameters:

  • compilerclasspathref - This represent the jars needed for the JBoss Retro processor to work. The compilerclasspath version takes the paths of the jar files, and the compilerclasspathref version takes the name of a predefined ant path.
  • classpath or classpathref - Path to the compiled classes to be processed by JBoss Retro. The classpath version takes the path of the directory, and the classpathref version takes the name of a predefined ant path.
  • verbose - Default is false. If true, verbose output is generated, which comes in handy for diagnosing unexpected results.
  • suppress - Default is true. If false, error messages will not be suppressed.
  • maxmemory - Sets the maximum memory of the java task that will be forked.
  • destdir - the dir where JBoss Retro will write the resulting bytecodes.

It is also possible to run JBoss Retro with the following command line:

   $ java -cp <all the JBoss AOP jars and the directory containing files we want to AOP> \
         -verbose <true/false> -suppress <true/false> -destdir <
         org.jboss.ant.tasks.retro.Weaver \
         [-bytecode]<files>+
            

JBoss Retro is the de facto standard solution JBoss Group provides to achieve JDK 1.4.2 backward compatibility. However, if you do not have the option to compile your code using JDK 5, you should go with the next solution, the annotation compiler.

8.2. JDK1.4.2 Annotation Compiler

Unlike JBoss Retro, the annotation compiler does not support all JDK 5 constructs and new APIs. Its functionality consists in supporting only annotations, that must be written in the form of doclets. Nevertheless, this is enough to allow the use of all JBoss AOP features, and doesn't require a JDK 5 compiler.

This way, if you can't use a JDK 5 compiler to compile your code, you should stick with the annotation compiler. It will process your application's bytecodes, transforming doclets into annotations. The result of this transformation is that your doclets will become viewable by JBoss AOP as if they were regular JDK 5 annotations.

In the next sections, we will see what is the format your doclets need to follow in order to be transformed into annotations, and how to use the annotation compiler.

8.1. Annotations with JDK 1.4.2

In JDK 5, annotations must map to an annotation type, which is defined using the following syntax:

   package com.mypackage;

   public @interface MyAnnotation
   {
      String myString();
      int myInteger();
   }
            

Similarly, annotations for use with the annotation compiler also need to map to a type. And this one is defined in exactly the same way as above, with the important difference that '@interface' is replaced by 'interface'. i.e. the simulated annotation type is a normal Java interface:

   package com.mypackage;

   public interface MyAnnotation extends org.jboss.lang.Annotation
   {
      String myString();
      int myInteger();
   }
            

One difference from AOP 1.x is that the interfaces defining the annotations must now extend org.jboss.lang.Annotation, this is because JBoss Retro is now the primary mechanism for using annotations in JDK 1.4.2.

The syntax for using annotations in JDK 1.4.2 is almost exactly the same as JDK 5 annotations except for these subtle differences:

  • they are embedded as doclet tags
  • You use a double at sign, i.e. '@@'
  • You MUST have a space after the tag name otherwise you will get a compilation error. (This is the quirkiness of the QDox doclet compiler used to compile the annotations.')
  • You cannot import the annotation type, you must use the fully qualified name of the interface.
  • You can only annotate top-level and inner classes, and their constructors, methods and fields. Annotating anonyomus classes, local classes, and parameters for constructors or methods is not supported.
  • You cannot specify default values for an annotation's value

This example shows an annotated class in JDK 1.4.2:

   package com.mypackage;

   /**
    * @@com.mypackage.MyAnnotation (myString="class", myInteger=5)
    */
   public class MyClass
   {
      /**
       * @@com.mypackage.MyAnnotation (myString="field", myInteger=4)
       */
      private String myField;

      /**
       * @@com.mypackage.MyAnnotation (myString="constructor", myInteger=3)
       */
      public MyClass()
      {
      }

      /**
       * @@com.mypackage.MyAnnotation (myString="method", myInteger=3)
       */
      public int myMethod()
      {
      }
   }
            

The next aspect is the JDK1.4.2 version of the @Introduction exammple (Chapter 6, Annotation Bindings). Notice the slight difference in the JDK 1.4.2 annotation: class values don't have the ".class" suffix:

   package com.mypackage;

   /*
    * @@org.jboss.aop.Aspect (scope = Scope.PER_VM)
    */
   public class IntroAspect
   {
      /*
       * @org.jboss.aop.Introduction (target=com.blah.SomeClass, \
             interfaces={java.io.Serializable})
       */
      public static Object pojoNoInterfacesIntro;
   }
         

Now, look at the next example:

   package com.mypackage;

   import org.jboss.aop.introduction.AnnotationIntroduction;

   /**
    * @@org.jboss.aop.InterceptorDef (scope=org.jboss.aop.advice.Scope.PER_VM)
    * @@org.jboss.aop.Bind (pointcut="all(com.blah.SomePOJO)")
    */
   public class IntroducedAnnotationInterceptor implements Interceptor
   {
      /**
       * @@org.jboss.aop.AnnotationIntroductionDef \
             (expr="method(* com.blah.SomePOJO->annotationIntroductionMethod())", \
             invisible=false, \
             annotation="@com.mypackage.MyAnnotation \
             (string='hello', integer=5, bool=true)")
       */
      public static AnnotationIntroduction annotationIntroduction;

      public String getName()
      {
         return "IntroducedAnnotationInterceptor";
      }

      public Object invoke(Invocation invocation) throws Throwable
      {
         return invocation.invokeNext();
      }
   }
         

The code above is the jdk1.4.2 version equivalent to the @AnnotationIntroductionDef example we have seen in Chapter 6, Annotation Bindings. Note that, in the version above, the reference to only uses one '@'. In addition,the value for its string parameter uses single quotes instead of double ones.

8.2. Enums in JDK 1.4.2

Another JDK 5 feature that JBoss AOP helps introduce to JBoss 1.4.2 are Enums. As an example we can look at the org.jboss.aop.advice.Scope enum that is used for the @Aspect annotation. Here is the JDK 5 version.

            package org.jboss.aop.advice;

            public enum Scope
            {
               PER_VM, PER_CLASS, PER_INSTANCE, PER_JOINPOINT
            }
            

And it's usage in JDK 5

   package com.mypackage;

   @Aspect (scope=org.jboss.aop.advice.Scope.PER_VM)
   public class SomeAspect
   {
   }
            

The usage in JDK 1.4.2 is similar:

   package com.mypackage;

   /**
    * @@org.jboss.aop.Aspect (scope=org.jboss.aop.advice.Scope.PER_VM)
    */
   public class SomeAspect
   {
      //...
   }
            

However the declaration of the enumeration is different in 1.4.2:

   package org.jboss.aop.advice;

   import java.io.ObjectStreamException;

   public class Scope extends org.jboss.lang.Enum
   {
      private Scope(String name, int v)
      {
         super(name, v);
      }

      public static final Scope PER_VM = new Scope("PER_VM", 0);
      public static final Scope PER_CLASS = new Scope("PER_CLASS", 1);
      public static final Scope PER_INSTANCE = new Scope("PER_INSTANCE", 2);
      public static final Scope PER_JOINPOINT = new Scope("PER_JOINPOINT", 3);

      private static final Scope[] values = {PER_VM, PER_CLASS, PER_INSTANCE, PER_JOINPOINT};

      Object readResolve() throws ObjectStreamException
      {
         return values[ordinal];
      }

   }
            

To create your own enum class for use within annotations, you need to inherit from org.jboss.lang.Enum. Each enum has two values, a String name, and an integer ordinal. The value used for the ordinal must be the same as it's index in the static array.

8.3. Using Annotations within Annotations

The annotation compiler allows you to use annotations within annotations. This is best illustrated with an example. The definitions of the annotation interfaces in JDK 1.4.2:

            com.mypackage;

            public interface Outer
            {
               Inner[] values();
            }
            

            com.mypackage;

            public interface Inner
            {
               String str();
               int integer();
            }
            

The annotations can be applied as follows

            com.mypackage;

            /**
             * @@com.mypackage.Outer ({@@com.mypackage.Inner (str="x", integer=1), \
                   @@com.mypackage.Inner (str="y", integer=2)})
             */
            public class Test
            {
               Inner[] values();
            }
            

8.4. Using the Annotation Compiler

In order to use the JDK 1.4.2 annotations you have to precompile your files with an annotation compiler.

To use the annotation compiler you can create a simple ant build.xml file

<?xml version="1.0" encoding="UTF-8"?>

<project default="run" name="JBoss/AOP">
   <target name="prepare">

Include the jars AOP depends on

      <path id="javassist.classpath">
         <pathelement path="../../../javassist.jar"/>
      </path>
      <path id="trove.classpath">
         <pathelement path="../../../trove.jar"/>
      </path>
      <path id="concurrent.classpath">
         <pathelement path="../../../concurrent.jar"/>
      </path>
      <path refid="jboss.common.core.classpath"/>
      <path refid="jboss.common.logging.spi.classpath"/>
      <path refid="jboss.common.logging.log4j.classpath"/>
      <path refid="jboss.common.logging.jdk.classpath"/>
         <pathelement path="../../../jboss-common.jar"/>
      </path>
      <path id="jboss.aop.classpath">
         <pathelement path="../../../jboss-aop.jar"/>
      </path>
      <path id="qdox.classpath">
         <pathelement path="../../../qdox.jar"/>
      </path>
      <path id="classpath">
         <path refid="javassist.classpath"/>
         <path refid="trove.classpath"/>
         <path refid="jboss.aop.classpath"/>
      <path refid="jboss.common.core.classpath"/>
      <path refid="jboss.common.logging.spi.classpath"/>
      <path refid="jboss.common.logging.log4j.classpath"/>
      <path refid="jboss.common.logging.jdk.classpath"/>
         <path refid="concurrent.classpath"/>
         <path refid="qdox.classpath"/>
      </path>

Define the ant task that does the annnotation compilation

      <taskdef
         name="annotationc"
         classname="org.jboss.aop.ant.AnnotationC"
         classpathref="jboss.aop.classpath"/>
   </target>

   <target name="compile" depends="prepare">]></programlisting>
         Compile the source files
         <programlisting><!CDATA[
      <javac srcdir="."
         destdir="."
         debug="on"
         deprecation="on"
         optimize="off"
         includes="**">
            <classpath refid="classpath"/>
      </javac>

Run the annotation compiler

      <annotationc compilerclasspathref="classpath" classpath="." bytecode="true">
         <src path="."/>
      </annotationc>
   </target>
</project>

The org.jboss.aop.ant.AnnotationC ant task takes several parameters.

  • compilerclasspath, compilerclasspathref, classpath, classpathref and verbose - These have the same meaning as in the JBoss Retro task.
  • bytecode - The default is false. If true the annotation compiler instruments (i.e. modifies) the class files with the annotations. In this case, the classes must be precompiled with javac and classpath or classpathref must be specified.
  • xml - Default is false. If true an xml file is generated containing information of how to attach the annotations at a later stage in the aop process.
  • output - If xml="true", this lets you specify the name you would like for the generated xml file. The default name is metadata-aop.xml

You cannot currently specify both bytecode and xml.

You can also run org.jboss.aop.ant.AnnotationC from the command line, you need

   $ java -cp <all the JBoss AOP jars and the directory containing files we want to AOP> \
         org.jboss.aop.annotation.compiler.AnnotationCompiler \
         [-xml [-o outputfile ]] [-bytecode]<files>+
            

In the /bin folder of the distribution we have provided batch/script files to make this easier. It includes all the aop libs for you, so you just have to worry about your files. The usage is:

   $ annotationc <classpath> [-verbose] [-xml [-o outputfile]] [-bytecode] <dir_or_file>+
            

  • classpath - path to your classes and any jars your code depends on

The other parameters are the same as above.