Metadata for language extensions
March 08, 2005

My two-part article series "AOP and metadata: A perfect match" [Update: Link added, since the article is now published], a part of the AOP@Work series, is about to go live on developerWorks. The first part of the series includes a small section on using metadata to extend the underlying language. However, this topic needs a more elaborate discussion, and hence this blog.

The new metadata facility in Java 5.0 can be used to extend the Java language. In this usage model, an annotation processor alters the semantics of program elements marked with special annotations. Such usage of metadata brings interesting possibilities and a few concerns.

The idea of using metadata to extend the language is not new. AOP systems such as AspectWerkz, JBoss AOP, and now the metadata-driven syntax in AspectJ5 (informally called @AspectJ syntax) use this idea to provide aspect-oriented extensions to Java, in which ordinary program elements are reinterpreted as AOP constructs. For example, you can mark a class to function like an aspect, a method as a pointcut or advice, and so on.

Extension classification

We can classify language extensions using metadata into three categories based on how they affect the programs: compile-time extensions, structural modifications, and behavioral modifications.

Compile-time extensions

These extensions make the compiler perform additional tasks but do not affect the compiled code. Java 5.0 already includes examples of this with @Override, @Deprecated and @SuppressWarnings annotations (albeit as a standard feature, rather than an extension).

Generalizing this idea, you can imagine extending the compiler to issue custom errors and warnings upon detecting certain usage patterns. For example, we can use a Const annotation type to implement the functionality of the C++ "const" keyword in Java. Consider the following annotation type:

public @interface Const {
}

Now consider the following class that uses this annotation:

public class ShoppingCart {
    ...

    public void addItem(@Const Item item) {
        // can't directly modify item or call methods that could modify item
    }

    public void removeItem(@Const Item item) {
        // can't directly modify item or call methods that could modify item
    }

    @Const
    public float getTotal() {
        // can't directly modify 'this' or call methods that could modify 'this'
        return 0;
    }
}

The annotation processor would consume the @Const annotations to produce compile-time errors for any attempts to modify an item while adding or removing from a shopping cart or any attempts to modify the state of shopping cart while querying for the total.

Okay, the language extension using @Const is only a theoretical possibility, since I don't think it will be of much practical value unless the core Java classes and existing libraries adopt this annotation type and its semantics. Nevertheless, it is an interesting theoretical possibility.

Structural modifications

These extensions modify the structure of the program without modifying the behavior. For example, we could use a @Property annotation to mark fields of a class as properties. An annotation processor then processes the annotations to add a getter method and/or a setter method for each property based on whether it is a read-write, read-only, or write-only property. Essentially, with this extension, you get the property feature support similar to the one in Groovy and C#.

Consider the following annotation type and the associated enumeration type:

public @interface Property {
    public PropertyKind value() default PropertyKind.ReadWrite;
}
public enum PropertyKind {
    ReadWrite, ReadOnly, WriteOnly;
}

Now consider the following class:

public class Person {
    @Property(ReadOnly) private long id;
    @Property private String name;
}

The annotation processor would consume the @Property to translate this class into a byte-code equivalent of the following snippet:

public class Person {
    @Property(ReadOnly) private long id;
    @Property private String name;

    public long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

If a getter or setter already existed for a property, the annotation processor would leave it untouched.

We can use the same idea to support certain structural design patterns directly into the extended language. For example, we can implement the singleton pattern by allowing classes to be marked with an annotation. Consider the following annotation and enumeration types:

public @interface Singleton {
    public SingletonKind value() default SingletonKind.Lazy;
}
public enum SingletonKind {
    Lazy,   // create the singleton instance upon first use
    Eager;  // create the singleton instance as soon as possible
}

Now consider a class marked with a @Singleton annotation:

@Singleton(Lazy)
public class Service {
    public void serve() {
        //...
    }
}

The annotation processor will translate this class into a byte-code equivalent of the following snippet:

@Singleton(Lazy)
public class Service {
    private static Service _instance;

    public static Service instance() {
        if(_instance == null) {
            _instance = new Service();
        }
        return _instance;
    }

    private Service() {
    }

    public void serve() {
        ...
    }
}

The annotation processor could also modify compile-time behavior to flag the presence of unsupportable structures such as non-zero arguments constructors.

Behavior modifications

These extensions alter the program behavior. For example, they may add additional code to implement security feature in all the program elements with a @Secure annotation. EJB 3.0 annotations essentially extend the Java language in the same vein. Another good example of this kind of extension is ContractJ that implements DBC in Java.

While implementing behavior modifications through language extension can be useful in some situations, aspect-oriented programming is a much better way to do the job. When implemented using AOP, the behavior logic doesn't disappear into code in an annotation processor, the code expressed remains much more readable, and the debugging process remains natural.

Extension implementation

While some of the features described here can be implemented through a pre-processor (generating code) or post processor (modifying byte-code), a more convenient way would be to allow plugins to the compiler. It would not be surprising if a future tool's JSR proposes a standard way to extend the compiler (perhaps called "complet", in the same spirit as "doclet" to use with Javadoc). Perhaps the Pluggable Annotation Processing API (JSR 269) might be the one to standardize such a facility.

Impact on Java

This possibility allows incorporating new features in the language without having to wait for them to make it into the standard. However, abusing this possibility can wreak havoc on the comprehensibility of the programs. Using this possibility to create principled extensions will make Java a more expressive language. AOP systems using annotations to add aspect-oriented features to Java is a good example of principled extensions (in that there is a system behind it -- aspect-oriented programming). Using this possibility to add ad hoc extensions, on the other hand, will make programs hard to follow without the context of the associated annotation processing.

How the community uses the metadata feature in general, and the language-extension use case in particular, will be interesting to watch.