What Are We Building?

Requirements, requirements. There are few things worse to me as a developer than having to guess what I’m supposed to build. I’m currently in a shop where we get high level requirements from an end customer, but the details are left to us.

–“Should this have a default, Bill?”
–“I dunno, what do you think, Jeff?”
–“I dunno, what do you wanna do, Marty?”

Ugh. It’s a huge comfort to have someone take the time to find out what we should really be building.

Greetings!

Welcome to my blog. While I’m going to concentrate more on the mini-articles located in the Articles section [[self-hosted entries since moved to the blog!]], I’ll use this as a source of very brief reflections on things as they occur to me. In fact, I’ll probably forget that I even have this blog thing for a while…

Annotations in J2SE 5.0—Part 2

Java version 5.0 introduces many dramatic new language features; this article provides additional information on the annotations facility. You may want to first read Annotations in J2SE 5.0—Part 1.

Further Modifications to JUnit

In the last installment, I demonstrated how an annotation type @Ignore could be used to mark methods so that JUnit would ignore them. While simply ignoring test methods is useful, it defeats my original purpose of ensuring that developers were cognizant of which methods have been ignored.

In order to make the ignored methods more visible, I’ve further modified JUnit to add a tab to the Swing UI that shows a list of the methods being ignored. Also, I’ve modified the @Ignore annotation type so that a developer can supply a reason for ignoring a test method. Here’s what the modified JUnit screen looks like:

(OK, so the icon needs work.) The modifications to the JUnit Swing user interface involved creating a new tab, Ignored Tests, that lists out the methods that have been marked as ignored.

Here is the test class code that generated the list of ignored methods in the above JUnit run:

public class NodeTest extends TestCase {
   @Ignore(reason="nothing done yet")
   public void testSomething() {
   }

   public void testError() {
      throw new RuntimeException("error!");
   }

   public void testFailure() {
      fail("failure!");
   }

   @Ignore(reason="just because")
   public void testDeadlock() throws Exception {
      // ...
   }
}

Member Value Pairs

Before, @Ignore took no parameters and thus was known as a marker annotation. Annotations can take parameters, as shown in the NodeTest code above. Note that the use of @Ignore as an annotation is a shortcut for the parameterless annotation @Ignore().

Now, @Ignore takes a member value pair as a parameter. A member value pair is a simple name, followed by an equals sign (=), followed by a value. The simple name in a member value pair must correspond to the name of a method in the annotation type declaration. In the annotation for testSomething above, the simple name is reason. The value is "nothing done yet".

In order to make @Ignore to support a reason annotation, I modified it to include a member named reason:

package junit.framework;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore {
   public String reason();
}

Modifications to JUnit

In the last installment, I modified JUnit to simply ignore test methods marked with @Ignore. The required modifications to JUnit were trivial.

Now that I need to track the ignored methods so they can be displayed in the Swing UI, the modifications are a bit more involved. I’m not going to detail all the changes here, only some of the core ones. If you’re truly interested in seeing all the changes, send me an email.

Here is the chief modification to the class junit.framework.TestCase. I didn’t like throwing an exception (TestIgnoredException, a new RuntimeException subclass) to trigger an ignored test, but it kept me from having to modify the Test interface.

   protected void runTest() throws Throwable {
      assertNotNull(fName);
      Method runMethod= null;
      try {
         // use getMethod to get all public inherited
         // methods. getDeclaredMethods returns all
         // methods of this class but excludes the
         // inherited ones.
         runMethod= getClass().getMethod(fName, null);
      } catch (NoSuchMethodException e) {
         fail("Method \""+fName+"\" not found");
      }
      if (!Modifier.isPublic(runMethod.getModifiers())) {
         fail("Method \""+fName+"\" should be public");
      }

      if (runMethod.isAnnotationPresent(Ignore.class)) {
         Ignore ignore = runMethod.getAnnotation(Ignore.class);
         throw new TestIgnoredException(ignore.reason());
      }

      try {
         runMethod.invoke(this, new Class[0]);
      }
      catch (InvocationTargetException e) {
         e.fillInStackTrace();
         throw e.getTargetException();
      }
      catch (IllegalAccessException e) {
         e.fillInStackTrace();
         throw e;
      }
   }

