Skip navigation links
org.openide.filesystems 9.15

Package org.openide.filesystems.annotations

Support for writing annotation processors which generate XML layer fragments.

See: Description

Package org.openide.filesystems.annotations Description

Support for writing annotation processors which generate XML layer fragments.

Whenever an SPI author defines a new way for objects to be registered in the system filesystem, it is encouraged to also define a matching annotation which can create such a registration. If the SPI is associated with a particular Java interface (or abstract class), conventionally this annotation should be named Registration and be located as a nested type inside the interface.

For example, consider an interface FrobnitzFactory which should be registered for a particular data type such as text/html. The SPI may declare that a frobnitz factory should be located in the system filesystem in the folder FrobnitzFactories/mime/type/ where mime/type is the associated data type, and should be declared as an instance file whose instance is assignable to FrobnitzFactory (basename of file irrelevant).

(The SPI could also have just declared a global service interface where each instance specifies its desired MIME type in the return value of a method. This would work but would be undesirable from a performance perspective: the first time the SPI ran, it would need to load every frobnitz factory in the system, which could mean a lot of class loading, even though only one was actually going to be used. Choosing a smart registration style can help avoid this kind of performance mistake, and friendly registration annotations mean that module developers can do the right thing without much effort.)

The SPI may also stipulate that for a given data type, it may matter which factory is found "first". SPI code to handle the factories might look like:

static Frobnitz findFrobnitz(FileObject f) {
    for (FrobnitzFactory ff : Lookups.forPath("FrobnitzFactories/" + f.getMIMEType()).
            lookup(FrobnitzFactory.class).allInstances()) {
        Frobnitz fz = ff.newFrobnitz(f);
        if (fz != null) {
            return fz;
        }
    }
    return null;
}
 

There should then be an interface with a corresponding annotation:

public interface FrobnitzFactory {
    Frobnitz newFrobnitz(FileObject file); // may return null
    @interface Registration {
        String mimeType();
        int position() default Integer.MAX_VALUE;
    }
}
 

Using the annotation is simple. The module author need create just one file containing both the factory and its registration:

@FrobnitzFactory.Registration(mimeType="text/html", position=300)
public class HtmlFactory implements FrobnitzFactory {
    public Frobnitz newFrobnitz(FileObject file) {...}
}
 

Now writing the annotation processor to create such a layer registration is easy. Put the processor in some nonpublic package in the SPI module and register it using ServiceProvider. You should extend LayerGeneratingProcessor, which manages the physical writing of the generated layer fragment(s) in cooperation with any other active processors. The LayerBuilder helper class is used to create file entries and attributes. LayerGenerationException can also be thrown if the source code is erroneous.

@ServiceProvider(service=Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class FrobnitzFactoryProcessor extends LayerGeneratingProcessor {
    public @Override Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(Registration.class.getCanonicalName());
    }
    protected boolean handleProcess(Set<? extends TypeElement> annotations,
                                    RoundEnvironment roundEnv)
                      throws LayerGenerationException {
        if (roundEnv.processingOver()) {
            return false;
        }
        for (Element e : roundEnv.getElementsAnnotatedWith(Registration.class)) {
            Registration r = e.getAnnotation(Registration.class);
            if (!r.mimeType().matches("(text|application|image)/([^/]+)")) {
                throw new LayerGenerationException("Bad MIME type: " + r.mimeType(), e);
            }
            layer(e).instanceFile("FrobnitzFactories/" + r.mimeType(), null,
                FrobnitzFactory.class).position(r.position()).write();
        }
        return true;
    }
}
 

Now when the module is compiled, build/classes/META-INF/generated-layer.xml should look something like this:

<filesystem>
    <folder name="FrobnitzFactories">
        <folder name="text">
            <folder name="html">
                <file name="my-module-HtmlFactory.instance">
                    <attr name="position" intvalue="300"/>
                </file>
            </folder>
        </folder>
    </folder>
</filesystem>
 

and this layer should be loaded automatically by the module system (in addition to any explicit layer specified in source code).

There are two basic ways to test a layer-generating processor:

  1. Create some registrations of the annotation inside the unit test class (so that they are processed as the tests are compiled). Make the test check that the corresponding SPI loads the registrations.
  2. Run the processor programmatically on some sample registrations, confirming that it succeeds or aborts under the right conditions. For this, AnnotationProcessorTestUtils is useful.

ServiceProviderProcessorTest demonstrates both styles.

Skip navigation links
org.openide.filesystems 9.15