# HG changeset patch # User Jesse Glick # Date 1256571565 14400 Issue #20838: declarative registration of URLStreamHandler instances by protocol. diff --git a/core.startup/src/org/netbeans/core/startup/NbURLStreamHandlerFactory.java b/core.startup/src/org/netbeans/core/startup/NbResourceStreamHandler.java rename from core.startup/src/org/netbeans/core/startup/NbURLStreamHandlerFactory.java rename to core.startup/src/org/netbeans/core/startup/NbResourceStreamHandler.java --- a/core.startup/src/org/netbeans/core/startup/NbURLStreamHandlerFactory.java +++ b/core.startup/src/org/netbeans/core/startup/NbResourceStreamHandler.java @@ -47,47 +47,18 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; -import java.net.URLStreamHandlerFactory; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; -import org.openide.filesystems.FileUtil; import org.openide.util.Lookup; import org.openide.util.NbBundle; +import org.openide.util.URLStreamHandlerRegistration; -/** - * Proxying stream handler factory. Currently searches Lookup for registered - * factories and delegates to them. But #20838 suggests using JNDI instead, - * in which case registering them via Lookup would be deprecated. - * @author Jesse Glick - */ -@org.openide.util.lookup.ServiceProvider(service=java.net.URLStreamHandlerFactory.class) -public final class NbURLStreamHandlerFactory implements URLStreamHandlerFactory { - - /** public for lookup */ - public NbURLStreamHandlerFactory() {} - - public URLStreamHandler createURLStreamHandler(String protocol) { - if (protocol.equals("nbfs")) { // NOI18N - return FileUtil.nbfsURLStreamHandler(); - } - - if (protocol.equals(NbResourceStreamHandler.PROTOCOL_SYSTEM_RESOURCE) || - protocol.equals(NbResourceStreamHandler.PROTOCOL_LOCALIZED_SYSTEM_RESOURCE)) { - return new NbResourceStreamHandler(); - } - - return null; - } - /** Stream handler for internal resource-based URLs. - * Copied with modifications from org.openide.execution - that version is now - * deprecated and handles only deprecated protocols. * @author Jesse Glick */ - private static final class NbResourceStreamHandler extends URLStreamHandler { - - public NbResourceStreamHandler() {} + @URLStreamHandlerRegistration(protocol={NbResourceStreamHandler.PROTOCOL_SYSTEM_RESOURCE, NbResourceStreamHandler.PROTOCOL_LOCALIZED_SYSTEM_RESOURCE}) + public final class NbResourceStreamHandler extends URLStreamHandler { public static final String PROTOCOL_SYSTEM_RESOURCE = "nbres"; // NOI18N public static final String PROTOCOL_LOCALIZED_SYSTEM_RESOURCE = "nbresloc"; // NOI18N @@ -133,7 +104,7 @@ if (resource.length() > 0 && resource.charAt(0) == '/') { // NOI18N resource = resource.substring(1); } else { - Logger.getLogger(NbURLStreamHandlerFactory.class.getName()).log(Level.WARNING, "URL path should begin with a slash: " + url); + Logger.getLogger(NbResourceStreamHandler.class.getName()).log(Level.WARNING, "URL path should begin with a slash: " + url); } ClassLoader loader = Lookup.getDefault().lookup(ClassLoader.class); URL target; @@ -166,7 +137,7 @@ target = t1; } if (target == null) { - throw new IOException(NbBundle.getMessage(NbURLStreamHandlerFactory.class, "EXC_nbres_cannot_connect", url)); + throw new IOException(NbBundle.getMessage(NbResourceStreamHandler.class, "EXC_nbres_cannot_connect", url)); } real = target.openConnection(); real.connect(); @@ -267,5 +238,3 @@ } } - -} diff --git a/core.startup/src/org/netbeans/core/startup/layers/NbinstURLStreamHandlerFactory.java b/core.startup/src/org/netbeans/core/startup/layers/NbinstURLStreamHandler.java rename from core.startup/src/org/netbeans/core/startup/layers/NbinstURLStreamHandlerFactory.java rename to core.startup/src/org/netbeans/core/startup/layers/NbinstURLStreamHandler.java --- a/core.startup/src/org/netbeans/core/startup/layers/NbinstURLStreamHandlerFactory.java +++ b/core.startup/src/org/netbeans/core/startup/layers/NbinstURLStreamHandler.java @@ -47,43 +47,19 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; -import java.net.URLStreamHandlerFactory; import java.net.UnknownServiceException; import org.openide.filesystems.FileObject; import org.openide.util.Exceptions; +import org.openide.util.URLStreamHandlerRegistration; /** - * StreamHandlerFactory for nbinst protocol + * URLStreamHandler for nbinst protocol */ -@org.openide.util.lookup.ServiceProvider(service=java.net.URLStreamHandlerFactory.class) -public class NbinstURLStreamHandlerFactory implements URLStreamHandlerFactory { +@URLStreamHandlerRegistration(protocol=NbinstURLMapper.PROTOCOL) +public class NbinstURLStreamHandler extends URLStreamHandler { - /** - * Creates URLStreamHandler for nbinst protocol - * @param protocol - * @return NbinstURLStreamHandler if the protocol is nbinst otherwise null - */ - public URLStreamHandler createURLStreamHandler(String protocol) { - if (NbinstURLMapper.PROTOCOL.equals(protocol)) { - return new NbinstURLStreamHandler (); - } - return null; - } - - /** - * URLStreamHandler for nbinst protocol - */ - private static class NbinstURLStreamHandler extends URLStreamHandler { - - /** - * Creates URLConnection for URL with nbinst protocol. - * @param u URL for which the URLConnection should be created - * @return URLConnection - * @throws IOException - */ - protected URLConnection openConnection(URL u) throws IOException { - return new NbinstURLConnection (u); - } + protected URLConnection openConnection(URL u) throws IOException { + return new NbinstURLConnection(u); } /** URLConnection for URL with nbinst protocol. diff --git a/core.startup/test/unit/src/org/netbeans/core/startup/layers/NbinstURLMapperTest.java b/core.startup/test/unit/src/org/netbeans/core/startup/layers/NbinstURLMapperTest.java --- a/core.startup/test/unit/src/org/netbeans/core/startup/layers/NbinstURLMapperTest.java +++ b/core.startup/test/unit/src/org/netbeans/core/startup/layers/NbinstURLMapperTest.java @@ -77,7 +77,6 @@ MockServices.setServices( TestInstalledFileLocator.class, - NbinstURLStreamHandlerFactory.class, NbinstURLMapper.class); org.netbeans.core.startup.Main.initializeURLFactory (); diff --git a/ide.ergonomics/src/org/netbeans/modules/ide/ergonomics/fod/FoDURLStreamHandlerFactory.java b/ide.ergonomics/src/org/netbeans/modules/ide/ergonomics/fod/FoDURLStreamHandler.java rename from ide.ergonomics/src/org/netbeans/modules/ide/ergonomics/fod/FoDURLStreamHandlerFactory.java rename to ide.ergonomics/src/org/netbeans/modules/ide/ergonomics/fod/FoDURLStreamHandler.java --- a/ide.ergonomics/src/org/netbeans/modules/ide/ergonomics/fod/FoDURLStreamHandlerFactory.java +++ b/ide.ergonomics/src/org/netbeans/modules/ide/ergonomics/fod/FoDURLStreamHandler.java @@ -46,24 +46,16 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; -import java.net.URLStreamHandlerFactory; import org.openide.util.NbBundle; -import org.openide.util.lookup.ServiceProvider; +import org.openide.util.URLStreamHandlerRegistration; /** * * @author Jaroslav Tulach */ -@ServiceProvider(service=URLStreamHandlerFactory.class, position=99999) -public class FoDURLStreamHandlerFactory implements URLStreamHandlerFactory { +@URLStreamHandlerRegistration(protocol="ergoloc") +public class FoDURLStreamHandler extends URLStreamHandler { - public URLStreamHandler createURLStreamHandler(String protocol) { - if (protocol.equals("ergoloc")) { - return new FoDSH(); - } - return null; - } - private static final class FoDSH extends URLStreamHandler { @Override protected URLConnection openConnection(URL u) throws IOException { URL orig = new URL("nbresloc", u.getHost(), u.getPort(), u.getFile()); // NOI18N @@ -79,7 +71,7 @@ throw new IOException(); } String head = new String(arr, 0, len, "UTF-8"); // NOI18N - String newHead = head.replaceFirst("<[bB][oO][dD][yY]>", NbBundle.getMessage(FoDSH.class, "MSG_NotEnabled")); // NOI18N + String newHead = head.replaceFirst("<[bB][oO][dD][yY]>", NbBundle.getMessage(FoDURLStreamHandler.class, "MSG_NotEnabled")); // NOI18N ByteArrayInputStream headIS = new ByteArrayInputStream(newHead.getBytes("UTF-8")); // NOI18N final SequenceInputStream seq = new SequenceInputStream(headIS, is); @@ -97,5 +89,4 @@ }; } - } } diff --git a/javahelp/src/org/netbeans/modules/javahelp/NbDocsStreamHandler.java b/javahelp/src/org/netbeans/modules/javahelp/NbDocsStreamHandler.java --- a/javahelp/src/org/netbeans/modules/javahelp/NbDocsStreamHandler.java +++ b/javahelp/src/org/netbeans/modules/javahelp/NbDocsStreamHandler.java @@ -50,22 +50,13 @@ import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; +import org.openide.util.URLStreamHandlerRegistration; /** Handler & connection cribbed from NbResourceStreamHandler. * @author Jesse Glick */ -final class NbDocsStreamHandler extends URLStreamHandler { - - @org.openide.util.lookup.ServiceProvider(service=java.net.URLStreamHandlerFactory.class) - public static final class Factory implements URLStreamHandlerFactory { - public URLStreamHandler createURLStreamHandler(String protocol) { - if (protocol.equals("nbdocs")) { // NOI18N - return new NbDocsStreamHandler(); - } else { - return null; - } - } - } +@URLStreamHandlerRegistration(protocol="nbdocs") +public final class NbDocsStreamHandler extends URLStreamHandler { /** Make a URLConnection for nbdocs: URLs. * @param u the URL diff --git a/o.n.bootstrap/src/org/netbeans/ProxyURLStreamHandlerFactory.java b/o.n.bootstrap/src/org/netbeans/ProxyURLStreamHandlerFactory.java --- a/o.n.bootstrap/src/org/netbeans/ProxyURLStreamHandlerFactory.java +++ b/o.n.bootstrap/src/org/netbeans/ProxyURLStreamHandlerFactory.java @@ -53,6 +53,7 @@ import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; +import org.openide.util.URLStreamHandlerRegistration; import org.openide.util.Utilities; /** @@ -154,7 +155,8 @@ public void resultChanged(LookupEvent ev) { Collection c = r.allInstances(); synchronized (this) { - handlers = c.toArray(new URLStreamHandlerFactory[0]); + handlers = c.toArray(new URLStreamHandlerFactory[c.size() + 1]); + handlers[c.size()] = new URLStreamHandlerRegistration.ProxyURLStreamHandlerFactory(); } } diff --git a/openide.filesystems/src/org/netbeans/modules/openide/filesystems/RecognizeInstanceFiles.java b/openide.filesystems/src/org/netbeans/modules/openide/filesystems/RecognizeInstanceFiles.java --- a/openide.filesystems/src/org/netbeans/modules/openide/filesystems/RecognizeInstanceFiles.java +++ b/openide.filesystems/src/org/netbeans/modules/openide/filesystems/RecognizeInstanceFiles.java @@ -69,6 +69,17 @@ public Lookup create(String path) { + if (path.equals("URLStreamHandler/nbres/")) { // NOI18N + // Need to avoid a stack overflow during initialization. + ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class); + if (l == null) { + l = Thread.currentThread().getContextClassLoader(); + if (l == null) { + l = RecognizeInstanceFiles.class.getClassLoader(); + } + } + return Lookups.metaInfServices(l, "META-INF/namedservices/URLStreamHandler/nbres/"); // NOI18N + } return new OverFiles(path); } diff --git a/openide.filesystems/src/org/openide/filesystems/FileURL.java b/openide.filesystems/src/org/openide/filesystems/FileURL.java --- a/openide.filesystems/src/org/openide/filesystems/FileURL.java +++ b/openide.filesystems/src/org/openide/filesystems/FileURL.java @@ -54,6 +54,7 @@ import java.net.URLStreamHandler; import java.net.UnknownServiceException; import java.security.Permission; +import org.openide.util.URLStreamHandlerRegistration; /** Special URL connection directly accessing an internal file object. * @@ -65,20 +66,15 @@ /** Default implemenatation of handler for this type of URL. */ - static URLStreamHandler HANDLER = new URLStreamHandler() { - /** - * @param u - URL to open connection to. - * @return new URLConnection. - */ - public URLConnection openConnection(URL u) - throws IOException { - return new FileURL(u); - } - - protected synchronized InetAddress getHostAddress(URL u) { - return null; - } - }; + @URLStreamHandlerRegistration(protocol=PROTOCOL) + public static class Handler extends URLStreamHandler { + public URLConnection openConnection(URL u) throws IOException { + return new FileURL(u); + } + protected @Override synchronized InetAddress getHostAddress(URL u) { + return null; + } + } /** 1 URLConnection == 1 InputSteam*/ InputStream iStream = null; diff --git a/openide.filesystems/src/org/openide/filesystems/FileUtil.java b/openide.filesystems/src/org/openide/filesystems/FileUtil.java --- a/openide.filesystems/src/org/openide/filesystems/FileUtil.java +++ b/openide.filesystems/src/org/openide/filesystems/FileUtil.java @@ -1492,16 +1492,11 @@ } /** - * Construct a stream handler that handles the nbfs URL protocol - * used for accessing file objects directly. - * This method is not intended for module use; only the core - * should need to call it. - * Modules probably need only use {@link URLMapper} to create and decode such - * URLs. - * @since 3.17 + * @deprecated No longer used. */ + @Deprecated public static URLStreamHandler nbfsURLStreamHandler() { - return FileURL.HANDLER; + return new FileURL.Handler(); } /** Recursively checks whether the file is underneath the folder. It checks whether diff --git a/openide.filesystems/src/org/openide/filesystems/NbfsUtil.java b/openide.filesystems/src/org/openide/filesystems/NbfsUtil.java --- a/openide.filesystems/src/org/openide/filesystems/NbfsUtil.java +++ b/openide.filesystems/src/org/openide/filesystems/NbfsUtil.java @@ -80,7 +80,7 @@ new PrivilegedExceptionAction() { public URL run() throws Exception { // #30397: the fsPart name cannot be null - return new URL(FileURL.PROTOCOL, host, -1, file, FileURL.HANDLER); // NOI18N + return new URL(FileURL.PROTOCOL, host, -1, file, new FileURL.Handler()); } } ); diff --git a/openide.util/src/META-INF/services/javax.annotation.processing.Processor b/openide.util/src/META-INF/services/javax.annotation.processing.Processor --- a/openide.util/src/META-INF/services/javax.annotation.processing.Processor +++ b/openide.util/src/META-INF/services/javax.annotation.processing.Processor @@ -1,1 +1,2 @@ org.netbeans.modules.openide.util.ServiceProviderProcessor +org.netbeans.modules.openide.util.URLStreamHandlerRegistrationProcessor diff --git a/openide.util/src/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java b/openide.util/src/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java new file mode 100644 --- /dev/null +++ b/openide.util/src/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java @@ -0,0 +1,289 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 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 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.openide.util; + +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.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +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; + +/** + * Infrastructure for generating {@code META-INF/services/*} and + * {@code META-INF/namedservices/*} registrations from annotations. + */ +public abstract class AbstractServiceProviderProcessor extends AbstractProcessor { + + private final Map>> outputFilesByProcessor = new WeakHashMap>>(); + private final Map>> originatingElementsByProcessor = new WeakHashMap>>(); + private final Map verifiedClasses = new WeakHashMap(); + + /** For access by subclasses. */ + protected AbstractServiceProviderProcessor() {} + + public @Override final boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.errorRaised()) { + return false; + } + if (roundEnv.processingOver()) { + writeServices(); + outputFilesByProcessor.clear(); + originatingElementsByProcessor.clear(); + return true; + } else { + return handleProcess(annotations, roundEnv); + } + } + + /** + * The regular body of {@link #process}. + * Called during regular rounds if there are no outstanding errors. + * In the last round, one of the processors will write out generated registrations. + * @param annotations as in {@link #process} + * @param roundEnv as in {@link #process} + * @return as in {@link #process} + */ + protected abstract boolean handleProcess(Set annotations, RoundEnvironment roundEnv); + + /** + * Register a service. + * If the class does not have an appropriate signature, an error will be printed and the registration skipped. + * @param clazz the service implementation type + * @param annotation the (top-level) annotation registering the service, for diagnostic purposes + * @param type the type to which the implementation must be assignable + * @param path a path under which to register, or "" if inapplicable + * @param position a position at which to register, or {@link Integer#MAX_VALUE} to skip + * @param supersedes possibly empty list of implementation to supersede + */ + protected final void register(TypeElement clazz, Class annotation, + TypeMirror type, String path, int position, String[] supersedes) { + Boolean verify = verifiedClasses.get(clazz); + if (verify == null) { + verify = verifyServiceProviderSignature(clazz, annotation); + verifiedClasses.put(clazz, verify); + } + if (!verify) { + return; + } + 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)) { + AnnotationMirror ann = findAnnotationMirror(clazz, annotation); + processingEnv.getMessager().printMessage(Kind.ERROR, impl + " is not assignable to " + xface, + clazz, ann, findAnnotationValue(ann, "service")); + return; + } + processingEnv.getMessager().printMessage(Kind.NOTE, + impl + " to be registered as a " + xface + (path.length() > 0 ? " under " + path : "")); + String rsrc = (path.length() > 0 ? "META-INF/namedservices/" + path + "/" : "META-INF/services/") + xface; + { + Map> originatingElements = originatingElementsByProcessor.get(processingEnv); + if (originatingElements == null) { + originatingElements = new HashMap>(); + originatingElementsByProcessor.put(processingEnv, originatingElements); + } + List origEls = originatingElements.get(rsrc); + if (origEls == null) { + origEls = new ArrayList(); + originatingElements.put(rsrc, origEls); + } + origEls.add(clazz); + } + Map> outputFiles = outputFilesByProcessor.get(processingEnv); + if (outputFiles == null) { + outputFiles = new HashMap>(); + outputFilesByProcessor.put(processingEnv, outputFiles); + } + 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 (position != Integer.MAX_VALUE) { + lines.add("#position=" + position); + } + for (String exclude : supersedes) { + lines.add("#-" + exclude); + } + } + + /** + * @param element a source element + * @param annotation a type of annotation + * @return the instance of that annotation on the element, or null if not found + */ + private AnnotationMirror findAnnotationMirror(Element element, Class annotation) { + for (AnnotationMirror ann : element.getAnnotationMirrors()) { + if (processingEnv.getElementUtils().getBinaryName((TypeElement) ann.getAnnotationType().asElement()). + contentEquals(annotation.getName())) { + return ann; + } + } + return null; + } + + /** + * @param annotation an annotation instance (null permitted) + * @param name the name of an attribute of that annotation + * @return the corresponding value if found + */ + private AnnotationValue findAnnotationValue(AnnotationMirror annotation, String name) { + if (annotation != null) { + for (Map.Entry entry : annotation.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().contentEquals(name)) { + return entry.getValue(); + } + } + } + return null; + } + + private final boolean verifyServiceProviderSignature(TypeElement clazz, Class annotation) { + AnnotationMirror ann = findAnnotationMirror(clazz, annotation); + if (!clazz.getModifiers().contains(Modifier.PUBLIC)) { + processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must be public", clazz, ann); + return false; + } + if (clazz.getModifiers().contains(Modifier.ABSTRACT)) { + processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must not be abstract", clazz, ann); + return false; + } + { + 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", clazz, ann); + return false; + } + } + return true; + } + + private void writeServices() { + for (Map.Entry>> outputFiles : outputFilesByProcessor.entrySet()) { + for (Map.Entry> entry : outputFiles.getValue().entrySet()) { + try { + FileObject out = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", entry.getKey(), + originatingElementsByProcessor.get(outputFiles.getKey()).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()); + } + } + } + } + +} diff --git a/openide.util/src/org/netbeans/modules/openide/util/ServiceProviderProcessor.java b/openide.util/src/org/netbeans/modules/openide/util/ServiceProviderProcessor.java --- a/openide.util/src/org/netbeans/modules/openide/util/ServiceProviderProcessor.java +++ b/openide.util/src/org/netbeans/modules/openide/util/ServiceProviderProcessor.java @@ -39,240 +39,58 @@ package org.netbeans.modules.openide.util; -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.lang.annotation.Annotation; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; -import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Completion; 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.MirroredTypeException; import javax.lang.model.type.TypeKind; 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.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; @SupportedSourceVersion(SourceVersion.RELEASE_6) @SupportedAnnotationTypes({"org.openide.util.lookup.ServiceProvider", "org.openide.util.lookup.ServiceProviders"}) -public class ServiceProviderProcessor extends AbstractProcessor { +public class ServiceProviderProcessor extends AbstractServiceProviderProcessor { /** public for ServiceLoader */ public ServiceProviderProcessor() {} - private final Map> outputFiles = new HashMap>(); - private final Map> originatingElements = new HashMap>(); - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - if (roundEnv.errorRaised()) { - return false; + protected boolean handleProcess(Set annotations, RoundEnvironment roundEnv) { + for (Element el : roundEnv.getElementsAnnotatedWith(ServiceProvider.class)) { + TypeElement clazz = (TypeElement) el; + ServiceProvider sp = clazz.getAnnotation(ServiceProvider.class); + register(clazz, ServiceProvider.class, sp); } - if (roundEnv.processingOver()) { - writeServices(); - return false; - } else { - for (Element el : roundEnv.getElementsAnnotatedWith(ServiceProvider.class)) { - TypeElement clazz = (TypeElement) el; - if (!verifyServiceProviderSignature(clazz)) { - continue; - } - ServiceProvider sp = clazz.getAnnotation(ServiceProvider.class); - register(clazz, sp); + for (Element el : roundEnv.getElementsAnnotatedWith(ServiceProviders.class)) { + TypeElement clazz = (TypeElement) el; + ServiceProviders spp = clazz.getAnnotation(ServiceProviders.class); + for (ServiceProvider sp : spp.value()) { + register(clazz, ServiceProviders.class, sp); } - for (Element el : roundEnv.getElementsAnnotatedWith(ServiceProviders.class)) { - TypeElement clazz = (TypeElement) el; - if (!verifyServiceProviderSignature(clazz)) { - continue; - } - ServiceProviders spp = clazz.getAnnotation(ServiceProviders.class); - for (ServiceProvider sp : spp.value()) { - register(clazz, sp); - } - } - return true; } + return true; } - private void register(TypeElement clazz, ServiceProvider svc) { - TypeMirror type; + private void register(TypeElement clazz, Class annotation, ServiceProvider svc) { try { svc.service(); assert false; return; } catch (MirroredTypeException e) { - type = e.getTypeMirror(); + register(clazz, annotation, e.getTypeMirror(), svc.path(), svc.position(), svc.supersedes()); } - 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)) { - AnnotationMirror ann = findAnnotationMirror(clazz, ServiceProvider.class); - processingEnv.getMessager().printMessage(Kind.ERROR, impl + " is not assignable to " + xface, - clazz, ann, findAnnotationValue(ann, "service")); - return; - } - processingEnv.getMessager().printMessage(Kind.NOTE, impl + " to be registered as a " + xface); - 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); - } - } - - private boolean verifyServiceProviderSignature(TypeElement clazz) { - AnnotationMirror ann = findAnnotationMirror(clazz, ServiceProvider.class); - if (!clazz.getModifiers().contains(Modifier.PUBLIC)) { - processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must be public", clazz, ann); - return false; - } - if (clazz.getModifiers().contains(Modifier.ABSTRACT)) { - processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must not be abstract", clazz, ann); - return false; - } - { - 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", clazz, ann); - return false; - } - } - return true; - } - - private void writeServices() { - 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()); - } - } - } - - /** - * @param element a source element - * @param annotation a type of annotation - * @return the instance of that annotation on the element, or null if not found - */ - private AnnotationMirror findAnnotationMirror(Element element, Class annotation) { - for (AnnotationMirror ann : element.getAnnotationMirrors()) { - if (processingEnv.getElementUtils().getBinaryName((TypeElement) ann.getAnnotationType().asElement()). - contentEquals(annotation.getName())) { - return ann; - } - } - return null; - } - - /** - * @param annotation an annotation instance (null permitted) - * @param name the name of an attribute of that annotation - * @return the corresponding value if found - */ - private AnnotationValue findAnnotationValue(AnnotationMirror annotation, String name) { - if (annotation != null) { - for (Map.Entry entry : annotation.getElementValues().entrySet()) { - if (entry.getKey().getSimpleName().contentEquals(name)) { - return entry.getValue(); - } - } - } - return null; } @Override diff --git a/openide.util/src/org/netbeans/modules/openide/util/URLStreamHandlerRegistrationProcessor.java b/openide.util/src/org/netbeans/modules/openide/util/URLStreamHandlerRegistrationProcessor.java new file mode 100644 --- /dev/null +++ b/openide.util/src/org/netbeans/modules/openide/util/URLStreamHandlerRegistrationProcessor.java @@ -0,0 +1,78 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 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 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.openide.util; + +import java.net.URLStreamHandler; +import java.util.Set; +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.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import org.openide.util.URLStreamHandlerRegistration; + +@SupportedSourceVersion(SourceVersion.RELEASE_6) +@SupportedAnnotationTypes({ + "org.openide.util.URLStreamHandlerRegistration" +}) +public class URLStreamHandlerRegistrationProcessor extends AbstractServiceProviderProcessor { + + public static final String REGISTRATION_PREFIX = "URLStreamHandler/"; // NOI18N + + /** public for ServiceLoader */ + public URLStreamHandlerRegistrationProcessor() {} + + protected boolean handleProcess(Set annotations, RoundEnvironment roundEnv) { + for (Element el : roundEnv.getElementsAnnotatedWith(URLStreamHandlerRegistration.class)) { + TypeElement clazz = (TypeElement) el; + URLStreamHandlerRegistration r = clazz.getAnnotation(URLStreamHandlerRegistration.class); + TypeMirror type = processingEnv.getTypeUtils().getDeclaredType( + processingEnv.getElementUtils().getTypeElement(URLStreamHandler.class.getName())); + for (String protocol : r.protocol()) { + register(clazz, URLStreamHandlerRegistration.class, type, + REGISTRATION_PREFIX + protocol, r.position(), new String[0]); + } + } + return true; + } + +} diff --git a/openide.util/src/org/openide/util/URLStreamHandlerRegistration.java b/openide.util/src/org/openide/util/URLStreamHandlerRegistration.java new file mode 100644 --- /dev/null +++ b/openide.util/src/org/openide/util/URLStreamHandlerRegistration.java @@ -0,0 +1,114 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 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 2009 Sun Microsystems, Inc. + */ + +package org.openide.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.net.URL; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.netbeans.modules.openide.util.URLStreamHandlerRegistrationProcessor; +import org.openide.util.lookup.Lookups; + +/** + * Replacement for {@link URLStreamHandlerFactory} within the NetBeans platform. + * (The JVM only permits one factory, whereas various independent modules may wish to register handlers.) + * May be placed on a {@link URLStreamHandler} implementation to register it. + * @since XXX + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface URLStreamHandlerRegistration { + + /** + * URL protocol(s) which are handled. + * {@link URLStreamHandler#openConnection} will be called with a matching {@link URL#getProtocol}. + */ + String[] protocol(); + + /** + * An optional position in which to register this handler relative to others. + * The lowest-numbered handler is used in favor of any others, including unnumbered handlers. + */ + int position() default Integer.MAX_VALUE; + + // cannot be a constant: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6857918 + /** + * Factory which uses these registrations. + * This factory is active whenever the module system is loaded. + * You may also wish to call {@link URL#setURLStreamHandlerFactory} + * from a unit test or otherwise without the module system active. + */ + final class ProxyURLStreamHandlerFactory implements URLStreamHandlerFactory { + + /** prevents GC only */ + private final Map> results = new HashMap>(); + private final Map handlers = new HashMap(); + + /** Create a new proxy factory. */ + public ProxyURLStreamHandlerFactory() {} + + public synchronized URLStreamHandler createURLStreamHandler(final String protocol) { + if (!results.containsKey(protocol)) { + final Lookup.Result result = Lookups.forPath( + URLStreamHandlerRegistrationProcessor.REGISTRATION_PREFIX + protocol).lookupResult(URLStreamHandler.class); + LookupListener listener = new LookupListener() { + public void resultChanged(LookupEvent ev) { + synchronized (ProxyURLStreamHandlerFactory.this) { + Collection instances = result.allInstances(); + handlers.put(protocol, instances.isEmpty() ? null : instances.iterator().next()); + } + } + }; + result.addLookupListener(listener); + listener.resultChanged(null); + results.put(protocol, result); + } + return handlers.get(protocol); + } + + } + +} diff --git a/openide.util/test/unit/src/org/openide/util/URLStreamHandlerRegistrationTest.java b/openide.util/test/unit/src/org/openide/util/URLStreamHandlerRegistrationTest.java new file mode 100644 --- /dev/null +++ b/openide.util/test/unit/src/org/openide/util/URLStreamHandlerRegistrationTest.java @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 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 2009 Sun Microsystems, Inc. + */ + +package org.openide.util; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import org.netbeans.junit.NbTestCase; + +public class URLStreamHandlerRegistrationTest extends NbTestCase { + + public URLStreamHandlerRegistrationTest(String n) { + super(n); + } + + public void testURLStreamHandlerRegistration() throws Exception { + URLStreamHandlerFactory factory = new URLStreamHandlerRegistration.ProxyURLStreamHandlerFactory(); + assertEquals(MyHandler.class, factory.createURLStreamHandler("stuff").getClass()); + assertEquals(MyHandler.class, factory.createURLStreamHandler("stuff").getClass()); + assertNull(factory.createURLStreamHandler("whatever")); + } + + @URLStreamHandlerRegistration(protocol="stuff") + public static class MyHandler extends URLStreamHandler { + protected URLConnection openConnection(URL u) throws IOException { + throw new IOException("unsupported"); + } + } + +}