If an Ignore annotation is present, I retrieve the Ignore object using the method getAnnotation. I can then use the methods defined in the @Ignore annotation type declaration.

Other Key JUnit Changes

I modified the method runProtected in junit.framework.TestResult:

public void runProtected(final Test test, Protectable p) {
   try {
      p.protect();
   }
   catch (TestIgnoredException e) {
      addIgnoredTest(test, e.getMessage());
   }
   catch (AssertionFailedError e) {
      addFailure(test, e);
   }
   catch (ThreadDeath e) { // don't catch ThreadDeath by accident
      throw e;
   }
   catch (Throwable e) {
      addError(test, e);
   }
}

The TestListener class, used by the UI classes, had to be enhanced:

package junit.framework;

public interface TestListener {
   // ...
   public void addIgnoredTest(Test test, String reason);
}

In all honesty, my changes to JUnit seemed like a bit of a hack. I was lax to make dramatic, sweeping changes to the JUnit code (which are sorely needed). Not doing so made for an ugly set of changes.

Additional Member Value Pairs

You can define an annotation type so that it supports any number of member-value pairs in an annotation. In an annotation, you separate the member-value pairs with commas:

@Ignore(reason="bogus!", initials="jjl")
public void test1() {
}

Each simple name of a member value pair must correspond to a method on the annotation type declaration. In this example, the new simple name initials corresponds to the @Ignore method initials(), defined here:

package junit.framework;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore {
   public String reason();
   public String initials();
}

Annotation Type Member Return Types and Defaults

An annotation type member cannot take any parameters. Its return type must be one of String, any primitive type, an enum type, Class, an annotation type itself, or an array of any of the preceding types.

An annotation type member can have a default value. The existence of a default for an annotation member means that the user is not required to supply a member value pair for the corresponding simple type.

I’ve modified the @Ignore annotation type here to demonstrate the default capability as well as some of the various return types.

package junit.framework;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore {
   public String reason();
   public String initials();
   public String[] relatedTestMethods();
   public boolean isForPerformance() default false;
   public Class<!--?-->[] relatedTestClasses();
}

Here’s a sample @Ignore annotation using the above annotation type declaration.

@Ignore(reason="bogus!",
   initials="jjl",
   relatedTestMethods={"test2", "test3"},
   relatedTestClasses=Test.class)
public void test1() {
}

As demonstrated, member value pairs can be in any order since they are keyworded.

Also, note that the member relatedTestClasses is defined as returning a Class array. Yet the annotation supplies only the single value Test.class, without any array braces. This is a shortcut: braces are not required for single-element array values.

Single Member Annotation Types

If you supply a single member named value for an annotation type, users of the annotation type need not provide a simple name:

public @interface Ignore {
   String value();
}

This example allows for annotations such as @Ignore("bogus!"). You can also have the value member return a String array. The annotation type declaration:

public @interface Ignore {
   String[] value();
}

allows:

@Ignore({"bogus!", "baby"})

Complex Annotation Types

You can create complex annotation types by having an annotation type member return an annotation type itself:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore {
   String reason();
   Date date();
}

Date is defined as:

import java.lang.annotation.*;

public @interface Date {
   int month();
   int day();
   int year();
}

Note the absence of @Retention and @Target meta-annotations on Date; they are not necessary.

Here is an example use of the now-complex @Ignore annotation:

@Ignore(reason="bogus!", date=@Date(month=11, day=5, year=2004))
public void test1() {
}

Final Notes on the Annotations Facility

Here are some odds and ends not covered in these two articles:

  • You can only define one target for an annotation; you cannot, for example, declare that @Ignore modifies both methods and first-level types.
  • The @Documented meta-annotation type lets you declare an annotation type to be included in the published API generated by tools such as javadoc.
  • The @Inherited meta-annotation type means that an annotation type is inherited by all subclasses. It will be returned if you send getAnnotation to a method or class object, but not if you send getDeclaredAnnotations.
  • The Method and Constructor classes have been supplemented with a getParameterAnnotations method.
  • In order to internally support annotation types, Sun modified the Arrays class to include implementations of toString and hashCode for arrays.
  • When adding new members to an annotation type, you should provide a default to avoid breaking existing code. Refer to the specification document for information on compatibility issues surrounding annotations.
  • You cannot use null as an annotation value.

Summary

