lookup/src/main/java/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java
1.1 --- a/lookup/src/main/java/org/netbeans/modules/openide/util/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,296 +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.netbeans.modules.openide.util;
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 - */
1.79 -public abstract class AbstractServiceProviderProcessor extends AbstractProcessor {
1.80 -
1.81 - private final Map<ProcessingEnvironment,Map<String,List<String>>> outputFilesByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<String>>>();
1.82 - private final Map<ProcessingEnvironment,Map<String,List<Element>>> originatingElementsByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<Element>>>();
1.83 - private final Map<TypeElement,Boolean> verifiedClasses = new WeakHashMap<TypeElement,Boolean>();
1.84 -
1.85 - /** For access by subclasses. */
1.86 - protected AbstractServiceProviderProcessor() {}
1.87 -
1.88 - public @Override final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1.89 - if (roundEnv.errorRaised()) {
1.90 - return false;
1.91 - }
1.92 - if (roundEnv.processingOver()) {
1.93 - writeServices();
1.94 - outputFilesByProcessor.clear();
1.95 - originatingElementsByProcessor.clear();
1.96 - return true;
1.97 - } else {
1.98 - return handleProcess(annotations, roundEnv);
1.99 - }
1.100 - }
1.101 -
1.102 - /**
1.103 - * The regular body of {@link #process}.
1.104 - * Called during regular rounds if there are no outstanding errors.
1.105 - * In the last round, one of the processors will write out generated registrations.
1.106 - * @param annotations as in {@link #process}
1.107 - * @param roundEnv as in {@link #process}
1.108 - * @return as in {@link #process}
1.109 - */
1.110 - protected abstract boolean handleProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
1.111 -
1.112 - /**
1.113 - * Register a service.
1.114 - * If the class does not have an appropriate signature, an error will be printed and the registration skipped.
1.115 - * @param clazz the service implementation type
1.116 - * @param annotation the (top-level) annotation registering the service, for diagnostic purposes
1.117 - * @param type the type to which the implementation must be assignable
1.118 - * @param path a path under which to register, or "" if inapplicable
1.119 - * @param position a position at which to register, or {@link Integer#MAX_VALUE} to skip
1.120 - * @param supersedes possibly empty list of implementation to supersede
1.121 - */
1.122 - protected final void register(TypeElement clazz, Class<? extends Annotation> annotation,
1.123 - TypeMirror type, String path, int position, String[] supersedes) {
1.124 - Boolean verify = verifiedClasses.get(clazz);
1.125 - if (verify == null) {
1.126 - verify = verifyServiceProviderSignature(clazz, annotation);
1.127 - verifiedClasses.put(clazz, verify);
1.128 - }
1.129 - if (!verify) {
1.130 - return;
1.131 - }
1.132 - String impl = processingEnv.getElementUtils().getBinaryName(clazz).toString();
1.133 - String xface = processingEnv.getElementUtils().getBinaryName((TypeElement) processingEnv.getTypeUtils().asElement(type)).toString();
1.134 - if (!processingEnv.getTypeUtils().isAssignable(clazz.asType(), type)) {
1.135 - AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
1.136 - processingEnv.getMessager().printMessage(Kind.ERROR, impl + " is not assignable to " + xface,
1.137 - clazz, ann, findAnnotationValue(ann, "service"));
1.138 - return;
1.139 - }
1.140 - processingEnv.getMessager().printMessage(Kind.NOTE,
1.141 - impl + " to be registered as a " + xface + (path.length() > 0 ? " under " + path : ""));
1.142 - String rsrc = (path.length() > 0 ? "META-INF/namedservices/" + path + "/" : "META-INF/services/") + xface;
1.143 - {
1.144 - Map<String,List<Element>> originatingElements = originatingElementsByProcessor.get(processingEnv);
1.145 - if (originatingElements == null) {
1.146 - originatingElements = new HashMap<String,List<Element>>();
1.147 - originatingElementsByProcessor.put(processingEnv, originatingElements);
1.148 - }
1.149 - List<Element> origEls = originatingElements.get(rsrc);
1.150 - if (origEls == null) {
1.151 - origEls = new ArrayList<Element>();
1.152 - originatingElements.put(rsrc, origEls);
1.153 - }
1.154 - origEls.add(clazz);
1.155 - }
1.156 - Map<String,List<String>> outputFiles = outputFilesByProcessor.get(processingEnv);
1.157 - if (outputFiles == null) {
1.158 - outputFiles = new HashMap<String,List<String>>();
1.159 - outputFilesByProcessor.put(processingEnv, outputFiles);
1.160 - }
1.161 - List<String> lines = outputFiles.get(rsrc);
1.162 - if (lines == null) {
1.163 - lines = new ArrayList<String>();
1.164 - try {
1.165 - try {
1.166 - FileObject in = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", rsrc);
1.167 - in.openInputStream().close();
1.168 - processingEnv.getMessager().printMessage(Kind.ERROR,
1.169 - "Cannot generate " + rsrc + " because it already exists in sources: " + in.toUri());
1.170 - return;
1.171 - } catch (NullPointerException ex) {
1.172 - // trying to prevent java.lang.NullPointerException
1.173 - // at com.sun.tools.javac.util.DefaultFileManager.getFileForOutput(DefaultFileManager.java:1078)
1.174 - // at com.sun.tools.javac.util.DefaultFileManager.getFileForOutput(DefaultFileManager.java:1054)
1.175 - // at com.sun.tools.javac.processing.JavacFiler.getResource(JavacFiler.java:434)
1.176 - // at org.netbeans.modules.openide.util.AbstractServiceProviderProcessor.register(AbstractServiceProviderProcessor.java:163)
1.177 - // at org.netbeans.modules.openide.util.ServiceProviderProcessor.register(ServiceProviderProcessor.java:99)
1.178 - } catch (FileNotFoundException x) {
1.179 - // Good.
1.180 - }
1.181 - try {
1.182 - FileObject in = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", rsrc);
1.183 - InputStream is = in.openInputStream();
1.184 - try {
1.185 - BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
1.186 - String line;
1.187 - while ((line = r.readLine()) != null) {
1.188 - lines.add(line);
1.189 - }
1.190 - } finally {
1.191 - is.close();
1.192 - }
1.193 - } catch (FileNotFoundException x) {
1.194 - // OK, created for the first time
1.195 - }
1.196 - } catch (IOException x) {
1.197 - processingEnv.getMessager().printMessage(Kind.ERROR, x.toString());
1.198 - return;
1.199 - }
1.200 - outputFiles.put(rsrc, lines);
1.201 - }
1.202 - int idx = lines.indexOf(impl);
1.203 - if (idx != -1) {
1.204 - lines.remove(idx);
1.205 - while (lines.size() > idx && lines.get(idx).matches("#position=.+|#-.+")) {
1.206 - lines.remove(idx);
1.207 - }
1.208 - }
1.209 - lines.add(impl);
1.210 - if (position != Integer.MAX_VALUE) {
1.211 - lines.add("#position=" + position);
1.212 - }
1.213 - for (String exclude : supersedes) {
1.214 - lines.add("#-" + exclude);
1.215 - }
1.216 - }
1.217 -
1.218 - /**
1.219 - * @param element a source element
1.220 - * @param annotation a type of annotation
1.221 - * @return the instance of that annotation on the element, or null if not found
1.222 - */
1.223 - private AnnotationMirror findAnnotationMirror(Element element, Class<? extends Annotation> annotation) {
1.224 - for (AnnotationMirror ann : element.getAnnotationMirrors()) {
1.225 - if (processingEnv.getElementUtils().getBinaryName((TypeElement) ann.getAnnotationType().asElement()).
1.226 - contentEquals(annotation.getName())) {
1.227 - return ann;
1.228 - }
1.229 - }
1.230 - return null;
1.231 - }
1.232 -
1.233 - /**
1.234 - * @param annotation an annotation instance (null permitted)
1.235 - * @param name the name of an attribute of that annotation
1.236 - * @return the corresponding value if found
1.237 - */
1.238 - private AnnotationValue findAnnotationValue(AnnotationMirror annotation, String name) {
1.239 - if (annotation != null) {
1.240 - for (Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : annotation.getElementValues().entrySet()) {
1.241 - if (entry.getKey().getSimpleName().contentEquals(name)) {
1.242 - return entry.getValue();
1.243 - }
1.244 - }
1.245 - }
1.246 - return null;
1.247 - }
1.248 -
1.249 - private final boolean verifyServiceProviderSignature(TypeElement clazz, Class<? extends Annotation> annotation) {
1.250 - AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
1.251 - if (!clazz.getModifiers().contains(Modifier.PUBLIC)) {
1.252 - processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must be public", clazz, ann);
1.253 - return false;
1.254 - }
1.255 - if (clazz.getModifiers().contains(Modifier.ABSTRACT)) {
1.256 - processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must not be abstract", clazz, ann);
1.257 - return false;
1.258 - }
1.259 - {
1.260 - boolean hasDefaultCtor = false;
1.261 - for (ExecutableElement constructor : ElementFilter.constructorsIn(clazz.getEnclosedElements())) {
1.262 - if (constructor.getModifiers().contains(Modifier.PUBLIC) && constructor.getParameters().isEmpty()) {
1.263 - hasDefaultCtor = true;
1.264 - break;
1.265 - }
1.266 - }
1.267 - if (!hasDefaultCtor) {
1.268 - processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must have a public no-argument constructor", clazz, ann);
1.269 - return false;
1.270 - }
1.271 - }
1.272 - return true;
1.273 - }
1.274 -
1.275 - private void writeServices() {
1.276 - for (Map.Entry<ProcessingEnvironment,Map<String,List<String>>> outputFiles : outputFilesByProcessor.entrySet()) {
1.277 - for (Map.Entry<String, List<String>> entry : outputFiles.getValue().entrySet()) {
1.278 - try {
1.279 - FileObject out = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", entry.getKey(),
1.280 - originatingElementsByProcessor.get(outputFiles.getKey()).get(entry.getKey()).toArray(new Element[0]));
1.281 - OutputStream os = out.openOutputStream();
1.282 - try {
1.283 - PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
1.284 - for (String line : entry.getValue()) {
1.285 - w.println(line);
1.286 - }
1.287 - w.flush();
1.288 - w.close();
1.289 - } finally {
1.290 - os.close();
1.291 - }
1.292 - } catch (IOException x) {
1.293 - processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString());
1.294 - }
1.295 - }
1.296 - }
1.297 - }
1.298 -
1.299 -}