Accessing attributes at runtime

The Attributes class contains static methods that access and test custom attributes of the type Attribute. This usage pattern mimics that of the collection classes found in java.util in which there is a base class Collection and a class Collections which provides various utility methods. You can obtain the attributes associated with a class, method, or field. Currently these attributes are associated with these program elements through the use of Javadoc tags. It might be more natural to provide programmatic access in a reflection like manner though java.lang.Class or java.lang.reflect.Field/Method

To obtain attributes defined at the class level associated with the attrib4j.examples.ExampleClass whose source file is shown below.

    Class cls = attrib4j.examples.ExampleClass.class;
    Attribute[] classAttribs = Attributes.getAttributes(cls);
    

For method attributes:

    Method method = cls.getMethod("deposit",new Class[]{int.class, long.class});
    Attribute[] methodAttribs = Attributes.getAttributes(method);
    

Method attributes can also be retrieved by filtering on their class.

    Method method = cls.getMethod("deposit",new Class[]{int.class, long.class});
    Attribute[] methodAttribs = 
                   Attributes.getAttributes(method, TransactionAttribute.class);
                        

For field attributes:

    Field field = cls.getField("name");
    Attribute[] fieldAttribs = Attributes.getAttributes(field);
    

You would then cast the elements in the object array depending on the result of performing an 'instanceof' test. The source source for TransactionExample is shown below and demonstrates how to specify and extract the attributes.

/**
 * A simple example class that demonstrates the use of custom
 * attributes.  The idea is to associate EJB like transaction
 * attributes to a method, much like would be described in the
 * deployment descriptor.  
 * 
 * @Transaction ("Never")
 */
public class TransactionExample {

    /**
     * Create an instance.  
     */
    public TransactionExample() {

    }

    /**
     * 
     * @param inputData represents information to use in a transaction.
     * 
     * 
     * @attrib4j.examples.attributes.Transaction ("Required")
     *
     */
    public void doDatabaseWork(Vector inputData) {
	    
        //would do database related work here
        System.out.println("Working inside attribute demarcated transaction.");
    }

    /**
     * Show how attributes can be extracted at runtime.
     * @throws NoSuchMethodException if cannot find declared method on class.
     */
    public void extractAttributes() throws NoSuchMethodException {

        //Get a reference to the java.lang.Class that we want
        //to examine for attributes.
        Class cls = TransactionExample.class;

        //Get the class level attributes.
        Attribute[] customClassAttribute = Attributes.getAttributes(cls);

        //Manipulate the attributes.
        //For the example, just cast to TransactionAttribute since we know 
        //it is there.
        TransactionAttribute classTransAttribute =
            (TransactionAttribute) customClassAttribute[0];

        System.out.println(
            "Found class level custom TransactionAttribute.  Value = "
                + classTransAttribute.getAsString());

        //Get a reference to the method we want to examine for
        //attributes.
        Class[] methodSignature = new Class[] { Vector.class };
        Method doDatabaseWorkMethod =
            cls.getDeclaredMethod("doDatabaseWork", methodSignature);

        //Get the method level attributes.
        Attribute[] customMethodAttributes =
            Attributes.getAttributes(doDatabaseWorkMethod);

        //Manipulate the attributes.
        //For the example, just cast to TransactionAttribute since we know
        //it is there.
        TransactionAttribute methodTransAttribute =
            (TransactionAttribute) customMethodAttributes[0];

        System.out.println(
            "Found method level custom TransactionAttribute.  Value = "
                + methodTransAttribute.getAsString());

    }

    /**
     * Run the example program.  It will execute a method that has been 
     * decorated with an attribute, just to show that the functionality has not 
     * been altered.  Then it will extract the attributes from the class. 
     * @param args The command line arguments, not used.
     */
    public static void main(String[] args) {
        //turn off debug verbositiy of Attrib4j.
        Log.setLevel(Log.WARNING);
        TransactionExample te = new TransactionExample();
        te.doDatabaseWork(new Vector());
        try {
            te.extractAttributes();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}			
	    

Notice how at the class level the Transaction attribute is specified only by typing 'Transaction'. This gets resolved to the fully qualified name (FQN), attrib4j.examples.attributes.TransactionAttribute' by specifying the attribute packages to prepend to the name after the '@' symbol during compile time. The suffix of the classname 'Attribute' is also appended to form the FQN.

Creating attributes at compile-time

Creating attributes relies on executing javac and then a post-processing step that runs javadoc. You can run the post-processing task using the Attrib4j custom Ant task. The custom task will soon evolve to be a drop-in replacement for Ant javac task, hiding a little the two step process, and hopefully making the use of attributes seem more natural. How to compile your code and run the post-processing task are shown below. These snippits were taken from the build.xml file that comes with Attrib4j examples. See src/examples/build.xml for the full file.

Compile your code

This standard Ant task compiles your code and then places your custom attributes in a seperate jar file. This separation is not necessary, but conveinent.

 

<!-- Build the example code -->
<target name="example-attributes" depends="prepare" >

  <!-- since the attrib4j.jar and bcel.jar are included in $ANT_HOME/lib, -->
  <!-- there is no need to specify any additional classpath -->

  <javac srcdir="${examples}" 
         destdir="${build}/examples" 
         debug="${debug}" 
         deprecation="${deprecation}">
  </javac>
    
  <!-- jar up just the attributes -->
  <jar jarfile="${build}/examples/example-attributes.jar"
       basedir="${build}/examples"
       includes="attrib4j/examples/attributes/*.class"/>
</target>

    

Generate the attributes using the Attrib4jCompiler Task

A custom task has been created to help you use Attrib4j. NOTE! To use the custom task you must place the attrib4j.jar and bcel.jar files in $ANT_HOME/lib. The Ant build file needs to import the definition of the custom task before it can be executed as shown below.

 
  <!-- include the custom task -->
  <taskdef name="attrib4jCompiler" classname="attrib4j.ant.Attrib4jTask"/>

  <target name="compile" depends="example-attributes">
    <attrib4jCompiler sourcepath="${examples}"
                      classpath="${build}/examples"
                      destdir="${build}/generated"
                      attributepackages="attrib4j.examples.attributes">
      <fileset dir="${examples}"/>
    </attrib4jCompiler>
  </target>
    

The XML attributes for the custom task are the following.

  • sourcepath - The location of the source .java files to parse for javadoc attribute comments.
  • classpath - The location of the corresponding .class files to modify with attribute information.
  • destdir - The location where the modified .class files will be placed. (Currently looking into overwriting in the same location as classpath)
  • attributepackages - Specifies what packages to prepend to classname listed after the javadoc '@' symbol. This allows you not to use the fully qualified name (FQN) when specifying the attribute class in your code

Run your program that uses attributes

Using the Ant java task, put in the classpath the directory containing the modified .class files. This is the directory specified by the destdir attribute of the Attrib4j custom task, ${build}/generated in the example build.xml file. Also, include in the classpath the base Attrib4j libraries and your custom attributes. Currently the implementation copies .class files to the destination directory even if they do not have any custom attributes.


  <!-- run the example -->
  <target name="runexample" depends="example-program">
    <java classname="attrib4j.examples.TransactionExample" fork="true">
      <classpath>
        <pathelement path="${build}/generated;
                           ${attrib4j.home.lib}/attrib4j-0.8.9.jar;
                           ${attrib4j.home.lib}/bcel-5.0.jar"/>
      </classpath>
	</java>
  </target>