January 16, 2021
Hot Topics:

Make Your Code Work for You with Java Annotations

  • By David Thurmond
  • Send Email »
  • More Articles »

HelpGenerator.java is an example of a doclet. Doclets are used by the Javadoc tool to control the output that is generated when Javadocs are generated from source code. In this case, rather than generating HTML API documentation, this doclet will generate a file called help_<class-name>.xml that contains all of the help topics defined in the source code with the @Help annotation.

The first method declared in the HelpGenerator class is:

public static boolean start(RootDoc root)

which is the standard entry point for all doclets. The Javadoc tool invokes this method to begin any processing that is to be done using a custom doclet. This method accepts a RootDoc as a parameter, which then allows the doclet to gain access to all of the source code files to be processed.

The doclet then grabs a list of all of the ClassDoc objects to be processed. The i for-loop creates a new help file corresponding to each ClassDoc object processed. A ClassDoc is the doclet equivalent of a Java class, and contains all of the documentation elements for the Java source code for the class. Next, an array of MethodDoc objects is obtained from the ClassDoc, and the j for-loop cycles through each one. Within the j for-loop, all of the annotations found in each MethodDoc are retrieved. The AnnotationDesc object corresponds to an occurrence of an annotation.

Next, in the k for-loop, each annotation is echoed to System.out, and the annotation's parameter name-value pairs are obtained. This is the meat of the help topic XML tags. For each name-value pair, the l for-loop cycles through and determines whether the parameter is the context ID, help topic, or text, and stores it in the correct variable. Then, the help topic XML tag is created and written to the output file.

The l for-loop obtains each annotation's parameters and prints them out as well. Within this loop, if the annotation is the @Help annotation, each parameter value is placed into a variable for outputting to the help XML tag to be generated.

To run the program, you must invoke the Javadoc tool as follows:

   -doclet com.dlt.developer.helpgenerator.HelpGenerator
   -docletpath classes
   -sourcepath src com.dlt.developer.numbers

The Javadoc tool accepts several parameters. The first is the –doclet parameter that tells Javadoc to use the HelpGenerator custom doclet. The next is the –docletpath parameter that tells Javadoc where to find the doclet class. Next is the –sourcepath parameter, which tells Javadoc where to find the source code to be processed. Finally, the package name for which to execute Javadoc is specified. In this case, it refers to com.dlt.developer.numbers, which contains NumberCruncher.java, a source code file with several @Help annotations included. The output from running this program can be found in the help_NumberCruncher.xml file that is included in the examples file in the "Download the Code" section.

It is easy to see how a custom doclet could be created to do any number of code generation tasks. The doclet API includes documentation equivalents for all of the Java language constructs, making it easy to access annotations that go along with each one.

Using Annotations at Run Time

Now that I have covered coding-time and compile-time annotations, I will cover perhaps the most exciting application of annotations: run-time development tools! Using the @ExecTime and @MemoryCheck annotations discussed earlier, I will demonstrate how annotations and reflection can be used to create a run-time development tool that profiles execution time and memory usage for the methods on a Java class in Listing 7.

Listing 7: Profiler.java

package com.dlt.developer.profiler;

import java.lang.reflect.Method;
import com.dlt.developer.annotation.MemoryCheck;
import com.dlt.developer.annotation.ExecTime;

public class Profiler {
   public final static String PROFILE_MEMORY = "-mem";
   public final static String PROFILE_TIME   = "-time";
   public final static String PROFILE_ALL    = "-all";

   private Object theInstance = null;
   private String theProfile  = PROFILE_ALL;

   public Profiler(String className, String profile) {
      try {
         theInstance = Class.forName(className).newInstance();
      } catch (Exception ex) {
         System.out.println("Cannot instantiate class due to
                             exception " + ex);
      }    // catch
      if (!(profile.equals(PROFILE_MEMORY) ||
            profile.equals(PROFILE_TIME) ||
            profile.equals(PROFILE_ALL))) {
      }    // if
   }       // Profiler()

   public static void doUsage() {
      System.out.println("$java Profiler MyClass <option>");
      System.out.println("where <option> must be one of:");
      System.out.println("-mem = Show memory usage for all
      System.out.println("-time = Show execution time for all
      System.out.println("-all = Show all profile information
                          for all methods");
   }    // doUsage()

   public void doProfile() throws Exception {
      Method [] methods =
      for(int i = 0; i < methods.length; i++) {
         Method theMethod = methods[i];
         MemoryCheck theMemCheckAnnotation =
         ExecTime theExecTimeAnnotation =

         long freeMemoryBefore = 0;
         long freeMemoryAfter  = 0;

         long timeBefore = 0;
         long timeAfter  = 0;

         if (theMemCheckAnnotation != null &&
            (theProfile.equals(PROFILE_MEMORY) ||
            theProfile.equals(PROFILE_ALL))) {
               freeMemoryBefore = Runtime.getRuntime().freeMemory();
         }    // if

         if (theExecTimeAnnotation != null &&
            (theProfile.equals(PROFILE_TIME) ||
            theProfile.equals(PROFILE_ALL))) {
               timeBefore = System.currentTimeMillis();
         }    // if

         if (theMemCheckAnnotation != null ||
            theExecTimeAnnotation != null) {
               System.out.println("Profiling " +
               theMethod.invoke(theInstance, null);
         }    // if

         if (theMemCheckAnnotation != null &&
            (theProfile.equals(PROFILE_MEMORY) ||
            theProfile.equals(PROFILE_ALL))) {
            freeMemoryAfter = Runtime.getRuntime().freeMemory();
            double memoryUsed = freeMemoryBefore - freeMemoryAfter;
            if (theMemCheckAnnotation.size().equals(MemoryCheck.KB)) {
               memoryUsed = memoryUsed / 1024;
            } else if (theMemCheckAnnotation.size().
               equals(MemoryCheck.MB)) {
               memoryUsed = memoryUsed / 1024000;
            }    // if
            System.out.println("Profiler: The memory required to
               execute " + theMethod.getName() + " for class " +
               theInstance.getClass().getName() + " was " +
               memoryUsed + theMemCheckAnnotation.size());
         }    // if

         if (theExecTimeAnnotation != null &&
            (theProfile.equals(PROFILE_TIME) ||
            theProfile.equals(PROFILE_ALL))) {
            timeAfter = System.currentTimeMillis();
            System.out.println("Profiler: The time required to
               execute " + theMethod.getName() + " for class " +
               theInstance.getClass().getName() + " was " +
               (timeAfter - timeBefore) + " milliseconds");
         }    // if
      }       // for i
   }          // doProfile()

   public static void main(String[] args) throws Exception {

      if (args.length != 2) {
      }    // if

      System.out.println("Beginning profiling of class " + args[0]);
      Profiler theProfiler = new Profiler(args[0], args[1]);
   }    // main()

}       // class Profiler

Page 3 of 4

This article was originally published on December 15, 2008

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date