avoid an implementation dependency
authorMilos Kleint <mkleint@netbeans.org>
Fri, 22 Jan 2010 14:50:19 +0100
changeset 967793a9628c24b
parent 966 c08236dd63c9
child 968 7479a9c25061
avoid an implementation dependency
openide.util.lookup/src/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java
openide.util.lookup/src/org/netbeans/modules/openide/util/ActiveQueue.java
openide.util/nbproject/project.xml
openide.util/src/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java
openide.util/src/org/netbeans/modules/openide/util/ActiveQueue.java
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/openide.util.lookup/src/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java	Fri Jan 22 14:50:19 2010 +0100
     1.3 @@ -0,0 +1,298 @@
     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 + * NOTE: This class is duplicated in openide.util and openide.util.lookup to prevent implementation dependency.
    1.80 + */
    1.81 +public abstract class AbstractServiceProviderProcessor extends AbstractProcessor {
    1.82 +
    1.83 +    private final Map<ProcessingEnvironment,Map<String,List<String>>> outputFilesByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<String>>>();
    1.84 +    private final Map<ProcessingEnvironment,Map<String,List<Element>>> originatingElementsByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<Element>>>();
    1.85 +    private final Map<TypeElement,Boolean> verifiedClasses = new WeakHashMap<TypeElement,Boolean>();
    1.86 +
    1.87 +    /** For access by subclasses. */
    1.88 +    protected AbstractServiceProviderProcessor() {}
    1.89 +
    1.90 +    public @Override final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    1.91 +        if (roundEnv.errorRaised()) {
    1.92 +            return false;
    1.93 +        }
    1.94 +        if (roundEnv.processingOver()) {
    1.95 +            writeServices();
    1.96 +            outputFilesByProcessor.clear();
    1.97 +            originatingElementsByProcessor.clear();
    1.98 +            return true;
    1.99 +        } else {
   1.100 +            return handleProcess(annotations, roundEnv);
   1.101 +        }
   1.102 +    }
   1.103 +
   1.104 +    /**
   1.105 +     * The regular body of {@link #process}.
   1.106 +     * Called during regular rounds if there are no outstanding errors.
   1.107 +     * In the last round, one of the processors will write out generated registrations.
   1.108 +     * @param annotations as in {@link #process}
   1.109 +     * @param roundEnv as in {@link #process}
   1.110 +     * @return as in {@link #process}
   1.111 +     */
   1.112 +    protected abstract boolean handleProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
   1.113 +
   1.114 +    /**
   1.115 +     * Register a service.
   1.116 +     * If the class does not have an appropriate signature, an error will be printed and the registration skipped.
   1.117 +     * @param clazz the service implementation type
   1.118 +     * @param annotation the (top-level) annotation registering the service, for diagnostic purposes
   1.119 +     * @param type the type to which the implementation must be assignable
   1.120 +     * @param path a path under which to register, or "" if inapplicable
   1.121 +     * @param position a position at which to register, or {@link Integer#MAX_VALUE} to skip
   1.122 +     * @param supersedes possibly empty list of implementation to supersede
   1.123 +     */
   1.124 +    protected final void register(TypeElement clazz, Class<? extends Annotation> annotation,
   1.125 +            TypeMirror type, String path, int position, String[] supersedes) {
   1.126 +        Boolean verify = verifiedClasses.get(clazz);
   1.127 +        if (verify == null) {
   1.128 +            verify = verifyServiceProviderSignature(clazz, annotation);
   1.129 +            verifiedClasses.put(clazz, verify);
   1.130 +        }
   1.131 +        if (!verify) {
   1.132 +            return;
   1.133 +        }
   1.134 +        String impl = processingEnv.getElementUtils().getBinaryName(clazz).toString();
   1.135 +        String xface = processingEnv.getElementUtils().getBinaryName((TypeElement) processingEnv.getTypeUtils().asElement(type)).toString();
   1.136 +        if (!processingEnv.getTypeUtils().isAssignable(clazz.asType(), type)) {
   1.137 +            AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
   1.138 +            processingEnv.getMessager().printMessage(Kind.ERROR, impl + " is not assignable to " + xface,
   1.139 +                    clazz, ann, findAnnotationValue(ann, "service"));
   1.140 +            return;
   1.141 +        }
   1.142 +        processingEnv.getMessager().printMessage(Kind.NOTE,
   1.143 +                impl + " to be registered as a " + xface + (path.length() > 0 ? " under " + path : ""));
   1.144 +        String rsrc = (path.length() > 0 ? "META-INF/namedservices/" + path + "/" : "META-INF/services/") + xface;
   1.145 +        {
   1.146 +            Map<String,List<Element>> originatingElements = originatingElementsByProcessor.get(processingEnv);
   1.147 +            if (originatingElements == null) {
   1.148 +                originatingElements = new HashMap<String,List<Element>>();
   1.149 +                originatingElementsByProcessor.put(processingEnv, originatingElements);
   1.150 +            }
   1.151 +            List<Element> origEls = originatingElements.get(rsrc);
   1.152 +            if (origEls == null) {
   1.153 +                origEls = new ArrayList<Element>();
   1.154 +                originatingElements.put(rsrc, origEls);
   1.155 +            }
   1.156 +            origEls.add(clazz);
   1.157 +        }
   1.158 +        Map<String,List<String>> outputFiles = outputFilesByProcessor.get(processingEnv);
   1.159 +        if (outputFiles == null) {
   1.160 +            outputFiles = new HashMap<String,List<String>>();
   1.161 +            outputFilesByProcessor.put(processingEnv, outputFiles);
   1.162 +        }
   1.163 +        List<String> lines = outputFiles.get(rsrc);
   1.164 +        if (lines == null) {
   1.165 +            lines = new ArrayList<String>();
   1.166 +            try {
   1.167 +                try {
   1.168 +                    FileObject in = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", rsrc);
   1.169 +                    in.openInputStream().close();
   1.170 +                    processingEnv.getMessager().printMessage(Kind.ERROR,
   1.171 +                            "Cannot generate " + rsrc + " because it already exists in sources: " + in.toUri());
   1.172 +                    return;
   1.173 +                } catch (NullPointerException ex) {
   1.174 +                    // trying to prevent java.lang.NullPointerException
   1.175 +                    // at com.sun.tools.javac.util.DefaultFileManager.getFileForOutput(DefaultFileManager.java:1078)
   1.176 +                    // at com.sun.tools.javac.util.DefaultFileManager.getFileForOutput(DefaultFileManager.java:1054)
   1.177 +                    // at com.sun.tools.javac.processing.JavacFiler.getResource(JavacFiler.java:434)
   1.178 +                    // at org.netbeans.modules.openide.util.AbstractServiceProviderProcessor.register(AbstractServiceProviderProcessor.java:163)
   1.179 +                    // at org.netbeans.modules.openide.util.ServiceProviderProcessor.register(ServiceProviderProcessor.java:99)
   1.180 +                } catch (FileNotFoundException x) {
   1.181 +                    // Good.
   1.182 +                }
   1.183 +                try {
   1.184 +                    FileObject in = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", rsrc);
   1.185 +                    InputStream is = in.openInputStream();
   1.186 +                    try {
   1.187 +                        BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
   1.188 +                        String line;
   1.189 +                        while ((line = r.readLine()) != null) {
   1.190 +                            lines.add(line);
   1.191 +                        }
   1.192 +                    } finally {
   1.193 +                        is.close();
   1.194 +                    }
   1.195 +                } catch (FileNotFoundException x) {
   1.196 +                    // OK, created for the first time
   1.197 +                }
   1.198 +            } catch (IOException x) {
   1.199 +                processingEnv.getMessager().printMessage(Kind.ERROR, x.toString());
   1.200 +                return;
   1.201 +            }
   1.202 +            outputFiles.put(rsrc, lines);
   1.203 +        }
   1.204 +        int idx = lines.indexOf(impl);
   1.205 +        if (idx != -1) {
   1.206 +            lines.remove(idx);
   1.207 +            while (lines.size() > idx && lines.get(idx).matches("#position=.+|#-.+")) {
   1.208 +                lines.remove(idx);
   1.209 +            }
   1.210 +        }
   1.211 +        lines.add(impl);
   1.212 +        if (position != Integer.MAX_VALUE) {
   1.213 +            lines.add("#position=" + position);
   1.214 +        }
   1.215 +        for (String exclude : supersedes) {
   1.216 +            lines.add("#-" + exclude);
   1.217 +        }
   1.218 +    }
   1.219 +
   1.220 +    /**
   1.221 +     * @param element a source element
   1.222 +     * @param annotation a type of annotation
   1.223 +     * @return the instance of that annotation on the element, or null if not found
   1.224 +     */
   1.225 +    private AnnotationMirror findAnnotationMirror(Element element, Class<? extends Annotation> annotation) {
   1.226 +        for (AnnotationMirror ann : element.getAnnotationMirrors()) {
   1.227 +            if (processingEnv.getElementUtils().getBinaryName((TypeElement) ann.getAnnotationType().asElement()).
   1.228 +                    contentEquals(annotation.getName())) {
   1.229 +                return ann;
   1.230 +            }
   1.231 +        }
   1.232 +        return null;
   1.233 +    }
   1.234 +
   1.235 +    /**
   1.236 +     * @param annotation an annotation instance (null permitted)
   1.237 +     * @param name the name of an attribute of that annotation
   1.238 +     * @return the corresponding value if found
   1.239 +     */
   1.240 +    private AnnotationValue findAnnotationValue(AnnotationMirror annotation, String name) {
   1.241 +        if (annotation != null) {
   1.242 +            for (Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : annotation.getElementValues().entrySet()) {
   1.243 +                if (entry.getKey().getSimpleName().contentEquals(name)) {
   1.244 +                    return entry.getValue();
   1.245 +                }
   1.246 +            }
   1.247 +        }
   1.248 +        return null;
   1.249 +    }
   1.250 +
   1.251 +    private final boolean verifyServiceProviderSignature(TypeElement clazz, Class<? extends Annotation> annotation) {
   1.252 +        AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
   1.253 +        if (!clazz.getModifiers().contains(Modifier.PUBLIC)) {
   1.254 +            processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must be public", clazz, ann);
   1.255 +            return false;
   1.256 +        }
   1.257 +        if (clazz.getModifiers().contains(Modifier.ABSTRACT)) {
   1.258 +            processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must not be abstract", clazz, ann);
   1.259 +            return false;
   1.260 +        }
   1.261 +        {
   1.262 +            boolean hasDefaultCtor = false;
   1.263 +            for (ExecutableElement constructor : ElementFilter.constructorsIn(clazz.getEnclosedElements())) {
   1.264 +                if (constructor.getModifiers().contains(Modifier.PUBLIC) && constructor.getParameters().isEmpty()) {
   1.265 +                    hasDefaultCtor = true;
   1.266 +                    break;
   1.267 +                }
   1.268 +            }
   1.269 +            if (!hasDefaultCtor) {
   1.270 +                processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must have a public no-argument constructor", clazz, ann);
   1.271 +                return false;
   1.272 +            }
   1.273 +        }
   1.274 +        return true;
   1.275 +    }
   1.276 +
   1.277 +    private void writeServices() {
   1.278 +        for (Map.Entry<ProcessingEnvironment,Map<String,List<String>>> outputFiles : outputFilesByProcessor.entrySet()) {
   1.279 +            for (Map.Entry<String, List<String>> entry : outputFiles.getValue().entrySet()) {
   1.280 +                try {
   1.281 +                    FileObject out = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", entry.getKey(),
   1.282 +                            originatingElementsByProcessor.get(outputFiles.getKey()).get(entry.getKey()).toArray(new Element[0]));
   1.283 +                    OutputStream os = out.openOutputStream();
   1.284 +                    try {
   1.285 +                        PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
   1.286 +                        for (String line : entry.getValue()) {
   1.287 +                            w.println(line);
   1.288 +                        }
   1.289 +                        w.flush();
   1.290 +                        w.close();
   1.291 +                    } finally {
   1.292 +                        os.close();
   1.293 +                    }
   1.294 +                } catch (IOException x) {
   1.295 +                    processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString());
   1.296 +                }
   1.297 +            }
   1.298 +        }
   1.299 +    }
   1.300 +
   1.301 +}
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/openide.util.lookup/src/org/netbeans/modules/openide/util/ActiveQueue.java	Fri Jan 22 14:50:19 2010 +0100
     2.3 @@ -0,0 +1,110 @@
     2.4 +package org.netbeans.modules.openide.util;
     2.5 +
     2.6 +import java.lang.ref.Reference;
     2.7 +import java.lang.ref.ReferenceQueue;
     2.8 +import java.util.logging.Level;
     2.9 +import java.util.logging.Logger;
    2.10 +
    2.11 +/**
    2.12 + * Implementation of the active reference queue.
    2.13 + *
    2.14 + * NOTE: This class is duplicated in openide.util and openide.util.lookup to prevent implementation dependency.
    2.15 + */
    2.16 +public final class ActiveQueue extends ReferenceQueue<Object> implements Runnable {
    2.17 +
    2.18 +    private static final Logger LOGGER = Logger.getLogger(ActiveQueue.class.getName().replace('$', '.'));
    2.19 +    private static ActiveQueue activeReferenceQueue;
    2.20 +    
    2.21 +    /** number of known outstanding references */
    2.22 +    private int count;
    2.23 +    private boolean deprecated;
    2.24 +
    2.25 +    ActiveQueue(boolean deprecated) {
    2.26 +        super();
    2.27 +        this.deprecated = deprecated;
    2.28 +    }
    2.29 +
    2.30 +    public static synchronized ReferenceQueue<Object> queue() {
    2.31 +        if (activeReferenceQueue == null) {
    2.32 +            activeReferenceQueue = new ActiveQueue(false);
    2.33 +        }
    2.34 +
    2.35 +        activeReferenceQueue.ping();
    2.36 +
    2.37 +        return activeReferenceQueue;
    2.38 +    }
    2.39 +
    2.40 +    @Override
    2.41 +    public Reference<Object> poll() {
    2.42 +        throw new UnsupportedOperationException();
    2.43 +    }
    2.44 +
    2.45 +    @Override
    2.46 +    public Reference<Object> remove(long timeout) throws IllegalArgumentException, InterruptedException {
    2.47 +        throw new InterruptedException();
    2.48 +    }
    2.49 +
    2.50 +    @Override
    2.51 +    public Reference<Object> remove() throws InterruptedException {
    2.52 +        throw new InterruptedException();
    2.53 +    }
    2.54 +
    2.55 +    public void run() {
    2.56 +        while (true) {
    2.57 +            try {
    2.58 +                Reference<?> ref = super.remove(0);
    2.59 +                LOGGER.finer("dequeued reference");
    2.60 +                if (!(ref instanceof Runnable)) {
    2.61 +                    LOGGER.warning("A reference not implementing runnable has been added to the Utilities.activeReferenceQueue(): " + ref.getClass());
    2.62 +                    continue;
    2.63 +                }
    2.64 +                if (deprecated) {
    2.65 +                    LOGGER.warning("Utilities.ACTIVE_REFERENCE_QUEUE has been deprecated for " + ref.getClass() + " use Utilities.activeReferenceQueue");
    2.66 +                }
    2.67 +                // do the cleanup
    2.68 +                try {
    2.69 +                    ((Runnable) ref).run();
    2.70 +                } catch (ThreadDeath td) {
    2.71 +                    throw td;
    2.72 +                } catch (Throwable t) {
    2.73 +                    // Should not happen.
    2.74 +                    // If it happens, it is a bug in client code, notify!
    2.75 +                    LOGGER.log(Level.WARNING, null, t);
    2.76 +                } finally {
    2.77 +                    // to allow GC
    2.78 +                    ref = null;
    2.79 +                }
    2.80 +            } catch (InterruptedException ex) {
    2.81 +                // Can happen during VM shutdown, it seems. Ignore.
    2.82 +                continue;
    2.83 +            }
    2.84 +            synchronized (this) {
    2.85 +                assert count > 0;
    2.86 +                count--;
    2.87 +                if (count == 0) {
    2.88 +                    // We have processed all we have to process (for now at least).
    2.89 +                    // Could be restarted later if ping() called again.
    2.90 +                    // This could also happen in case someone called queue() once and tried
    2.91 +                    // to use it for several references; in that case run() might never be called on
    2.92 +                    // the later ones to be collected. Can't really protect against that situation.
    2.93 +                    // See issue #86625 for details.
    2.94 +                    LOGGER.fine("stopping thread");
    2.95 +                    break;
    2.96 +                }
    2.97 +            }
    2.98 +        }
    2.99 +    }
   2.100 +
   2.101 +    synchronized void ping() {
   2.102 +        if (count == 0) {
   2.103 +            Thread t = new Thread(this, "Active Reference Queue Daemon");
   2.104 +            t.setPriority(Thread.MIN_PRIORITY);
   2.105 +            t.setDaemon(true);
   2.106 +            t.start();
   2.107 +            LOGGER.fine("starting thread");
   2.108 +        } else {
   2.109 +            LOGGER.finer("enqueuing reference");
   2.110 +        }
   2.111 +        count++;
   2.112 +    }
   2.113 +}
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/openide.util/nbproject/project.xml	Fri Jan 22 14:50:19 2010 +0100
     3.3 @@ -0,0 +1,88 @@
     3.4 +<?xml version="1.0" encoding="UTF-8"?>
     3.5 +<!--
     3.6 +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.7 +
     3.8 +Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
     3.9 +
    3.10 +
    3.11 +The contents of this file are subject to the terms of either the GNU
    3.12 +General Public License Version 2 only ("GPL") or the Common
    3.13 +Development and Distribution License("CDDL") (collectively, the
    3.14 +"License"). You may not use this file except in compliance with the
    3.15 +License. You can obtain a copy of the License at
    3.16 +http://www.netbeans.org/cddl-gplv2.html
    3.17 +or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    3.18 +specific language governing permissions and limitations under the
    3.19 +License.  When distributing the software, include this License Header
    3.20 +Notice in each file and include the License file at
    3.21 +nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    3.22 +particular file as subject to the "Classpath" exception as provided
    3.23 +by Sun in the GPL Version 2 section of the License file that
    3.24 +accompanied this code. If applicable, add the following below the
    3.25 +License Header, with the fields enclosed by brackets [] replaced by
    3.26 +your own identifying information:
    3.27 +"Portions Copyrighted [year] [name of copyright owner]"
    3.28 +
    3.29 +Contributor(s):
    3.30 +
    3.31 +The Original Software is NetBeans. The Initial Developer of the Original
    3.32 +Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
    3.33 +Microsystems, Inc. All Rights Reserved.
    3.34 +
    3.35 +If you wish your version of this file to be governed by only the CDDL
    3.36 +or only the GPL Version 2, indicate your decision by adding
    3.37 +"[Contributor] elects to include this software in this distribution
    3.38 +under the [CDDL or GPL Version 2] license." If you do not indicate a
    3.39 +single choice of license, a recipient has the option to distribute
    3.40 +your version of this file under either the CDDL, the GPL Version 2 or
    3.41 +to extend the choice of license to its licensees as provided above.
    3.42 +However, if you add GPL Version 2 code and therefore, elected the GPL
    3.43 +Version 2 license, then the option applies only if the new code is
    3.44 +made subject to such option by the copyright holder.
    3.45 +-->
    3.46 +<project xmlns="http://www.netbeans.org/ns/project/1">
    3.47 +    <type>org.netbeans.modules.apisupport.project</type>
    3.48 +    <configuration>
    3.49 +        <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
    3.50 +            <code-name-base>org.openide.util</code-name-base>
    3.51 +            <module-dependencies>
    3.52 +                <dependency>
    3.53 +                    <code-name-base>org.openide.util.lookup</code-name-base>
    3.54 +                    <build-prerequisite/>
    3.55 +                    <compile-dependency/>
    3.56 +                    <run-dependency>
    3.57 +                        <specification-version>8.0</specification-version>
    3.58 +                    </run-dependency>
    3.59 +                </dependency>
    3.60 +            </module-dependencies>
    3.61 +            <test-dependencies>
    3.62 +                <test-type>
    3.63 +                    <name>unit</name>
    3.64 +                    <test-dependency>
    3.65 +                        <code-name-base>org.netbeans.libs.junit4</code-name-base>
    3.66 +                        <compile-dependency/>
    3.67 +                    </test-dependency>
    3.68 +                    <test-dependency>
    3.69 +                        <code-name-base>org.netbeans.modules.nbjunit</code-name-base>
    3.70 +                        <recursive/>
    3.71 +                        <compile-dependency/>
    3.72 +                    </test-dependency>
    3.73 +                    <test-dependency>
    3.74 +                        <code-name-base>org.openide.util.lookup</code-name-base>
    3.75 +                        <compile-dependency/>
    3.76 +                        <test/>
    3.77 +                    </test-dependency>
    3.78 +                </test-type>
    3.79 +            </test-dependencies>
    3.80 +            <public-packages>
    3.81 +                <package>org.openide</package>
    3.82 +                <package>org.openide.util</package>
    3.83 +                <package>org.openide.util.datatransfer</package>
    3.84 +                <package>org.openide.util.actions</package>
    3.85 +                <package>org.openide.util.lookup</package>
    3.86 +                <package>org.openide.util.io</package>
    3.87 +                <package>org.openide.xml</package>
    3.88 +            </public-packages>
    3.89 +        </data>
    3.90 +    </configuration>
    3.91 +</project>
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/openide.util/src/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java	Fri Jan 22 14:50:19 2010 +0100
     4.3 @@ -0,0 +1,298 @@
     4.4 +/*
     4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 + *
     4.7 + * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
     4.8 + *
     4.9 + * The contents of this file are subject to the terms of either the GNU
    4.10 + * General Public License Version 2 only ("GPL") or the Common
    4.11 + * Development and Distribution License("CDDL") (collectively, the
    4.12 + * "License"). You may not use this file except in compliance with the
    4.13 + * License. You can obtain a copy of the License at
    4.14 + * http://www.netbeans.org/cddl-gplv2.html
    4.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    4.16 + * specific language governing permissions and limitations under the
    4.17 + * License.  When distributing the software, include this License Header
    4.18 + * Notice in each file and include the License file at
    4.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    4.20 + * particular file as subject to the "Classpath" exception as provided
    4.21 + * by Sun in the GPL Version 2 section of the License file that
    4.22 + * accompanied this code. If applicable, add the following below the
    4.23 + * License Header, with the fields enclosed by brackets [] replaced by
    4.24 + * your own identifying information:
    4.25 + * "Portions Copyrighted [year] [name of copyright owner]"
    4.26 + *
    4.27 + * If you wish your version of this file to be governed by only the CDDL
    4.28 + * or only the GPL Version 2, indicate your decision by adding
    4.29 + * "[Contributor] elects to include this software in this distribution
    4.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    4.31 + * single choice of license, a recipient has the option to distribute
    4.32 + * your version of this file under either the CDDL, the GPL Version 2 or
    4.33 + * to extend the choice of license to its licensees as provided above.
    4.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    4.35 + * Version 2 license, then the option applies only if the new code is
    4.36 + * made subject to such option by the copyright holder.
    4.37 + *
    4.38 + * Contributor(s):
    4.39 + *
    4.40 + * Portions Copyrighted 2009 Sun Microsystems, Inc.
    4.41 + */
    4.42 +
    4.43 +package org.netbeans.modules.openide.util;
    4.44 +
    4.45 +import java.io.BufferedReader;
    4.46 +import java.io.FileNotFoundException;
    4.47 +import java.io.IOException;
    4.48 +import java.io.InputStream;
    4.49 +import java.io.InputStreamReader;
    4.50 +import java.io.OutputStream;
    4.51 +import java.io.OutputStreamWriter;
    4.52 +import java.io.PrintWriter;
    4.53 +import java.lang.annotation.Annotation;
    4.54 +import java.util.ArrayList;
    4.55 +import java.util.HashMap;
    4.56 +import java.util.List;
    4.57 +import java.util.Map;
    4.58 +import java.util.Set;
    4.59 +import java.util.WeakHashMap;
    4.60 +import javax.annotation.processing.AbstractProcessor;
    4.61 +import javax.annotation.processing.ProcessingEnvironment;
    4.62 +import javax.annotation.processing.RoundEnvironment;
    4.63 +import javax.lang.model.element.AnnotationMirror;
    4.64 +import javax.lang.model.element.AnnotationValue;
    4.65 +import javax.lang.model.element.Element;
    4.66 +import javax.lang.model.element.ExecutableElement;
    4.67 +import javax.lang.model.element.Modifier;
    4.68 +import javax.lang.model.element.TypeElement;
    4.69 +import javax.lang.model.type.TypeMirror;
    4.70 +import javax.lang.model.util.ElementFilter;
    4.71 +import javax.tools.Diagnostic.Kind;
    4.72 +import javax.tools.FileObject;
    4.73 +import javax.tools.StandardLocation;
    4.74 +
    4.75 +/**
    4.76 + * Infrastructure for generating {@code META-INF/services/*} and
    4.77 + * {@code META-INF/namedservices/*} registrations from annotations.
    4.78 + *
    4.79 + * NOTE: This class is duplicated in openide.util and openide.util.lookup to prevent implementation dependency.
    4.80 + */
    4.81 +public abstract class AbstractServiceProviderProcessor extends AbstractProcessor {
    4.82 +
    4.83 +    private final Map<ProcessingEnvironment,Map<String,List<String>>> outputFilesByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<String>>>();
    4.84 +    private final Map<ProcessingEnvironment,Map<String,List<Element>>> originatingElementsByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<Element>>>();
    4.85 +    private final Map<TypeElement,Boolean> verifiedClasses = new WeakHashMap<TypeElement,Boolean>();
    4.86 +
    4.87 +    /** For access by subclasses. */
    4.88 +    protected AbstractServiceProviderProcessor() {}
    4.89 +
    4.90 +    public @Override final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    4.91 +        if (roundEnv.errorRaised()) {
    4.92 +            return false;
    4.93 +        }
    4.94 +        if (roundEnv.processingOver()) {
    4.95 +            writeServices();
    4.96 +            outputFilesByProcessor.clear();
    4.97 +            originatingElementsByProcessor.clear();
    4.98 +            return true;
    4.99 +        } else {
   4.100 +            return handleProcess(annotations, roundEnv);
   4.101 +        }
   4.102 +    }
   4.103 +
   4.104 +    /**
   4.105 +     * The regular body of {@link #process}.
   4.106 +     * Called during regular rounds if there are no outstanding errors.
   4.107 +     * In the last round, one of the processors will write out generated registrations.
   4.108 +     * @param annotations as in {@link #process}
   4.109 +     * @param roundEnv as in {@link #process}
   4.110 +     * @return as in {@link #process}
   4.111 +     */
   4.112 +    protected abstract boolean handleProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
   4.113 +
   4.114 +    /**
   4.115 +     * Register a service.
   4.116 +     * If the class does not have an appropriate signature, an error will be printed and the registration skipped.
   4.117 +     * @param clazz the service implementation type
   4.118 +     * @param annotation the (top-level) annotation registering the service, for diagnostic purposes
   4.119 +     * @param type the type to which the implementation must be assignable
   4.120 +     * @param path a path under which to register, or "" if inapplicable
   4.121 +     * @param position a position at which to register, or {@link Integer#MAX_VALUE} to skip
   4.122 +     * @param supersedes possibly empty list of implementation to supersede
   4.123 +     */
   4.124 +    protected final void register(TypeElement clazz, Class<? extends Annotation> annotation,
   4.125 +            TypeMirror type, String path, int position, String[] supersedes) {
   4.126 +        Boolean verify = verifiedClasses.get(clazz);
   4.127 +        if (verify == null) {
   4.128 +            verify = verifyServiceProviderSignature(clazz, annotation);
   4.129 +            verifiedClasses.put(clazz, verify);
   4.130 +        }
   4.131 +        if (!verify) {
   4.132 +            return;
   4.133 +        }
   4.134 +        String impl = processingEnv.getElementUtils().getBinaryName(clazz).toString();
   4.135 +        String xface = processingEnv.getElementUtils().getBinaryName((TypeElement) processingEnv.getTypeUtils().asElement(type)).toString();
   4.136 +        if (!processingEnv.getTypeUtils().isAssignable(clazz.asType(), type)) {
   4.137 +            AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
   4.138 +            processingEnv.getMessager().printMessage(Kind.ERROR, impl + " is not assignable to " + xface,
   4.139 +                    clazz, ann, findAnnotationValue(ann, "service"));
   4.140 +            return;
   4.141 +        }
   4.142 +        processingEnv.getMessager().printMessage(Kind.NOTE,
   4.143 +                impl + " to be registered as a " + xface + (path.length() > 0 ? " under " + path : ""));
   4.144 +        String rsrc = (path.length() > 0 ? "META-INF/namedservices/" + path + "/" : "META-INF/services/") + xface;
   4.145 +        {
   4.146 +            Map<String,List<Element>> originatingElements = originatingElementsByProcessor.get(processingEnv);
   4.147 +            if (originatingElements == null) {
   4.148 +                originatingElements = new HashMap<String,List<Element>>();
   4.149 +                originatingElementsByProcessor.put(processingEnv, originatingElements);
   4.150 +            }
   4.151 +            List<Element> origEls = originatingElements.get(rsrc);
   4.152 +            if (origEls == null) {
   4.153 +                origEls = new ArrayList<Element>();
   4.154 +                originatingElements.put(rsrc, origEls);
   4.155 +            }
   4.156 +            origEls.add(clazz);
   4.157 +        }
   4.158 +        Map<String,List<String>> outputFiles = outputFilesByProcessor.get(processingEnv);
   4.159 +        if (outputFiles == null) {
   4.160 +            outputFiles = new HashMap<String,List<String>>();
   4.161 +            outputFilesByProcessor.put(processingEnv, outputFiles);
   4.162 +        }
   4.163 +        List<String> lines = outputFiles.get(rsrc);
   4.164 +        if (lines == null) {
   4.165 +            lines = new ArrayList<String>();
   4.166 +            try {
   4.167 +                try {
   4.168 +                    FileObject in = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", rsrc);
   4.169 +                    in.openInputStream().close();
   4.170 +                    processingEnv.getMessager().printMessage(Kind.ERROR,
   4.171 +                            "Cannot generate " + rsrc + " because it already exists in sources: " + in.toUri());
   4.172 +                    return;
   4.173 +                } catch (NullPointerException ex) {
   4.174 +                    // trying to prevent java.lang.NullPointerException
   4.175 +                    // at com.sun.tools.javac.util.DefaultFileManager.getFileForOutput(DefaultFileManager.java:1078)
   4.176 +                    // at com.sun.tools.javac.util.DefaultFileManager.getFileForOutput(DefaultFileManager.java:1054)
   4.177 +                    // at com.sun.tools.javac.processing.JavacFiler.getResource(JavacFiler.java:434)
   4.178 +                    // at org.netbeans.modules.openide.util.AbstractServiceProviderProcessor.register(AbstractServiceProviderProcessor.java:163)
   4.179 +                    // at org.netbeans.modules.openide.util.ServiceProviderProcessor.register(ServiceProviderProcessor.java:99)
   4.180 +                } catch (FileNotFoundException x) {
   4.181 +                    // Good.
   4.182 +                }
   4.183 +                try {
   4.184 +                    FileObject in = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", rsrc);
   4.185 +                    InputStream is = in.openInputStream();
   4.186 +                    try {
   4.187 +                        BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
   4.188 +                        String line;
   4.189 +                        while ((line = r.readLine()) != null) {
   4.190 +                            lines.add(line);
   4.191 +                        }
   4.192 +                    } finally {
   4.193 +                        is.close();
   4.194 +                    }
   4.195 +                } catch (FileNotFoundException x) {
   4.196 +                    // OK, created for the first time
   4.197 +                }
   4.198 +            } catch (IOException x) {
   4.199 +                processingEnv.getMessager().printMessage(Kind.ERROR, x.toString());
   4.200 +                return;
   4.201 +            }
   4.202 +            outputFiles.put(rsrc, lines);
   4.203 +        }
   4.204 +        int idx = lines.indexOf(impl);
   4.205 +        if (idx != -1) {
   4.206 +            lines.remove(idx);
   4.207 +            while (lines.size() > idx && lines.get(idx).matches("#position=.+|#-.+")) {
   4.208 +                lines.remove(idx);
   4.209 +            }
   4.210 +        }
   4.211 +        lines.add(impl);
   4.212 +        if (position != Integer.MAX_VALUE) {
   4.213 +            lines.add("#position=" + position);
   4.214 +        }
   4.215 +        for (String exclude : supersedes) {
   4.216 +            lines.add("#-" + exclude);
   4.217 +        }
   4.218 +    }
   4.219 +
   4.220 +    /**
   4.221 +     * @param element a source element
   4.222 +     * @param annotation a type of annotation
   4.223 +     * @return the instance of that annotation on the element, or null if not found
   4.224 +     */
   4.225 +    private AnnotationMirror findAnnotationMirror(Element element, Class<? extends Annotation> annotation) {
   4.226 +        for (AnnotationMirror ann : element.getAnnotationMirrors()) {
   4.227 +            if (processingEnv.getElementUtils().getBinaryName((TypeElement) ann.getAnnotationType().asElement()).
   4.228 +                    contentEquals(annotation.getName())) {
   4.229 +                return ann;
   4.230 +            }
   4.231 +        }
   4.232 +        return null;
   4.233 +    }
   4.234 +
   4.235 +    /**
   4.236 +     * @param annotation an annotation instance (null permitted)
   4.237 +     * @param name the name of an attribute of that annotation
   4.238 +     * @return the corresponding value if found
   4.239 +     */
   4.240 +    private AnnotationValue findAnnotationValue(AnnotationMirror annotation, String name) {
   4.241 +        if (annotation != null) {
   4.242 +            for (Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : annotation.getElementValues().entrySet()) {
   4.243 +                if (entry.getKey().getSimpleName().contentEquals(name)) {
   4.244 +                    return entry.getValue();
   4.245 +                }
   4.246 +            }
   4.247 +        }
   4.248 +        return null;
   4.249 +    }
   4.250 +
   4.251 +    private final boolean verifyServiceProviderSignature(TypeElement clazz, Class<? extends Annotation> annotation) {
   4.252 +        AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
   4.253 +        if (!clazz.getModifiers().contains(Modifier.PUBLIC)) {
   4.254 +            processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must be public", clazz, ann);
   4.255 +            return false;
   4.256 +        }
   4.257 +        if (clazz.getModifiers().contains(Modifier.ABSTRACT)) {
   4.258 +            processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must not be abstract", clazz, ann);
   4.259 +            return false;
   4.260 +        }
   4.261 +        {
   4.262 +            boolean hasDefaultCtor = false;
   4.263 +            for (ExecutableElement constructor : ElementFilter.constructorsIn(clazz.getEnclosedElements())) {
   4.264 +                if (constructor.getModifiers().contains(Modifier.PUBLIC) && constructor.getParameters().isEmpty()) {
   4.265 +                    hasDefaultCtor = true;
   4.266 +                    break;
   4.267 +                }
   4.268 +            }
   4.269 +            if (!hasDefaultCtor) {
   4.270 +                processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must have a public no-argument constructor", clazz, ann);
   4.271 +                return false;
   4.272 +            }
   4.273 +        }
   4.274 +        return true;
   4.275 +    }
   4.276 +
   4.277 +    private void writeServices() {
   4.278 +        for (Map.Entry<ProcessingEnvironment,Map<String,List<String>>> outputFiles : outputFilesByProcessor.entrySet()) {
   4.279 +            for (Map.Entry<String, List<String>> entry : outputFiles.getValue().entrySet()) {
   4.280 +                try {
   4.281 +                    FileObject out = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", entry.getKey(),
   4.282 +                            originatingElementsByProcessor.get(outputFiles.getKey()).get(entry.getKey()).toArray(new Element[0]));
   4.283 +                    OutputStream os = out.openOutputStream();
   4.284 +                    try {
   4.285 +                        PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
   4.286 +                        for (String line : entry.getValue()) {
   4.287 +                            w.println(line);
   4.288 +                        }
   4.289 +                        w.flush();
   4.290 +                        w.close();
   4.291 +                    } finally {
   4.292 +                        os.close();
   4.293 +                    }
   4.294 +                } catch (IOException x) {
   4.295 +                    processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString());
   4.296 +                }
   4.297 +            }
   4.298 +        }
   4.299 +    }
   4.300 +
   4.301 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/openide.util/src/org/netbeans/modules/openide/util/ActiveQueue.java	Fri Jan 22 14:50:19 2010 +0100
     5.3 @@ -0,0 +1,110 @@
     5.4 +package org.netbeans.modules.openide.util;
     5.5 +
     5.6 +import java.lang.ref.Reference;
     5.7 +import java.lang.ref.ReferenceQueue;
     5.8 +import java.util.logging.Level;
     5.9 +import java.util.logging.Logger;
    5.10 +
    5.11 +/**
    5.12 + * Implementation of the active reference queue.
    5.13 + *
    5.14 + * NOTE: This class is duplicated in openide.util and openide.util.lookup to prevent implementation dependency.
    5.15 + */
    5.16 +public final class ActiveQueue extends ReferenceQueue<Object> implements Runnable {
    5.17 +
    5.18 +    private static final Logger LOGGER = Logger.getLogger(ActiveQueue.class.getName().replace('$', '.'));
    5.19 +    private static ActiveQueue activeReferenceQueue;
    5.20 +    
    5.21 +    /** number of known outstanding references */
    5.22 +    private int count;
    5.23 +    private boolean deprecated;
    5.24 +
    5.25 +    ActiveQueue(boolean deprecated) {
    5.26 +        super();
    5.27 +        this.deprecated = deprecated;
    5.28 +    }
    5.29 +
    5.30 +    public static synchronized ReferenceQueue<Object> queue() {
    5.31 +        if (activeReferenceQueue == null) {
    5.32 +            activeReferenceQueue = new ActiveQueue(false);
    5.33 +        }
    5.34 +
    5.35 +        activeReferenceQueue.ping();
    5.36 +
    5.37 +        return activeReferenceQueue;
    5.38 +    }
    5.39 +
    5.40 +    @Override
    5.41 +    public Reference<Object> poll() {
    5.42 +        throw new UnsupportedOperationException();
    5.43 +    }
    5.44 +
    5.45 +    @Override
    5.46 +    public Reference<Object> remove(long timeout) throws IllegalArgumentException, InterruptedException {
    5.47 +        throw new InterruptedException();
    5.48 +    }
    5.49 +
    5.50 +    @Override
    5.51 +    public Reference<Object> remove() throws InterruptedException {
    5.52 +        throw new InterruptedException();
    5.53 +    }
    5.54 +
    5.55 +    public void run() {
    5.56 +        while (true) {
    5.57 +            try {
    5.58 +                Reference<?> ref = super.remove(0);
    5.59 +                LOGGER.finer("dequeued reference");
    5.60 +                if (!(ref instanceof Runnable)) {
    5.61 +                    LOGGER.warning("A reference not implementing runnable has been added to the Utilities.activeReferenceQueue(): " + ref.getClass());
    5.62 +                    continue;
    5.63 +                }
    5.64 +                if (deprecated) {
    5.65 +                    LOGGER.warning("Utilities.ACTIVE_REFERENCE_QUEUE has been deprecated for " + ref.getClass() + " use Utilities.activeReferenceQueue");
    5.66 +                }
    5.67 +                // do the cleanup
    5.68 +                try {
    5.69 +                    ((Runnable) ref).run();
    5.70 +                } catch (ThreadDeath td) {
    5.71 +                    throw td;
    5.72 +                } catch (Throwable t) {
    5.73 +                    // Should not happen.
    5.74 +                    // If it happens, it is a bug in client code, notify!
    5.75 +                    LOGGER.log(Level.WARNING, null, t);
    5.76 +                } finally {
    5.77 +                    // to allow GC
    5.78 +                    ref = null;
    5.79 +                }
    5.80 +            } catch (InterruptedException ex) {
    5.81 +                // Can happen during VM shutdown, it seems. Ignore.
    5.82 +                continue;
    5.83 +            }
    5.84 +            synchronized (this) {
    5.85 +                assert count > 0;
    5.86 +                count--;
    5.87 +                if (count == 0) {
    5.88 +                    // We have processed all we have to process (for now at least).
    5.89 +                    // Could be restarted later if ping() called again.
    5.90 +                    // This could also happen in case someone called queue() once and tried
    5.91 +                    // to use it for several references; in that case run() might never be called on
    5.92 +                    // the later ones to be collected. Can't really protect against that situation.
    5.93 +                    // See issue #86625 for details.
    5.94 +                    LOGGER.fine("stopping thread");
    5.95 +                    break;
    5.96 +                }
    5.97 +            }
    5.98 +        }
    5.99 +    }
   5.100 +
   5.101 +    synchronized void ping() {
   5.102 +        if (count == 0) {
   5.103 +            Thread t = new Thread(this, "Active Reference Queue Daemon");
   5.104 +            t.setPriority(Thread.MIN_PRIORITY);
   5.105 +            t.setDaemon(true);
   5.106 +            t.start();
   5.107 +            LOGGER.fine("starting thread");
   5.108 +        } else {
   5.109 +            LOGGER.finer("enqueuing reference");
   5.110 +        }
   5.111 +        count++;
   5.112 +    }
   5.113 +}