changeset: 105054:d822d0d40c42 tag: tip user: Jesse Glick date: Fri Oct 17 13:06:18 2008 -0400 summary: #150447: @Service annotation. diff --git a/openide.modules/nbproject/project.properties b/openide.modules/nbproject/project.properties --- a/openide.modules/nbproject/project.properties +++ b/openide.modules/nbproject/project.properties @@ -39,6 +39,7 @@ javac.compilerargs=-Xlint:unchecked javac.source=1.5 +cp.extra=${nb_all}/libs.javacapi/external/javac-api-nb-7.0-b07.jar module.jar.dir=lib javadoc.main.page=org/openide/modules/doc-files/api.html javadoc.arch=${basedir}/arch.xml diff --git a/openide.modules/src/META-INF/services/javax.annotation.processing.Processor b/openide.modules/src/META-INF/services/javax.annotation.processing.Processor new file mode 100644 --- /dev/null +++ b/openide.modules/src/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1,1 @@ +org.netbeans.modules.openide.modules.ServiceProcessor diff --git a/openide.modules/src/org/netbeans/modules/openide/modules/ServiceProcessor.java b/openide.modules/src/org/netbeans/modules/openide/modules/ServiceProcessor.java new file mode 100644 --- /dev/null +++ b/openide.modules/src/org/netbeans/modules/openide/modules/ServiceProcessor.java @@ -0,0 +1,211 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2008 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.openide.modules; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.tools.Diagnostic.Kind; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import org.openide.modules.Service; + +@SupportedSourceVersion(SourceVersion.RELEASE_6) +@SupportedAnnotationTypes("org.openide.modules.Service") +public class ServiceProcessor extends AbstractProcessor { + + /** public for ServiceLoader */ + public ServiceProcessor() {} + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + return false; + } + Map> outputFiles = new HashMap>(); + Map> originatingElements = new HashMap>(); + for (Element el : roundEnv.getElementsAnnotatedWith(Service.class)) { + TypeElement clazz = (TypeElement) el; + if (!clazz.getModifiers().contains(Modifier.PUBLIC)) { + processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must be public"); + continue; + } + if (clazz.getModifiers().contains(Modifier.ABSTRACT)) { + processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must not be abstract"); + continue; + } + { + boolean hasDefaultCtor = false; + for (ExecutableElement constructor : ElementFilter.constructorsIn(clazz.getEnclosedElements())) { + if (constructor.getModifiers().contains(Modifier.PUBLIC) && constructor.getParameters().isEmpty()) { + hasDefaultCtor = true; + break; + } + } + if (!hasDefaultCtor) { + processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must have a public no-argument constructor"); + continue; + } + } + for (AnnotationMirror ann : clazz.getAnnotationMirrors()) { + if (ann.getAnnotationType().asElement().equals(processingEnv.getElementUtils().getTypeElement(Service.class.getName()))) { + for (Map.Entry entry : ann.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().contentEquals("value")) { + for (Object val : (List) entry.getValue().getValue()) { + register(clazz, (TypeMirror) ((AnnotationValue) val).getValue(), outputFiles, originatingElements); + } + } + } + } + } + } + for (Map.Entry> entry : outputFiles.entrySet()) { + try { + FileObject out = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", entry.getKey(), + originatingElements.get(entry.getKey()).toArray(new Element[0])); + OutputStream os = out.openOutputStream(); + try { + PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8")); + for (String line : entry.getValue()) { + w.println(line); + } + w.flush(); + w.close(); + } finally { + os.close(); + } + } catch (IOException x) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString()); + } + } + return true; + } + + private void register(TypeElement clazz, TypeMirror type, Map> outputFiles, Map> originatingElements) { + String impl = processingEnv.getElementUtils().getBinaryName(clazz).toString(); + String xface = processingEnv.getElementUtils().getBinaryName((TypeElement) processingEnv.getTypeUtils().asElement(type)).toString(); + if (!processingEnv.getTypeUtils().isAssignable(clazz.asType(), type)) { + processingEnv.getMessager().printMessage(Kind.ERROR, impl + " is not assignable to " + xface); + return; + } + processingEnv.getMessager().printMessage(Kind.NOTE, impl + " to be registered as a " + xface); + Service svc = clazz.getAnnotation(Service.class); + String rsrc = (svc.path().length() > 0 ? "META-INF/namedservices/" + svc.path() + "/" : "META-INF/services/") + xface; + { + List origEls = originatingElements.get(rsrc); + if (origEls == null) { + origEls = new ArrayList(); + originatingElements.put(rsrc, origEls); + } + origEls.add(clazz); + } + List lines = outputFiles.get(rsrc); + if (lines == null) { + lines = new ArrayList(); + try { + try { + FileObject in = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", rsrc); + in.openInputStream().close(); + processingEnv.getMessager().printMessage(Kind.ERROR, "Cannot generate " + rsrc + " because it already exists in sources: " + in.toUri()); + return; + } catch (FileNotFoundException x) { + // Good. + } + try { + FileObject in = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", rsrc); + InputStream is = in.openInputStream(); + try { + BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8")); + String line; + while ((line = r.readLine()) != null) { + lines.add(line); + } + } finally { + is.close(); + } + } catch (FileNotFoundException x) { + // OK, created for the first time + } + } catch (IOException x) { + processingEnv.getMessager().printMessage(Kind.ERROR, x.toString()); + return; + } + outputFiles.put(rsrc, lines); + } + int idx = lines.indexOf(impl); + if (idx != -1) { + lines.remove(idx); + while (lines.size() > idx && lines.get(idx).matches("#position=.+|#-.+")) { + lines.remove(idx); + } + } + lines.add(impl); + if (svc.position() != Integer.MAX_VALUE) { + lines.add("#position=" + svc.position()); + } + for (String exclude : svc.supersedes()) { + lines.add("#-" + exclude); + } + } + +} diff --git a/openide.modules/src/org/openide/modules/Service.java b/openide.modules/src/org/openide/modules/Service.java new file mode 100644 --- /dev/null +++ b/openide.modules/src/org/openide/modules/Service.java @@ -0,0 +1,102 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2008 Sun Microsystems, Inc. + */ + +package org.openide.modules; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +/** + * Declarative registration of a singleton service. + * By marking an implementation class with this annotation, + * you automatically register that implementation, normally in {@link Lookup#getDefault}. + * The class must be public and have a public no-argument constructor. + *

