The annotation processing APIs provided by apt in JDK 5 and javac in JDK 6 both present a read-only view of source code; by design directly modifying the input sources is not supported through either annotation processing API. Recently, Project Lombok has used the hook of being able to run an annotation processor inside javac to start an agent which rewrites javac internal classes to change the compiler's behavior; yikes! Such extreme measures are not needed to get much of the effect of modifying the input sources. As I've outlined in the annotation processing forum over the years, just using standard annotation processing to generate a subclass or superclass of a type being processed is a very powerful technique for controlling the ultimate semantics and behavior of the original class. For example, I've hacked up a proof-of-concept annotation type and matching processor to generate property-style getter and setter methods based on annotations on fields.

Concretely, the programmer writes something like
public class TestProperty extends TestPropertyParent { protected TestProperty() {} @ProofOfConceptProperty protected int property1; @ProofOfConceptProperty(readOnly = false) // Generate a setter too. protected long property2; @ProofOfConceptProperty protected double property3; public static TestProperty newInstance(int property1, long property2, double property3) { return new TestPropertyChild(property1, property2, property3); } }
and, after suitable annotation processing, using the TestProperty type as in
public class Main { public static void main(String... args) { TestProperty testProperty = TestProperty.newInstance(1, 2, 3); output(testProperty); testProperty.setproperty2(42); output(testProperty); } private static void output(TestProperty testProperty) { System.out.format("%d, %d, %g%n", testProperty.getproperty1(), testProperty.getproperty2(), testProperty.getproperty3()); } }
produces the expected output:
prompt$ java Main 1, 2, 3.00000 1, 42, 3.00000
This approach does have limitations; primarily the annotated class like TestProperty needs to be written to allow its superclass and subclass(es) to be generated. Since it runs as part of the build, the annotation processor needs to be built separately beforehand. The javac command to run the annotation processor looks like:
javac -s ../gen_src/ -d ../bin -processor foo.PocProcessor -cp ../lib TestProperty.java Main.java
Good practice sets an output location for generated source code, TestPropertyParent and TestPropertyChild in this case, separate from the output location for class files. Java IDEs have varying levels of support for annotation processing; check your IDEs' documentation for details.

The annotation type and processor is only a proof of concept; many possible refinements are left as "exercises for the reader" including:

  • Developing a second annotation type to mark a class separate from the annotation to configure how each field should be treated.
  • Additional structural checks on the annotated code, proper modifiers on fields and constructors, etc.

    Generation of equals and hashCode methods.

While using an annotation processor to approximate properties is awkward compared to built-in language support, annotation processors can be used today as part of some toolchains and they are configurable by the user. The code provided should be enough of a starting part for others to experiment with using annotation processors in this fashion; have fun.



More...