Annotations are a powerful facility that can help structure the notes you put into your code. One of the examples that is touted the most is the ability to annotate interface declarations so that tools can generate code from the pertinent methods.

The downside is that you make your code dependent upon an annotation type when your code uses it. Changes to the annotation type declaration could negatively impact your code, although Sun has built in some facilities to help maintain binary compatibility (see the specification document). You also must have the annotation type class file present in order to compile. This should not be surprising; an annotation type is effectively an interface type. There is an interesting discussion in the specification document about this dependency and some of the discarded ideas for minimizing it.

An exercise left to the reader is to modify the JUnit implementation to use annotation types such as @TestClass and @TestMethod for recognizing test classes and methods. This is how NUnit currently works. There are two significant benefits: JUnit test classes would no longer have to follow the reflections-based conventions for naming test methods, and your test classes would no longer have to inherit from a concrete superclass (junit.framework.TestCase).

It’s about time for a JUnit rewrite, anyway. I hope to see a version 4.0 of JUnit with the introduction of J2SE 5.0. [[ JUnit 4.0 is recently available. JjL, 8/25/2005 ]]

Reference: Java Community Process public review document, “JSR-000175: A Metadata Facility for the JavaTM Programming Language,” located at http://jcp.org/aboutJava/communityprocess/review/jsr175/index.html

 

Polymorphic Enums in J2SE 5.0

Java version 5.0 introduces many dramatic new language features. This article further discusses the new enum facility, demonstrating how you can define enum constants polymorphically. You may want to first read the article Typesafe Enum: Using enum in J2SE 5.0.

The Grade enum

In my first article on enum, I introduced a Grade enum. Here is the code:

enum Grade {
   A("great"),
   B("good"),
   C("ok"),
   D("eh"),
   F("loser");

   private String message;

   Grade(String message) {
      this.message = message;
   }

   String getMessage() {
      return message;
   }
}

Suppose you want the ability to determine whether a test score falls into a specific grade. A score of 90 up through 100 is an A; an 80 up to but not including 90 is a B, and so on. A score of less than (but not including) 60 warrants an F.

You can modify the Grade enum to take a lower and upper range for each grade. You can then provide a method (includes) to determine whether or not a score maps to the current Grade enum:

public enum Grade {
   A("great", 90.0, 100.0),
   B("good", 80.0, 90.0),
   C("ok", 70.0, 80.0),
   D("eh", 60.0, 70.0),
   F("loser", 0.0, 60.0);

   private String message;
   private double from;
   private double to;

   Grade(String message, double from, double to) {
      this.message = message;
      this.from = from;
      this.to = to;
   }

   String getMessage() {
      return message;
   }

   boolean includes(double score) {
      return from <= score && score < to;
   }
}

So far, nothing new. The only problem is that it won’t work. The to value represents the top end of the grade range and must be exclusive–except for an A. A perfect score of 100 must report back as an A, but it does not with the above code.

One solution would be to include special logic in the definition of enum, or perhaps modify the constructor to take a boolean representing whether or not the range upper bound is inclusive.

Another solution is to have the declaration of the Grade.A enum constant override the definition for includes.

public enum Grade {
   A("great", 90.0, 100.0) {
      boolean includes(double score) {
         return super.includes(score) || score == to;
      }
   },
   B("good", 80.0, 90.0),
   C("ok", 70.0, 80.0),
   D("eh", 60.0, 70.0),
   F("loser", 0.0, 60.0);

   private String message;
   private double from;
   protected double to;

   Grade(String message, double from, double to) {
      this.message = message;
      this.from = from;
      this.to = to;
   }

   String getMessage() {
      return message;
   }

   boolean includes(double score) {
      return from <= score && score < to;
   }
}

You supply the new definition for includes inline, just as if you were overriding a method in an inner class declaration.

Note that you must also define the to variable as protected in order for this to work.

If you want a different implementation for an enum method for each and every enum constant, you can declare both the method and the enum type as abstract.

Annotations in J2SE 5.0—Part 1

Java version 5.0 introduces many dramatic new language features. This article introduces the new annotations facility.

Annotations and Annotation Types

An annotation is a tag you can insert into your source code. It does not alter the semantics of your code, but instead adds the ability for other application code to recognize and interpret the tag. An example of an annotation you are familiar with is the @deprecated tag1.

