# Wednesday, 09 May 2007
« New Snapshot | Main | Merging the First OpenJDK Code and Anoth... »
Custom Attribute Annotations

Custom attributes are a powerful capability of the .NET Framework and unlike Java annotations, there are quite a few custom attributes that are understood directly by the CLR runtime engine, for example things like P/Invoke, declarative Code Access Security and declaring thread or context local static variables can all be done by applying custom attributes. In addition to this there are also lots of reflection based applications of custom attributes because they've been part of the platform since day one.

Because of the importance of custom attributes, it makes sense to try to support them in ikvm. It has been possible for quite a while to apply custom attributes to Java classes, methods and fields by specifying them in the map.xml file and previously I used this when compiling IKVM.GNU.Classpath.dll in a few places (e.g. for declaring a thread local static variable), but given the similarity between custom attributes and Java annotations (which, like many other Java 5 features, were obviously "inspired" by .NET) it only makes sense to try to map custom attributes to annotations whenever possible.

I started working on this a while ago and recently checked in major improvements to this. The feature is now started to take shape nicely.

Limitations

Although conceptually very similar, there are significant differences between .NET custom attributes and Java annotations. The most important one is that custom attributes are classes while annotations are interfaces, as a direct result of that custom attributes use a constructor invocation syntax (plus the ability to set fields and properties), while annotations just name the type and list a number of key/value pairs (very similar to custom attribute properties). This means that in .NET, a custom attribute can specify which parameters are required and which are optional, by making the required parameters constructor arguments and the optional ones properties (or fields) and additionally, because of constructor overloading multiple combinations are possible. Annotation can specify for each individual property if it is required or optional, but there is no mechanism similar to constructor overloading.

This leads to the first limitation: Currently only custom attributes that have a default constructor or a single one-argument constructor (taking a supported type) are supported.

The second limitation is that not all parameter types are supported, only the Java primitive types (where Java's byte maps to System.Byte), type literals, strings and enumerations are supported (and single dimensional arrays of these types). Custom attribute parameters typed as Object are not supported.

Nested Annotation Type

When viewed from Java, every supported custom attribute class will appear to have a nested annotation type named Annotation. Here's an example of applying a simple custom attribute annotation to a field:

public class Foo
{
  @cli.System.ThreadStaticAttribute.Annotation
  private static int threadLocalCounter;
}

I toyed with the idea of naming the annotation by chopping of the Attribute part, to make it consistent with common C# usage, but I ultimately I decided the nested type approach was better. It's a bit more verbose, but it's consistent with how delegates are handled and it greatly reduces the chance of name clashes.

Single Argument Constructor

If a custom attribute has a single argument constructor, that will be translated into an annotation property named value. This allows the simplified syntax to be used:

@cli.System.CLSCompliantAttribute.Annotation(true)
public class Foo { ... }

If the custom attribute also has a default constructor, the value property will be optional.

Enums

Since .NET enums are value types and behave like integral types most of the time, they are not mapped to Java enums, but for use in annotations they have to be mapped to Java enums, so every .NET enum type has a nested type __Enum that is a Java enum. This allows enum values to be used in annotations:

import cli.System.*;

@AttributeUsageAttribute.Annotation({
  AttributeTargets.__Enum.Class,
  AttributeTargets.__Enum.Interface
})
public class MyCustomAttribute extends Attribute
{ ... }

The above example also shows that an enum that has a FlagsAttribute (as AttributeTargets does) will be translated into an array of that enum, to allow combining the flags. The exception to this is that if the parameter type is already an array, it will not be turned into a two dimensional array, because annotations only support one dimensional arrays.

ValidOn & AllowMultiple

Custom attribute annotations are annotated with the Target and Retention annotations. The target element types are based on the ValidOn property of the AttributeUsageAttribute of the custom attribute and Retention always has a RetentionPolicy.RUNTIME.

The ValidOn property of AttributeUsageAttribute is of type AttributeTargets. Here's a table that shows how the values map to the Java ElementType enum:

System.AttributeTargets java.lang.annotation.ElementType
Assembly, Class, Delegate, Enum, Interface, Struct     TYPE
Constructor CONSTRUCTOR
Method METHOD
Field FIELD
Parameter PARAMETER

Unlike custom attributes, annotations cannot be applied to a method's return value. The workaround for that is to nest them inside a special __ReturnValue annotation that is applied to the method:

public class Foo
{
  @cli.System.CLSCompliantAttribute.Annotation.__ReturnValue(
     @cli.System.CLSCompliantAttribute.Annotation(true)
  )
  public int foo() { ... }
}

Another custom attribute feature not supported by annotations is the ability to specify multiple instances of a custom attributes (controlled by the AllowMultiple property of AttributeUsageAttribute). This is handled in a similar way:

import cli.System.Security.Permissions.*;

@UIPermissionAttribute.Annotation.__Multiple({
  @UIPermissionAttribute.Annotation(value = SecurityAction.__Enum.Demand,
    Window = UIPermissionWindow.__Enum.SafeTopLevelWindows),
  @UIPermissionAttribute.Annotation(value = SecurityAction.__Enum.LinkDemand,
    Clipboard = UIPermissionClipboard.__Enum.AllClipboard)
})
public class Foo { ... }

Note that __Multiple is never nested inside a __ReturnValue, when a custom attribute has AllowMultiple and can be applied to return values, the __ReturnValue annotation simply has an array value property.

Type Literals

Type literals can be specified as class literals:

import cli.System.Runtime.CompilerServices.*;

@TypeForwardedToAttribute(Foo.class)
interface assembly {}

Note that the assembly type in the default package is treated specially by ikvm and acts as a placeholder for custom attributes that should be applied to the assembly.

Default Values

This table lists the default values for each supported type:

Type Default Value
boolean      false
byte (byte)0
char (char)0
short (short)0
int 0
float 0.0F
long 0L
double 0.0D
String ""
Class ikvm.internal.__unspecified.class
Enum __unspecified
array { }

The __unspecified hacks are necessary because annotation property values cannot be null. Every __Enum has an __unspecified field specifically for this purpose. Note that these defaults do not necessarily correspond to the real default values used by the custom attribute. These are simply the values that are reported by Method.getDefaultValue() and put into the stubs generated by ikvmstub. If you don't specify a value for an optional property, the resulting custom attribute won't have the property set and hence it will have the default value as defined by the implementation of the custom attribute.

Error Handling

Not yet implemented, but the idea is to ignore invalid annotations (most likely due to version conflicts between the stub jar that the Java code was compiled against and the assembly used at runtime). In the case of ikvmc, a warning message should be emitted as well.

Wednesday, 09 May 2007 08:15:26 (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]
Name
E-mail
Home page

I apologize for the lameness of this, but the comment spam was driving me nuts. In order to be able to post a comment, you need to answer a simple question. Hopefully this question is easy enough not to annoy serious commenters, but hard enough to keep the spammers away.

Anti-Spam Question: What method on java.lang.System returns an object's original hashcode (i.e. the one that would be returned by java.lang.Object.hashCode() if it wasn't overridden)? (case is significant)

Answer:  
Comment (HTML not allowed)  

Live Comment Preview