Example of usage: + *

+ * package my.module;
+ * import org.netbeans.spi.whatever.Thing;
+ * @Service(Thing.class)
+ * public class MyThing implements Thing {...}
+ * 
+ *

would result in a resource file META-INF/services/org.netbeans.spi.whatever.Thing + * containing the single line of text: my.module.MyThing + * @see Lookups#metaInfServices(ClassLoader) + * @since XXX #150447 + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Service { + + /** + * The interface to register this implementation under. + * You must supply at least one interface (or abstract class); rarely you might want more than one. + * It is an error if the implementation class is not in fact assignable to the interface. + *

Requests to look up the specified interface should result in this implementation. + * Requests for any other types may or may not result in this implementation even if the + * implementation is assignable to those types. + */ + Class[] value(); + + /** + * An optional position in which to register this service relative to others. + * Lower-numbered services are returned in the lookup result first. + * Services with no specified position are returned last. + */ + int position() default Integer.MAX_VALUE; + + /** + * An optional list of implementations (given as fully-qualified class names) which this implementation supersedes. + * If specified, those implementations will not be loaded even if they were registered. + * Useful on occasion to cancel a generic implementation and replace it with a more advanced one. + */ + String[] supersedes() default {}; + + /** + * An optional path to register this implementation in. + * For example, Projects/sometype/Nodes could be used. + * This style of registration would be recognized by {@link Lookups#forPath} + * rather than {@link Lookup#getDefault}. + */ + String path() default ""; + +}