[This is the first installment in a series of two articles]
One feature I really enjoy when programming on the .NET platform is attributes. Attributes are tags in the source code, which again is compiled into metadata in the assemblies. The attributes provide additional information about programming elements such as fields and methods.
In the .NET framework attributes are used to mark that methods should be callable over the SOAP protocol, include copyright and versioning information in an assembly, enforce security and describe how XML serialization should map class members to XML markup. These are just some many uses of attributes in the framework.
The attributes themselves do not contain any logic. For instance, the actual plumbing used to expose a method over the SOAP protocol is handled by the ASP.NET host application. This makes attributes highly reusable.
On a recent project I had the need to include similar metadata in my client-side JavaScript code as provided by the attributes in the System.Xml.Serialization namespace. I wanted this to be as reusable as the System.Attribute class.
However, the value of such a class is to a great extent determined by the developer’s affordance with its usage. In addition, JavaScript and C# are to widely different programming languages. I therefore had to come up with a declarative programming style for JavaScript that bore resemblance of .NET attributes.
With the introduction of the Java 5 platform, codenamed “Tiger”, Sun has brought the declarative programming style to the Java language. The “Tiger” annotations are functionally similar to the .NET attributes, but they differ syntactically.
In C# you can declare a method as WebMethod with the following lines of code:
[WebMethod(Description=”This method is callable over the SOAP protocol.”)]
public void HelloWorld() {
// Implementation eluded for clarity
}
Figure 1 - Example of a .NET attribute
In “Tiger” a similar declaration would look something like this:
@webMethod(description=”This method is also callable over the SOAP protocol.”)
public void helloWorld() {
// Implementation eluded for clarity
}
Figure 2 - Example of a Java "Tiger" annotation
The Java language has had limited support for metadata via its Javadoc tags. XDoclet, an open source project, has used these tags to support code annotation and generation based on metadata. Using Apache Axis and XDoclet a web method is declared like this:
/**
*ejb.interface-method
*axis.method
*/
public void helloWorld() {
// Implementation eluded for clarity
}
Figure 3 - Example of XDoclet attributes
As you can see from the example above, XDoclet uses comments to declare its metadata. This pattern could also be adopted in JavaScript. I chose to use the Java annotation syntax since JavaScript has a closer relation to the Java language than .NET.
The first step was to create a simple Annotation class as shown in Figure 1.
|
Annotation |
| |
|
| _setMember(string name,object value) |
Sets the field of the given name to the given value. |
Figure 4 - The Annotation base class
The Annotation class does not have any fields or methods, except for a _setMember method intended to set the metadata values on the different annotations. The Annotation class should be regarded as abstract and all concrete annotations should be prototypes of this class.
function VersionAnnotation() {
var v=args.length==1?args.split(’.’):args;
this.major=v[0];
this.minor=v[1];
this.revision=v[2];
}
VersionAnnotation.prototype=new Annotation();
Figure 5 - Defining an annotation
As mentioned, attributes or annotations are pure metadata and do not contain any logic. We therefore need a way to retrieve the annotations for a particular object. In both .NET and Java “Tiger” this is done via reflection. JavaScript also has an ability to reflect over different object instances. A named member of an object is accessible by treating the object as a collection. obj[“name”] would yield the value of the name member of the obj instance. In addition it is possible to iterate through all the members of an object using a for-each construct.
When calling the toString method on a function the actual source code is returned. This gives us a possibility of including an XDoclet like comment with the different annotations in any method which can be parsed to retrieve annotation names and metadata. This can then be used to create instances of the different Annotation classes.
However, fields do not have any associated source code that can be retrieved programmatically. This makes it difficult to annotate the fields on an object. A simple workaround for this is to create get accessors for all fields and declare the annotations in these.
Calculator.square=function(num) {
/**
*@Version(“1.0.0”)
*@Modifiers(modifiers=Modifier.static|Modifier.public) *@Returns(type=”number”,description=”The square of the given number”)
*/
return num*num;
}
Figure 6 - Declaratively annotating a method
To retrieve the metadata information I developed a Reflection class. This class has a number of methods to retrieve different information about JavaScript objects. The Reflection.getAnnotations accepts a function as a parameter creates instances of all annotations and returns these in an array. If an annotation is declared in a comment without a corresponding annotation class defined, an exception is thrown.
It is also possible to retrieve a single, named annotation by using the Reflection.getNamedAnnotation method. This method also accepts a function as a parameter.
var version=Reflection.getNamedAnnotation(Calculator.square);
alert(“Major: “+version.major+'\n'+
"Minor: “+version.minor+'\n'+
"Revision: “+version.revision);
Figure 7 - Retrieving an annotation
The code in Figure 7 shows how to retrieve the version annotation from the Calculator.square method.
This article has shown how the concept can be implemented in JavaScript. In the next installment in this series I will make use of declarative JavaScript programming to bridge the gap between client side JavaScript code and server side .NET code by adding support for XML serialization to JavaScript. Figures 8 and 9 show the complete source code Reflection and Annotation classes. Note that these classes are “work-in-progress” and probably contain some bugs.
The full source code to the Refletion and Annotation classes can be viewed here: http://dotnetjunkies.com/WebLog/anoras/articles/21501.aspx