org.openide.filesystems.annotations.package-info Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* 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
* {@code Registration} and be located as a nested type inside the interface.
* For example, consider an interface {@code FrobnitzFactory} which should be
* registered for a particular data type such as {@code text/html}. The SPI may
* declare that a frobnitz factory should be located in the system filesystem
* in the folder {@code FrobnitzFactories/mime/type/} where {@code mime/type} is
* the associated data type, and should be declared as an instance file whose instance
* is assignable to {@code 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 {@link org.openide.util.lookup.ServiceProvider}.
* You should extend {@link org.openide.filesystems.annotations.LayerGeneratingProcessor}, which manages the physical
* writing of the generated layer fragment(s) in cooperation with any other active processors.
* The {@link org.openide.filesystems.annotations.LayerBuilder} helper class is used to create file entries and attributes.
* {@link org.openide.filesystems.annotations.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, {@code 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:
*
* - 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.
* - 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.
*/
package org.openide.filesystems.annotations;