openide.util.lookup/src/org/openide/util/lookup/implspi/AbstractServiceProviderProcessor.java
1.1 --- a/openide.util.lookup/src/org/openide/util/lookup/implspi/AbstractServiceProviderProcessor.java Wed Jan 27 17:46:23 2010 -0500
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,307 +0,0 @@
1.4 -/*
1.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
1.6 - *
1.7 - * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
1.8 - *
1.9 - * The contents of this file are subject to the terms of either the GNU
1.10 - * General Public License Version 2 only ("GPL") or the Common
1.11 - * Development and Distribution License("CDDL") (collectively, the
1.12 - * "License"). You may not use this file except in compliance with the
1.13 - * License. You can obtain a copy of the License at
1.14 - * http://www.netbeans.org/cddl-gplv2.html
1.15 - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
1.16 - * specific language governing permissions and limitations under the
1.17 - * License. When distributing the software, include this License Header
1.18 - * Notice in each file and include the License file at
1.19 - * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
1.20 - * particular file as subject to the "Classpath" exception as provided
1.21 - * by Sun in the GPL Version 2 section of the License file that
1.22 - * accompanied this code. If applicable, add the following below the
1.23 - * License Header, with the fields enclosed by brackets [] replaced by
1.24 - * your own identifying information:
1.25 - * "Portions Copyrighted [year] [name of copyright owner]"
1.26 - *
1.27 - * If you wish your version of this file to be governed by only the CDDL
1.28 - * or only the GPL Version 2, indicate your decision by adding
1.29 - * "[Contributor] elects to include this software in this distribution
1.30 - * under the [CDDL or GPL Version 2] license." If you do not indicate a
1.31 - * single choice of license, a recipient has the option to distribute
1.32 - * your version of this file under either the CDDL, the GPL Version 2 or
1.33 - * to extend the choice of license to its licensees as provided above.
1.34 - * However, if you add GPL Version 2 code and therefore, elected the GPL
1.35 - * Version 2 license, then the option applies only if the new code is
1.36 - * made subject to such option by the copyright holder.
1.37 - *
1.38 - * Contributor(s):
1.39 - *
1.40 - * Portions Copyrighted 2009 Sun Microsystems, Inc.
1.41 - */
1.42 -
1.43 -package org.openide.util.lookup.implspi;
1.44 -
1.45 -import java.io.BufferedReader;
1.46 -import java.io.FileNotFoundException;
1.47 -import java.io.IOException;
1.48 -import java.io.InputStream;
1.49 -import java.io.InputStreamReader;
1.50 -import java.io.OutputStream;
1.51 -import java.io.OutputStreamWriter;
1.52 -import java.io.PrintWriter;
1.53 -import java.lang.annotation.Annotation;
1.54 -import java.util.ArrayList;
1.55 -import java.util.HashMap;
1.56 -import java.util.List;
1.57 -import java.util.Map;
1.58 -import java.util.Set;
1.59 -import java.util.WeakHashMap;
1.60 -import javax.annotation.processing.AbstractProcessor;
1.61 -import javax.annotation.processing.ProcessingEnvironment;
1.62 -import javax.annotation.processing.RoundEnvironment;
1.63 -import javax.lang.model.element.AnnotationMirror;
1.64 -import javax.lang.model.element.AnnotationValue;
1.65 -import javax.lang.model.element.Element;
1.66 -import javax.lang.model.element.ExecutableElement;
1.67 -import javax.lang.model.element.Modifier;
1.68 -import javax.lang.model.element.TypeElement;
1.69 -import javax.lang.model.type.TypeMirror;
1.70 -import javax.lang.model.util.ElementFilter;
1.71 -import javax.tools.Diagnostic.Kind;
1.72 -import javax.tools.FileObject;
1.73 -import javax.tools.StandardLocation;
1.74 -
1.75 -/**
1.76 - * Infrastructure for generating {@code META-INF/services/*} and
1.77 - * {@code META-INF/namedservices/*} registrations from annotations.
1.78 - * @since 8.1
1.79 - */
1.80 -public abstract class AbstractServiceProviderProcessor extends AbstractProcessor {
1.81 -
1.82 - private final Map<ProcessingEnvironment,Map<String,List<String>>> outputFilesByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<String>>>();
1.83 - private final Map<ProcessingEnvironment,Map<String,List<Element>>> originatingElementsByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<Element>>>();
1.84 - private final Map<TypeElement,Boolean> verifiedClasses = new WeakHashMap<TypeElement,Boolean>();
1.85 -
1.86 - /** Throws IllegalStateException. For access by selected subclasses. */
1.87 - protected AbstractServiceProviderProcessor() {
1.88 - if (getClass().getName().equals("org.netbeans.modules.openide.util.ServiceProviderProcessor")) { // NOI18N
1.89 - // OK subclass
1.90 - return;
1.91 - }
1.92 - if (getClass().getName().equals("org.netbeans.modules.openide.util.URLStreamHandlerRegistrationProcessor")) { // NOI18N
1.93 - // OK subclass
1.94 - return;
1.95 - }
1.96 - throw new IllegalStateException();
1.97 - }
1.98 -
1.99 - public @Override final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1.100 - if (roundEnv.errorRaised()) {
1.101 - return false;
1.102 - }
1.103 - if (roundEnv.processingOver()) {
1.104 - writeServices();
1.105 - outputFilesByProcessor.clear();
1.106 - originatingElementsByProcessor.clear();
1.107 - return true;
1.108 - } else {
1.109 - return handleProcess(annotations, roundEnv);
1.110 - }
1.111 - }
1.112 -
1.113 - /**
1.114 - * The regular body of {@link #process}.
1.115 - * Called during regular rounds if there are no outstanding errors.
1.116 - * In the last round, one of the processors will write out generated registrations.
1.117 - * @param annotations as in {@link #process}
1.118 - * @param roundEnv as in {@link #process}
1.119 - * @return as in {@link #process}
1.120 - */
1.121 - protected abstract boolean handleProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
1.122 -
1.123 - /**
1.124 - * Register a service.
1.125 - * If the class does not have an appropriate signature, an error will be printed and the registration skipped.
1.126 - * @param clazz the service implementation type
1.127 - * @param annotation the (top-level) annotation registering the service, for diagnostic purposes
1.128 - * @param type the type to which the implementation must be assignable
1.129 - * @param path a path under which to register, or "" if inapplicable
1.130 - * @param position a position at which to register, or {@link Integer#MAX_VALUE} to skip
1.131 - * @param supersedes possibly empty list of implementation to supersede
1.132 - */
1.133 - protected final void register(TypeElement clazz, Class<? extends Annotation> annotation,
1.134 - TypeMirror type, String path, int position, String[] supersedes) {
1.135 - Boolean verify = verifiedClasses.get(clazz);
1.136 - if (verify == null) {
1.137 - verify = verifyServiceProviderSignature(clazz, annotation);
1.138 - verifiedClasses.put(clazz, verify);
1.139 - }
1.140 - if (!verify) {
1.141 - return;
1.142 - }
1.143 - String impl = processingEnv.getElementUtils().getBinaryName(clazz).toString();
1.144 - String xface = processingEnv.getElementUtils().getBinaryName((TypeElement) processingEnv.getTypeUtils().asElement(type)).toString();
1.145 - if (!processingEnv.getTypeUtils().isAssignable(clazz.asType(), type)) {
1.146 - AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
1.147 - processingEnv.getMessager().printMessage(Kind.ERROR, impl + " is not assignable to " + xface,
1.148 - clazz, ann, findAnnotationValue(ann, "service"));
1.149 - return;
1.150 - }
1.151 - processingEnv.getMessager().printMessage(Kind.NOTE,
1.152 - impl + " to be registered as a " + xface + (path.length() > 0 ? " under " + path : ""));
1.153 - String rsrc = (path.length() > 0 ? "META-INF/namedservices/" + path + "/" : "META-INF/services/") + xface;
1.154 - {
1.155 - Map<String,List<Element>> originatingElements = originatingElementsByProcessor.get(processingEnv);
1.156 - if (originatingElements == null) {
1.157 - originatingElements = new HashMap<String,List<Element>>();
1.158 - originatingElementsByProcessor.put(processingEnv, originatingElements);
1.159 - }
1.160 - List<Element> origEls = originatingElements.get(rsrc);
1.161 - if (origEls == null) {
1.162 - origEls = new ArrayList<Element>();
1.163 - originatingElements.put(rsrc, origEls);
1.164 - }
1.165 - origEls.add(clazz);
1.166 - }
1.167 - Map<String,List<String>> outputFiles = outputFilesByProcessor.get(processingEnv);
1.168 - if (outputFiles == null) {
1.169 - outputFiles = new HashMap<String,List<String>>();
1.170 - outputFilesByProcessor.put(processingEnv, outputFiles);
1.171 - }
1.172 - List<String> lines = outputFiles.get(rsrc);
1.173 - if (lines == null) {
1.174 - lines = new ArrayList<String>();
1.175 - try {
1.176 - try {
1.177 - FileObject in = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", rsrc);
1.178 - in.openInputStream().close();
1.179 - processingEnv.getMessager().printMessage(Kind.ERROR,
1.180 - "Cannot generate " + rsrc + " because it already exists in sources: " + in.toUri());
1.181 - return;
1.182 - } catch (NullPointerException ex) {
1.183 - // trying to prevent java.lang.NullPointerException
1.184 - // at com.sun.tools.javac.util.DefaultFileManager.getFileForOutput(DefaultFileManager.java:1078)
1.185 - // at com.sun.tools.javac.util.DefaultFileManager.getFileForOutput(DefaultFileManager.java:1054)
1.186 - // at com.sun.tools.javac.processing.JavacFiler.getResource(JavacFiler.java:434)
1.187 - // at org.netbeans.modules.openide.util.AbstractServiceProviderProcessor.register(AbstractServiceProviderProcessor.java:163)
1.188 - // at org.netbeans.modules.openide.util.ServiceProviderProcessor.register(ServiceProviderProcessor.java:99)
1.189 - } catch (FileNotFoundException x) {
1.190 - // Good.
1.191 - }
1.192 - try {
1.193 - FileObject in = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", rsrc);
1.194 - InputStream is = in.openInputStream();
1.195 - try {
1.196 - BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
1.197 - String line;
1.198 - while ((line = r.readLine()) != null) {
1.199 - lines.add(line);
1.200 - }
1.201 - } finally {
1.202 - is.close();
1.203 - }
1.204 - } catch (FileNotFoundException x) {
1.205 - // OK, created for the first time
1.206 - }
1.207 - } catch (IOException x) {
1.208 - processingEnv.getMessager().printMessage(Kind.ERROR, x.toString());
1.209 - return;
1.210 - }
1.211 - outputFiles.put(rsrc, lines);
1.212 - }
1.213 - int idx = lines.indexOf(impl);
1.214 - if (idx != -1) {
1.215 - lines.remove(idx);
1.216 - while (lines.size() > idx && lines.get(idx).matches("#position=.+|#-.+")) {
1.217 - lines.remove(idx);
1.218 - }
1.219 - }
1.220 - lines.add(impl);
1.221 - if (position != Integer.MAX_VALUE) {
1.222 - lines.add("#position=" + position);
1.223 - }
1.224 - for (String exclude : supersedes) {
1.225 - lines.add("#-" + exclude);
1.226 - }
1.227 - }
1.228 -
1.229 - /**
1.230 - * @param element a source element
1.231 - * @param annotation a type of annotation
1.232 - * @return the instance of that annotation on the element, or null if not found
1.233 - */
1.234 - private AnnotationMirror findAnnotationMirror(Element element, Class<? extends Annotation> annotation) {
1.235 - for (AnnotationMirror ann : element.getAnnotationMirrors()) {
1.236 - if (processingEnv.getElementUtils().getBinaryName((TypeElement) ann.getAnnotationType().asElement()).
1.237 - contentEquals(annotation.getName())) {
1.238 - return ann;
1.239 - }
1.240 - }
1.241 - return null;
1.242 - }
1.243 -
1.244 - /**
1.245 - * @param annotation an annotation instance (null permitted)
1.246 - * @param name the name of an attribute of that annotation
1.247 - * @return the corresponding value if found
1.248 - */
1.249 - private AnnotationValue findAnnotationValue(AnnotationMirror annotation, String name) {
1.250 - if (annotation != null) {
1.251 - for (Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : annotation.getElementValues().entrySet()) {
1.252 - if (entry.getKey().getSimpleName().contentEquals(name)) {
1.253 - return entry.getValue();
1.254 - }
1.255 - }
1.256 - }
1.257 - return null;
1.258 - }
1.259 -
1.260 - private final boolean verifyServiceProviderSignature(TypeElement clazz, Class<? extends Annotation> annotation) {
1.261 - AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
1.262 - if (!clazz.getModifiers().contains(Modifier.PUBLIC)) {
1.263 - processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must be public", clazz, ann);
1.264 - return false;
1.265 - }
1.266 - if (clazz.getModifiers().contains(Modifier.ABSTRACT)) {
1.267 - processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must not be abstract", clazz, ann);
1.268 - return false;
1.269 - }
1.270 - {
1.271 - boolean hasDefaultCtor = false;
1.272 - for (ExecutableElement constructor : ElementFilter.constructorsIn(clazz.getEnclosedElements())) {
1.273 - if (constructor.getModifiers().contains(Modifier.PUBLIC) && constructor.getParameters().isEmpty()) {
1.274 - hasDefaultCtor = true;
1.275 - break;
1.276 - }
1.277 - }
1.278 - if (!hasDefaultCtor) {
1.279 - processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must have a public no-argument constructor", clazz, ann);
1.280 - return false;
1.281 - }
1.282 - }
1.283 - return true;
1.284 - }
1.285 -
1.286 - private void writeServices() {
1.287 - for (Map.Entry<ProcessingEnvironment,Map<String,List<String>>> outputFiles : outputFilesByProcessor.entrySet()) {
1.288 - for (Map.Entry<String, List<String>> entry : outputFiles.getValue().entrySet()) {
1.289 - try {
1.290 - FileObject out = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", entry.getKey(),
1.291 - originatingElementsByProcessor.get(outputFiles.getKey()).get(entry.getKey()).toArray(new Element[0]));
1.292 - OutputStream os = out.openOutputStream();
1.293 - try {
1.294 - PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
1.295 - for (String line : entry.getValue()) {
1.296 - w.println(line);
1.297 - }
1.298 - w.flush();
1.299 - w.close();
1.300 - } finally {
1.301 - os.close();
1.302 - }
1.303 - } catch (IOException x) {
1.304 - processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString());
1.305 - }
1.306 - }
1.307 - }
1.308 - }
1.309 -
1.310 -}