How do I develop and debug annotations for NetBeans platform apps?

Apache NetBeans Wiki Index

Note: These pages are being reviewed.

The NetBeans platform has API support for creating XML layer entries from annotations. The popular book "NetBeans Platform for Beginners" has several examples of writing your own annotations. Familiarity with developing annotations and annotation processing is a prerequisite. Though not specifically about NetBeans platform annotations, Annotation Processors Support in the NetBeans IDE may also be useful for those new to working with annotions.

The relevant NetBeans platform APIs are described at Package org.openide.filesystems.annotations. Note that Class LayerBuilder.File has the methods for adding specific attributes to a layer file. Near the end of the package description there is mention of AnnotationProcessorTestUtils this is found in the NetBeans sources at openide.util.lookup/test/unit/src/org/openide/util/test/AnnotationProcessorTestUtils.java.

Much of the following is found in messages of the thread Debugging Platform annotations, from the platform mailing list.

Debugging Custom Annotations

Printf is your friend

Tim Boudreau says: I’ve written quite a few annotation processors, and System.out.println() is your friend. Trying to actually step through this stuff in a debugger is pretty useless, but I’ve never hit something I couldn’t solve with plain old console logging.

If you really want to use a debugger

There is a mailing list thread Debugging an annotation processor from a few years ago which has a messy/complicated process suggesting ANT_OPS and "Attach Debugger" outlined by Jaroslav Tulach .

Hints and techniques for developing annotations

Things that generally bite you when writing an annotation processor:

  • Handling unexpected types - i.e. someone writes an annotation and a parameter that’s supposed to be an int is in source code as a string - best you can do is catch these and bail out, but if you don’t, you’ll see an exception dialog pop up in NetBeans when your processor hits code like that

  • Dealing with things that explode on contact - i.e. annotation parameters of type Class will throw an exception if you try to read their value via the Annotation instances javac gives you (the Class objects referenced are not necessarily on the classpath, or even valid) - you have to instead find the right AnnotationMirror and get the value as a string - example here, see validatorsForParam() https://github.com/timboudreau/numble/blob/master/src/main/java/com/mastfrog/parameters/processor/Processor.java

  • Opening or trying to write a file more than once (annotation processing happens in multiple rounds - until the last round you should just collect data) - but LayerGeneratingProcessor should solve this for you if you’re using it - but if not, have a look at the source code for it

Anything you do regarding analyzing or using classes when processing annotations you want to do using javac’s API - you do not ever want to load a user-defined class into an annotation processor. Imagine what

static {
    for (;;) { EventQueue.invokeLater(new Runnable(){ throw new Error(); })}
}

would do if you actually loaded something like that during a compilation.

Examples

Checking for a subtype, can’t use instanceof since should not load user classes.

public void isSubtypeOf(Element e, String qualifiedClassName) {
        Types types = processingEnv.getTypeUtils();
        Elements elements = processingEnv.getElementUtils();
        TypeElement pageType = elements.getTypeElement(qualifiedClassName);
        if (pageType == null) { //not on the classpath javac can see
            return false;
        }
        return types.isSubtype(e.asType(), pageType.asType());
}

Getting the elements of an enum, with lots of checking

private List<String> getEnumConstants(String enumQalifiedClassName) {
    if(enumQalifiedClassName.isEmpty())
        return Collections.emptyList();
    Element e = processingEnv.getElementUtils()
            .getTypeElement(enumQalifiedClassName);
    if(e == null) {
        processingEnv.getMessager().printMessage(
                Diagnostic.Kind.ERROR, "enumQalifiedClassName '" + enumQalifiedClassName
                + "' does not exist");
        return null;
    }
    if(e.getKind() != ElementKind.ENUM) {
        processingEnv.getMessager().printMessage(
                Diagnostic.Kind.ERROR, "enumQalifiedClassName '" + enumQalifiedClassName
                        + "' is not an Enum");
        return null;
    }
    Types types = processingEnv.getTypeUtils();
    List<? extends Element> elems = e.getEnclosedElements();
    List<String> enumConstantsNames = new ArrayList<String>(elems.size());
    for(Element e01 : elems) {
        if(e01.getKind() == ElementKind.ENUM_CONSTANT)
            enumConstantsNames.add(e01.getSimpleName().toString());
    }
    return enumConstantsNames;
}