| <html> |
| <head> |
| <title>Using JAM to Improve Developer Experience with JSR175</title> |
| |
| <link href="jam.css" rel="stylesheet" type="text/css" /> |
| |
| |
| </style> |
| </head> |
| |
| |
| <body> |
| |
| <h2>Using JAM to Improve Developer Experience with JSR175</h2> |
| |
| <h3>Overview</h3> |
| <p> |
| <a href='http://www.jcp.org/en/jsr/detail?id=175'>JSR175</a> is |
| a new metadata specification being rolled out in JDK1.5. It promises |
| to formalize the current ad-hoc javadoc tag-based systems by introducing |
| new language constructs and relfection APIs for viewing metadata. On |
| the whole, this is going to be a very good thing for the java |
| community. |
| </p> |
| |
| <p> |
| However, the transition to JSR175 is going to be painful for some |
| developers, especially those who have already developed tools which process |
| javadoc tags. This document provides a detailed description of what |
| some of those difficulties are and how the JAM API can help. |
| </p> |
| |
| <h3>The Problem</h3> |
| |
| <p> |
| JSR175 brings us a world in which Java has language constructs |
| to support the declaration of metadata, as well as strongly-typed APIs |
| for accessing it at runtime. Gone will be the days of parsing |
| nastily-encoded javadoc tags, replaced by the convenience of |
| compile-time checks on annotations and simple bean-like interfaces for |
| accessing them. There is no doubt that these are huge and long-overdue |
| improvements to Java. |
| </p> |
| <p> |
| However, there are a few practical problems which java developers |
| face when looking at JSR175. The most obvious is that it only works in |
| JDK1.5. Java developers who wish to utilize 175 must convince their users |
| to upgrade to a new JRE. Historically, this has often been a very |
| difficult thing to |
| do. Developers who are unable to convince their users to upgrade to 1.5 |
| but wish to still take advantage of JSR175 are faced with the daunting |
| task of wedding older javadoc tag-based annotations with the newer 175 |
| structures. The problem is compounded by the fact that Sun has |
| failed to articulate any migration strategy for such developers and users. |
| </p> |
| |
| <p> |
| However, aside from JRE versioning problems, there is another, less-obvious |
| fly in the 175 ointment. This other problem will not evaporate as |
| soon as JRE 1.4.2 has gone the way of 1.0.2 - it is a problem fundamentally |
| woven into the JSR175 specification. It was the result of a conscious design |
| decision by the spec's authors, and it was made with good reasons |
| and the best of intentions. It is not a problem that will affect all |
| java developers, and perhaps not even very many of them. Nonetheless, for a |
| subset of java programmers trying to perform certain kinds of tasks, it |
| will remain a problem. |
| </p> |
| |
| <p> |
| The root of this problem is that these new, wonderful |
| strongly-typed annotation structures are interfaces, as opposed |
| concrete classes. |
| </p> |
| |
| <h3>Divergent APIs</h3> |
| |
| <p> |
| Why is that a problem? To answer that, a quick perusal of the 1.5 |
| JDK documentation is informative. Notice <code>java.lang.Class</code>, |
| you can get instances of <code>java.lang.annotation.Annotation</code>. |
| However, if you look at the 1.5 Javadoc Doclet API, you will see that |
| there is no way to do this; rather, it provides only untyped, 'by-name' |
| access to the annotation data. |
| </p> |
| |
| <p> |
| Why is this? Remember that those Annotation types are really interfaces. |
| They don't have a constructor and you cannot instantiate them directly. |
| The 1.5 JVM has some magic to synthesize an implementation of the |
| annotation type on the fly at runtime and return an instance of it for you |
| to use. Unfortunately, the JVM doesn't share that magic with Javadoc, |
| or with anyone else, for that matter. As a result, there is no way to get |
| an instance of those annotation interfaces unless you are running in a VM |
| that has classloaded compiled versions of the annotated classes. |
| </p> |
| |
| <p> |
| Now one may well ask: so what? Aren't you always running in a VM |
| that has classloaded compiled version of the annotated classes? Well, |
| this is true most of the time for most developers. However, there are some |
| developers who want to pre-process java souce before it is compiled. |
| Often this is because they want access to information that is lost |
| during java compilation, such as comments. This source analysis is |
| typically used in driving some kind code generation - xdoclet is a good |
| example of this. But more generally, this will be a problem for anyone |
| who is implementing or extending any kind of compilation technology which |
| directly reads JSR175-annotated java sources. |
| </p> |
| |
| <p> |
| Sun's response (implicit in the 1.5 doclet API) seems to be that if you |
| are processing java sources in this way, not only are you stuck using a |
| completely different API for modelling java types, that API in turn forces |
| you to use an untyped access model for metadata on those types. |
| <p> |
| |
| <p> |
| This is somewhat inconvenient. Strong typing in java programming is |
| almost always a Good Thing, and again, one of the great promises of 175 |
| is to bring strong typing to the world of metadata. The failure to fully |
| deliver on this promise to our cadre of compiler and compiler extension |
| authors may cause them to wonder what was so great about JSR175 in the |
| first place. |
| </p> |
| |
| <p> |
| <h3>So what can JAM do about this?</h3> |
| |
| <p>JAM's solution to this problem is simple: return control of the |
| metadata access model to the compiler authors. This is achieved by |
| allowing authors to create a bean which acts as a proxy to the JSR 175 |
| annotation data; these beans are called <i>AnnotationProxies</i>.</p> |
| |
| <p> |
| The annotation data being proxied may in fact reside on an instance of |
| <code>java.lang.annotation.Annotation</code>, in the case where we are |
| looking at an annotation in a 1.5 VM. However, it may also simply |
| be an 175 annotation declaration in a java source file (remember, |
| again, that there we have no way to instantiate the 175 interface for |
| the annotation be declared). JAM does all the work to plug the |
| data into the AnnotationProxy. The compiler author in turn gets a |
| strongly-typed, consistent API metadata without having to worry about |
| where it's coming from. |
| </p> |
| |
| <p> |
| Here is a simple example of what this looks like. Say we have defined |
| a 175 annotation type, like the following: |
| </p> |
| |
| <pre> |
| package com.special; |
| |
| public @interface SpecialId { |
| public int id() default 13; |
| } |
| </pre> |
| |
| <p> |
| Our compiler author would then define a proxy to represent the SpecialId |
| annotation which would probably look something like this: |
| </p> |
| |
| <p> |
| (you can ignore the verbose javadocs unless you're looking for more detail): |
| </p> |
| |
| <pre> |
| package com.special; |
| /** |
| * This class is defined by the author to act as a proxy to instances |
| * ot the SpecialId annotation. Though the author is largely free |
| * to do whatever they want in this class here, it is probably advisable |
| * to make it conform to the conventions and constraints |
| * associated with JSR 175 annotation types as much as possible, i.e. |
| * as if it actually implemented the 175 annotation interface. |
| * This means there are limits on the types of the bean properties |
| * and also that accessor methods are not prefixed with 'get'. A |
| * proxy should resemble the real thing as much as possible. |
| * |
| * The only really firm requirement is that the class be public, |
| * non-abstract, and have a public default constructor. It also is |
| * required that you provide setter methods that correspond to the type |
| * and names of the annotation properties so that JAM can use reflection |
| * to populate the AnnotationProxy. (Actually, even this is not a really |
| * firm requirement if you're willing to do a little extra work - see |
| * the TypedAnnotationProxyBase javadocs for details). |
| */ |
| public class SpecialIdProxy extends TypedAnnotationProxyBase { |
| public SpecialIdProxy() {} // a public no-arg constructor is required |
| public int id() { return mId;} // just like the 175 annotation method |
| public void setId(int id) { mId = id; } // this gets called by JAM |
| private int mId = 13; // note that this is same as default |
| } |
| </pre> |
| |
| <p> |
| We have chosen to make the SpecialIdProxy expose the same public face |
| as our JSR175 interface, <code>SpecialId</code> (they both have an |
| <code>id()</code> property accessor). However, it is worth noting that |
| SpecialIdProxy does <b>not</b> implement <code>SpecialId</code>. |
| There are two reasons for this: |
| </p><p> |
| <ul> |
| <li> |
| Doing so would immediately lock our code into JDK 1.5. This |
| is beacuse SpecialId will extend from |
| <code>java.lang.annotation-Annotation</code>, which is 1.5-specific. |
| </li> |
| <li> |
| As of this writing, it's not entirely clear that writing your own |
| implementation of a JSR175 annotation interface is a valid thing to do |
| </li> |
| </ul> |
| </p> |
| |
| <p> |
| So, now we have an JSR175 annotation interface, and we've written a |
| proxy class that looks just like it (but does not implement it). |
| So far, so redundant, right? |
| </p> |
| |
| <p> |
| To see why we have done this, let's write a very simple compiler-like |
| tool that inspects a single java source file or java class, prints out |
| it's comments (if it has any) and outputs the value of the SpecialId |
| annotation. That program would look something like this: |
| </p> |
| |
| <pre> |
| package com.special; |
| |
| public class SpecialIdViewer { |
| |
| public static void main(String[] args) |
| { |
| // Verify the args are correct |
| if (args.length != 1) { |
| throw new Exception("must pass one java class name or "+ |
| "relative source file path"); |
| } |
| |
| // Bootstrap into JAM. We need to create a JamService that has either |
| // a class- or source- based representation of a class, depending |
| // on what they specified. |
| |
| JamServiceFactory factory = JamServiceFactory.getInstance(); |
| JamServiceParams params = factory.createServiceParams(); |
| |
| // Establish a mapping between the 175 annotation type and our proxy |
| params.register175AnnotationProxy(SpecialIdProxy.class,"com.special.SpecialId"); |
| |
| // Decide whether they want to look at a source file or a class |
| if (args[0].endsWith(".java")) { |
| params.includeSourceFiles(new File("."),args[0]); |
| } else { |
| params.includeClass(args[0]); |
| } |
| |
| // Ok, create our entry point into JAM |
| JamService service = factory.createService(params); |
| |
| // Whew! Ok, enough set up, let's get some real work done |
| |
| // First, we get a JClass from the service that represents the |
| // class they specified. In this particular case, we know |
| // there can be exactly one, so just get it. |
| JClass clazz = service.getClasses[0]; |
| |
| // Now get the class doc and print it out if it's available |
| JComment comment = clazz.getComment(); |
| System.out.println((comment != null) ? comment.getText(): |
| "[comments are not available from classfiles]"); |
| |
| // Ok, get (a proxy to) the annotation |
| SpecialIdProxy annotation = |
| (SpecialIdProxy)c.getAnnotation(SpecialIdProxy.class); |
| |
| // Note that at this point we have a typed structure for our metadata |
| // which we can use exactly like a real 175 annotation. |
| int specialId = annotation.id(); |
| System.out.println("The special id is: "+specialId); |
| } |
| } |
| </pre> |
| |
| <p> |
| We can now compile our SpecialIdViewer along with our SpecialId 175 |
| annotation type and our SpecialIdProxy proxy. With that done, we might |
| now jar these things up and then send them off into the world so that |
| people can annotate their classes with SpecialIds and view them with our |
| SpecialIdViewer. |
| </p> |
| |
| <p>Let's take a look at how that would work. Say we have a JRE 1.5 user |
| who has just downloaded our jar. They add our jar to their classpath |
| and then compile the following java source file they have written: |
| |
| <pre> |
| package com.random.user; |
| |
| import com.special.SpecialId; |
| |
| /** |
| * This is some random user code compiled with 1.5 javac. |
| */ |
| @SpecialId(id = 42) |
| protected abstract class RandomUserCode {} |
| </pre> |
| |
| <p>Now they run our SpecialIdViewer tool and see the following:</p> |
| |
| <pre> |
| > java com.special.SpecialIdViewer com.random.user.RandomUserCode |
| [comments are not available from classfiles] |
| The special id is: 42 |
| </pre> |
| |
| <p> |
| Nothing too surprising there, but what exactly happened? When we asked |
| for the class' annotation of type SpecialIdProxy, JAM looked up the |
| SpecialId on the user class (because that's what we mapped |
| SpecialIdProxy to in the call to <code>mapAnnotationProxy()</code>), |
| created a new instance of SpecialIdProxy, copied the SpecialId data into |
| that proxy, and returned it to us so that we could print it out. It also |
| realized that it was unable to print out the class comments because it |
| only had the class, not the source. |
| </p> |
| |
| <p> |
| At this point, you are probably still wondering why we went to all |
| of this trouble with this proxy business. Why didn't we just use the |
| 175 SpecialId annotation type directly? To illustrate the advantage, |
| consider the same example again, except that this time the user |
| does not run <code>javac</code> on RandomUserCode.java and instead |
| provides our tool with only raw, uncompiled <code>.java</code> source code. |
| </p> |
| |
| <pre> |
| > java com.special.SpecialIdViewer RandomUserCode.java |
| This is some random user code. |
| The special id is: 42 |
| </pre> |
| |
| <p> |
| Note JAM is able to give us the comment now, since this time we |
| have the source code. But otherwise, things basically worked just |
| the same as before. Nothing to surprising here, either. |
| </p> |
| |
| <p> |
| But wait, isn't that itself surprising? Think for a minute about what you |
| would have to do in order to make this work without JAM (i.e. with |
| reflection and javadoc or something similar like xjavadoc or qdox. |
| At the point in our SpecialIdViewer where you decide whether you are |
| looking at a source file or a class file, you would have to fork your |
| codepath - on the one path, you would use the Reflection APIs, get your |
| 175 SpecialId instance directly, and decide not to print out a comment. |
| On the other path, you would have to use javadoc's untyped API to get the |
| SpecialId value, and in this case you would be able to print out a comment. |
| In effect, you would have written almost all of your tool twice over. |
| </p> |
| |
| <p> |
| In case you're still not surprised, consider what happens if our user |
| suddently decides to switch back to JRE 1.4. If they repeat the |
| previous example (using the source file), it still works just the |
| same. How is that possible, you ask? Well, first off, note that |
| SpecialIdViewer itself will still load just fine under 1.4 because |
| SpecialIdProxy has isolated it from any 1.5 dependencies. And because |
| we have the source file (RandomUserCode.java), JAM's java source parser is |
| able to parse the 1.5 annotations and populate the SpecialIdProxy without |
| requiring anything from 1.5. |
| </p> |
| |
| <p> |
| That may be a neat party trick, but is it really useful? After all, |
| RandomUserCode is never going to compile or classload under 1.4. |
| Why someone want to do this? In truth, they probably won't. However, |
| they might want to do something similar but slightly different. |
| </p> |
| |
| <h2>Processing Javadoc Tags</h2> |
| |
| <p> |
| Take the case of a user who has not yet upgraded to JDK 1.5. They |
| simply refuse to upgrade, for the usual reasons users refuse to upgrade. |
| They still want to markup their classes with our SpecialIds, but because |
| they won't have JRE 1.5, they won't be able to use our JSR175 annotation |
| (SpecialId) to do it.</p> |
| |
| <p> |
| The obvious choice in this case to let them declare their special ids |
| with javadoc tags, which have been the de facto standard for declaring |
| java metadata prior to JDK 1.5. Accordingly, our stubborn 1.4 user might |
| expect to be able to run SpecialIdViewer on the following 1.4 java |
| source file: |
| </p> |
| |
| <pre> |
| package com.stubborn.user; |
| |
| /** |
| * This is code from someone who simply will not upgrade to 1.5. |
| * |
| * @special id=79 |
| */ |
| protected abstract class StubbornUserCode {} |
| </pre> |
| |
| <p> |
| This seems reasonable enough. Unfortunately, though, that means we're going |
| to have to write a lot more code in our tool. Now we have to check for |
| javadoc tags, parse them if they are there, and then somehow glue |
| everything together in our code so that the behavior is the same. Right? |
| </p> |
| |
| <p> |
| Wrong, fortunately. JAM can be a big help in this case as well. To support |
| this javadoc tag, we only need to add one new line (in bold) to our |
| SpecialIdViewer tool: |
| </p> |
| |
| <pre> |
| package com.special; |
| |
| public class SpecialIdViewer { |
| |
| public static void main(String[] args) |
| { |
| ... |
| // Establish a mapping between the 175 annotation type and our proxy |
| params.register175AnnotationProxy(SpecialIdProxy.class, "com.special.SpecialId"); |
| |
| // Also establish a mapping between the '@special' javadoc tag and our proxy |
| <b>params.registerJavadocTagProxymapJavadocTagProxy(SpecialIdProxy.class, "special");</b> |
| ... |
| } |
| </pre> |
| |
| <p> |
| Now our stubborn 1.4 user runs our tool, and the desired output appears: |
| </p> |
| |
| <pre> |
| > java com.special.SpecialIdViewer StubbornUserCode.java |
| This is code from someone who simply will not upgrade to 1.4. |
| The special id is: 79 |
| </pre> |
| |
| <p> |
| This works by exactly the same principle as our earlier example using |
| JRE 1.5. We have created a SpecialIdProxy through which we want to |
| view metadata. We now have told JAM that we want that class to be a |
| proxy for the 'special' javadoc tag (as well as the SpecialId 175 |
| annotation type). When JAM is processes the java source, it therefore |
| knows that when it sees a 'special' tag, it should parse out it's values |
| and populate an instance of our SpecialIdProxy for us to use in our code. |
| </p> |
| |
| <p> |
| So, here again we see how useful the annotation proxy is. Because we |
| have isolated our processing logic from the details of parsing metadata, |
| the same code works in all of the different scenarios we have seen: with |
| source files, with class files, with 175-style annotations, or with javadoc |
| annotations. You don't have to waste time worrying about how your |
| metadata and type information is going to get represented or parsed. |
| JAM lets you hide all of this away so you can focus on the real problems |
| your your application is trying to solve. |
| |
| </p> |
| |
| |
| <p> |
| <i>Still have questions? <a href='faq.html'>Try looking in FAQ.</a> |
| </p> |
| |
| |
| |
| |
| </body> |
| </html> |
| |
| |