You mark methods with @deprecated to indicate that they should no longer be used. An annotation is just a note. Marking a method with @deprecated does not change how that method works. However, the compiler notes the methods marked with @deprecation and issues a warning when any client code calls these methods.

J2SE 5.0 allows you to add custom annotations to your code. As an example, you might want to be able to mark bits of code as @Todo, meaning that work remains to be done on that code. To support using an @Todo tag, you code an annotation type definition in Todo.java (you’ll see what this looks like shortly).

Once you’ve compiled the annotation type, you can use it in your source code. Just like any Java type, the annotation type must be available on the classpath. You can then build a tool that uses reflection code to extract the annotation information from the code.

The @Override Annotation Type

As one example of practical use, Sun included a new standard annotation type, @Override, in J2SE 5.0. This annotation type, defined in java.lang, allows you to declare that you think a method overrides, and does not overload, a method from the superclass.

public @Override boolean equals(Employee that)

In this example, the developer likely intended to override the method:

public boolean equals(Object o)

The compiler will fail if there is no method with an equivalent signature in the superclass:

method does not override a method from its superclass
public @Override boolean equals(String o) {
^

Example: Ignoring Test Methods

I use JUnit heavily for building unit tests for my code. JUnit depends upon test methods being named according to a convention: all test methods should begin with the name “test”, should return no value, and should have no parameters. JUnit uses Java’s reflection capabilities to find and execute these test methods.

One of the things I often do when coding with JUnit is to temporarily comment out one or more test methods while I work on another. (One technique I use is to rename the method xxxtestMethod instead of testMethod.) Perhaps the commented-out method is causing additional problems. In any case, I always intend to go back to the method and uncomment it, but I sometimes forget where the method was, or even that I commented out any methods.

It would be nice if I could annotate methods with a tag that tells JUnit to not run them. (NUnit, the corresponding test tool for .Net, provides this capability.) It would be even nicer if JUnit were able to list the methods that were marked as ignored. As a brief example, I will step you through the first part of the changes needed to accomplish these goals.

Step 1—Readying JUnit

Note: The example here uses JUnit 3.8.1.

The JUnit distribution includes the source for JUnit. Unzip the source (src.jar) into the same directory in which you installed JUnit (make sure the option to “Use folder names” is checked).

Step 2—Writing the Test

Next, you need to create a new test class in the junit.tests.framework package. This test class will demonstrate use of the @Ignore tag.

package junit.tests.framework;

/**
* Test class used in SuiteTest
*/
import junit.framework.TestCase;
import junit.framework.Ignore;

public class IgnoreMethodTestCase extends TestCase {
   @Ignore
   public void testCase() {
   }

   public void testNotIgnored() {
   }
}

The test class IgnoreMethodTestCase contains two test methods. One is marked with the @Ignore annotation; the other is not. Now that you have created this test class, you can add a new test method to JUnit. This new test method will be used to demonstrate that the @Ignore annotation works as expected.

Here is the new test method; add it to junit.tests.framework.SuiteTest:

public void testIgnoreMethodTestCase() {
   TestSuite suite= new TestSuite();
   suite.addTestSuite(IgnoreMethodTestCase.class);
   suite.run(fResult);
   assertEquals(1, fResult.runCount());
   assertTrue(fResult.wasSuccessful());
}

This new test looks like most of the other existing tests in SuiteTest. It first constructs a TestSuite, then adds your IgnoreMethodTestCase class to the suite. After running the suite, the test expects a successful run with a runCount of 1 instead of 2. In other words, the JUnit code should find only one test method in IgnoreMethodTestCase, since the other test method has been marked with the @Ignore tag.

Execute the JUnit Ant script to demonstrate that the the new code does not compile–you haven’t yet created the @Ignore annotation type.

Step 3—Defining the Annotation Type

Here is the code for the custom annotation type @Ignore. Save it as Ignore.java:

package junit.framework;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore {}

The annotation type declaration is preceded with two meta-annotation tags: @Retention and @Target. The “meta” means that these annotations are tags that apply to annotation declarations: annotations for annotations. (The meta-annotation types @Retention and @Target are defined in java.lang.annotation, hence the import statement.) Note that both of the meta-annotations take parameters. You’ll see how to define parameters for @Ignore in the next installment of this article.

The @Retention tag indicates that the @Ignore tag will be available at runtime. This means you’ll be able to use reflection capabilities to determine if the @Ignore tag has been used.

The RetentionPolicy enum defines two values in addition to RetentionPolicy.RUNTIME. RetentionPolicy.CLASS indicates that the compiler stores the annotation but does not make it available at runtime (this is how Sun declares the @Override annotation). If no @Retention tag is supplied, Java uses RetentionPolicy.CLASS as the default. A third option, RetentionPolicy.SOURCE, means that the Java compiler discards the annotation at compile time; the annotation is only available in the source. An IDE might find such tags useful.

The @Target tag indicates where the annotation can appear. The enum ElementType defines the possibilities. In the @Ignore definition above, ElementType.METHOD means that @Ignore can precede a method declaration. Here is the complete list of enum values:

ElementType enum What it can modify
TYPE class, enum, interface
FIELD field, enum constant
METHOD method
PARAMETER parameter
CONSTRUCTOR constructor
LOCAL_VARIABLE temporary variable
ANNOTATION_TYPE annotation type
PACKAGE package

The final line in Ignore.java is the annotation type declaration, repeated here:

public @interface Ignore {}

An annotation type declaration looks just like an interface type declaration. The only distinction is that you precede the name of an annotation type with @ (plus optional whitespace, which is not recommended).

Now execute the JUnit Ant script again. The code should compile but will fail the tests. It fails because there are two test methods in IgnoreMethodTestCase. You haven’t yet coded the logic for @Ignore, so JUnit doesn’t know to skip one of the IgnoreMethodTestCase methods.

Step 4— Modifying JUnit

Getting JUnit to skip tests marked with @Ignore will require a few small modifications to the class junit.framework.TestSuite:

private void addTestMethod(Method m, Vector names, Class theClass) {
   String name= m.getName();
   if (names.contains(name))
      return;

   if (!isTestMethod(m))
      return;
   if (isIgnore(m))
      return;
   if (!isPublic(m)) {
      addTest(warning("Test method isn't public: "+m.getName()));
      return;
   }

   names.addElement(name);
   addTest(createTest(theClass, name));
}

The TestSuite method addTestMethod now calls a new method named isIgnore. The code for isIgnore is where you determine whether or not an @Ignore annotation has been applied to the Method object:

private boolean isIgnore(Method m) {
   return m.isAnnotationPresent(Ignore.class);
}

All elements that can be annotated–classes, methods, etc.– support the ability for you to extract annotation information by using the appropriate java.lang.reflect class. To determine whether or not an annotation has been applied to a method, you call isAnnotationPresent on the Method object and pass it the class object representing the annotation type.

Sun has updated the java.lang.reflect classes in J2SE 5.0 to implement AnnotatedElement. AnnotatedElement is a new interface that declares isAnnotationPresent as well as three other methods: getAnnotation(Class), getAnnotations, and getDeclaredAnnotations.

The original conditional logic in addTestMethod is a little confusing, so a bit of restructuring is prudent. You’ll need to make an additional minor modification to the existing JUnit code. Change isPublicTestMethod to simply isPublic, and modify the method contents appropriately:

private boolean isPublic(Method m) {
   return Modifier.isPublic(m.getModifiers());
}
Step 5— Rebuilding JUnit

You’re almost done. The last step is to rebuild JUnit.jar with the changes.

Executing Ant from within the JUnit install directory will kick off the default target, dist. The dist target will rebuild the entire JUnit distribution, including JUnit.jar and place it in the subdirectory (of the JUnit installation directory) junit3.8.1.

You can now mark your JUnit test methods as @Ignore. Refer to the test class IgnoreMethodTestCase (above) as an example of how to use the @Ignore tag. (That’s part of what having unit tests is for!)

As mentioned, the second part of this exercise would be to modify JUnit to report on the ignored tests. I’ll leave that exercise up to you.

In the next installment, I’ll go into a little more detail on annotations. Additionally, you’ll modify the above example to allow @Ignore to take parameters.

Reference: Java Community Process public review document, “JSR-000175: A Metadata Facility for the JavaTM Programming Language,” located at http://jcp.org/aboutJava/communityprocess/review/jsr175/index.html

1 @deprecated is supported in a different manner by Java than the new annotations scheme, but it’s still semantically an annotation.

Atom