Issue #20838: declarative registration of URLStreamHandler instances by protocol.
authorJesse Glick <jglick@netbeans.org>
Fri, 30 Oct 2009 13:29:24 -0400
changeset 845ca5ca94c13b6
parent 839 9a12cc406da3
child 846 1497742f45ca
child 847 99da7ea2e16e
child 948 2ef2818a3058
Issue #20838: declarative registration of URLStreamHandler instances by protocol.
openide.util/apichanges.xml
openide.util/nbproject/project.properties
openide.util/src/META-INF/services/java.net.URLStreamHandlerFactory
openide.util/src/META-INF/services/javax.annotation.processing.Processor
openide.util/src/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java
openide.util/src/org/netbeans/modules/openide/util/ProxyURLStreamHandlerFactory.java
openide.util/src/org/netbeans/modules/openide/util/ServiceProviderProcessor.java
openide.util/src/org/netbeans/modules/openide/util/URLStreamHandlerRegistrationProcessor.java
openide.util/src/org/openide/util/URLStreamHandlerRegistration.java
openide.util/test/unit/src/org/netbeans/modules/openide/util/ProxyURLStreamHandlerFactoryTest.java
     1.1 --- a/openide.util/apichanges.xml	Fri Oct 30 10:41:18 2009 -0400
     1.2 +++ b/openide.util/apichanges.xml	Fri Oct 30 13:29:24 2009 -0400
     1.3 @@ -49,6 +49,27 @@
     1.4      <apidef name="actions">Actions API</apidef>
     1.5  </apidefs>
     1.6  <changes>
     1.7 +    <change id="URLStreamHandlerRegistration">
     1.8 +        <api name="util"/>
     1.9 +        <summary>Added <code>@URLStreamHandlerRegistration</code></summary>
    1.10 +        <version major="7" minor="31"/>
    1.11 +        <date day="30" month="10" year="2009"/>
    1.12 +        <author login="jglick"/>
    1.13 +        <compatibility addition="yes">
    1.14 +            <p>
    1.15 +                Modules registering <code>URLStreamHandlerFactory</code>s into
    1.16 +                global lookup will still work but are advised to switch to this
    1.17 +                annotation, which is both easier to use and more efficient.
    1.18 +            </p>
    1.19 +        </compatibility>
    1.20 +        <description>
    1.21 +            <p>
    1.22 +                Introduced an annotation to register URL protocols.
    1.23 +            </p>
    1.24 +        </description>
    1.25 +        <class package="org.openide.util" name="URLStreamHandlerRegistration"/>
    1.26 +        <issue number="20838"/>
    1.27 +    </change>
    1.28      <change id="ImageUtilities.createDisabled">
    1.29          <api name="util"/>
    1.30          <summary><code>ImageUtilities.createDisabledIcon</code> and <code>ImageUtilities.createDisabledImage</code> added.</summary>
     2.1 --- a/openide.util/nbproject/project.properties	Fri Oct 30 10:41:18 2009 -0400
     2.2 +++ b/openide.util/nbproject/project.properties	Fri Oct 30 13:29:24 2009 -0400
     2.3 @@ -42,7 +42,7 @@
     2.4  module.jar.dir=lib
     2.5  cp.extra=${nb_all}/apisupport.harness/external/openjdk-javac-6-b12.jar
     2.6  
     2.7 -spec.version.base=7.30.0
     2.8 +spec.version.base=7.31.0
     2.9  
    2.10  # For XMLSerializer, needed for XMLUtil.write to work w/ namespaces under JDK 1.4:
    2.11  
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/openide.util/src/META-INF/services/java.net.URLStreamHandlerFactory	Fri Oct 30 13:29:24 2009 -0400
     3.3 @@ -0,0 +1,1 @@
     3.4 +org.netbeans.modules.openide.util.ProxyURLStreamHandlerFactory
     4.1 --- a/openide.util/src/META-INF/services/javax.annotation.processing.Processor	Fri Oct 30 10:41:18 2009 -0400
     4.2 +++ b/openide.util/src/META-INF/services/javax.annotation.processing.Processor	Fri Oct 30 13:29:24 2009 -0400
     4.3 @@ -1,1 +1,2 @@
     4.4  org.netbeans.modules.openide.util.ServiceProviderProcessor
     4.5 +org.netbeans.modules.openide.util.URLStreamHandlerRegistrationProcessor
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/openide.util/src/org/netbeans/modules/openide/util/AbstractServiceProviderProcessor.java	Fri Oct 30 13:29:24 2009 -0400
     5.3 @@ -0,0 +1,289 @@
     5.4 +/*
     5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.6 + *
     5.7 + * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
     5.8 + *
     5.9 + * The contents of this file are subject to the terms of either the GNU
    5.10 + * General Public License Version 2 only ("GPL") or the Common
    5.11 + * Development and Distribution License("CDDL") (collectively, the
    5.12 + * "License"). You may not use this file except in compliance with the
    5.13 + * License. You can obtain a copy of the License at
    5.14 + * http://www.netbeans.org/cddl-gplv2.html
    5.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    5.16 + * specific language governing permissions and limitations under the
    5.17 + * License.  When distributing the software, include this License Header
    5.18 + * Notice in each file and include the License file at
    5.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    5.20 + * particular file as subject to the "Classpath" exception as provided
    5.21 + * by Sun in the GPL Version 2 section of the License file that
    5.22 + * accompanied this code. If applicable, add the following below the
    5.23 + * License Header, with the fields enclosed by brackets [] replaced by
    5.24 + * your own identifying information:
    5.25 + * "Portions Copyrighted [year] [name of copyright owner]"
    5.26 + *
    5.27 + * If you wish your version of this file to be governed by only the CDDL
    5.28 + * or only the GPL Version 2, indicate your decision by adding
    5.29 + * "[Contributor] elects to include this software in this distribution
    5.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    5.31 + * single choice of license, a recipient has the option to distribute
    5.32 + * your version of this file under either the CDDL, the GPL Version 2 or
    5.33 + * to extend the choice of license to its licensees as provided above.
    5.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    5.35 + * Version 2 license, then the option applies only if the new code is
    5.36 + * made subject to such option by the copyright holder.
    5.37 + *
    5.38 + * Contributor(s):
    5.39 + *
    5.40 + * Portions Copyrighted 2009 Sun Microsystems, Inc.
    5.41 + */
    5.42 +
    5.43 +package org.netbeans.modules.openide.util;
    5.44 +
    5.45 +import java.io.BufferedReader;
    5.46 +import java.io.FileNotFoundException;
    5.47 +import java.io.IOException;
    5.48 +import java.io.InputStream;
    5.49 +import java.io.InputStreamReader;
    5.50 +import java.io.OutputStream;
    5.51 +import java.io.OutputStreamWriter;
    5.52 +import java.io.PrintWriter;
    5.53 +import java.lang.annotation.Annotation;
    5.54 +import java.util.ArrayList;
    5.55 +import java.util.HashMap;
    5.56 +import java.util.List;
    5.57 +import java.util.Map;
    5.58 +import java.util.Set;
    5.59 +import java.util.WeakHashMap;
    5.60 +import javax.annotation.processing.AbstractProcessor;
    5.61 +import javax.annotation.processing.ProcessingEnvironment;
    5.62 +import javax.annotation.processing.RoundEnvironment;
    5.63 +import javax.lang.model.element.AnnotationMirror;
    5.64 +import javax.lang.model.element.AnnotationValue;
    5.65 +import javax.lang.model.element.Element;
    5.66 +import javax.lang.model.element.ExecutableElement;
    5.67 +import javax.lang.model.element.Modifier;
    5.68 +import javax.lang.model.element.TypeElement;
    5.69 +import javax.lang.model.type.TypeMirror;
    5.70 +import javax.lang.model.util.ElementFilter;
    5.71 +import javax.tools.Diagnostic.Kind;
    5.72 +import javax.tools.FileObject;
    5.73 +import javax.tools.StandardLocation;
    5.74 +
    5.75 +/**
    5.76 + * Infrastructure for generating {@code META-INF/services/*} and
    5.77 + * {@code META-INF/namedservices/*} registrations from annotations.
    5.78 + */
    5.79 +public abstract class AbstractServiceProviderProcessor extends AbstractProcessor {
    5.80 +
    5.81 +    private final Map<ProcessingEnvironment,Map<String,List<String>>> outputFilesByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<String>>>();
    5.82 +    private final Map<ProcessingEnvironment,Map<String,List<Element>>> originatingElementsByProcessor = new WeakHashMap<ProcessingEnvironment,Map<String,List<Element>>>();
    5.83 +    private final Map<TypeElement,Boolean> verifiedClasses = new WeakHashMap<TypeElement,Boolean>();
    5.84 +
    5.85 +    /** For access by subclasses. */
    5.86 +    protected AbstractServiceProviderProcessor() {}
    5.87 +
    5.88 +    public @Override final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    5.89 +        if (roundEnv.errorRaised()) {
    5.90 +            return false;
    5.91 +        }
    5.92 +        if (roundEnv.processingOver()) {
    5.93 +            writeServices();
    5.94 +            outputFilesByProcessor.clear();
    5.95 +            originatingElementsByProcessor.clear();
    5.96 +            return true;
    5.97 +        } else {
    5.98 +            return handleProcess(annotations, roundEnv);
    5.99 +        }
   5.100 +    }
   5.101 +
   5.102 +    /**
   5.103 +     * The regular body of {@link #process}.
   5.104 +     * Called during regular rounds if there are no outstanding errors.
   5.105 +     * In the last round, one of the processors will write out generated registrations.
   5.106 +     * @param annotations as in {@link #process}
   5.107 +     * @param roundEnv as in {@link #process}
   5.108 +     * @return as in {@link #process}
   5.109 +     */
   5.110 +    protected abstract boolean handleProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
   5.111 +
   5.112 +    /**
   5.113 +     * Register a service.
   5.114 +     * If the class does not have an appropriate signature, an error will be printed and the registration skipped.
   5.115 +     * @param clazz the service implementation type
   5.116 +     * @param annotation the (top-level) annotation registering the service, for diagnostic purposes
   5.117 +     * @param type the type to which the implementation must be assignable
   5.118 +     * @param path a path under which to register, or "" if inapplicable
   5.119 +     * @param position a position at which to register, or {@link Integer#MAX_VALUE} to skip
   5.120 +     * @param supersedes possibly empty list of implementation to supersede
   5.121 +     */
   5.122 +    protected final void register(TypeElement clazz, Class<? extends Annotation> annotation,
   5.123 +            TypeMirror type, String path, int position, String[] supersedes) {
   5.124 +        Boolean verify = verifiedClasses.get(clazz);
   5.125 +        if (verify == null) {
   5.126 +            verify = verifyServiceProviderSignature(clazz, annotation);
   5.127 +            verifiedClasses.put(clazz, verify);
   5.128 +        }
   5.129 +        if (!verify) {
   5.130 +            return;
   5.131 +        }
   5.132 +        String impl = processingEnv.getElementUtils().getBinaryName(clazz).toString();
   5.133 +        String xface = processingEnv.getElementUtils().getBinaryName((TypeElement) processingEnv.getTypeUtils().asElement(type)).toString();
   5.134 +        if (!processingEnv.getTypeUtils().isAssignable(clazz.asType(), type)) {
   5.135 +            AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
   5.136 +            processingEnv.getMessager().printMessage(Kind.ERROR, impl + " is not assignable to " + xface,
   5.137 +                    clazz, ann, findAnnotationValue(ann, "service"));
   5.138 +            return;
   5.139 +        }
   5.140 +        processingEnv.getMessager().printMessage(Kind.NOTE,
   5.141 +                impl + " to be registered as a " + xface + (path.length() > 0 ? " under " + path : ""));
   5.142 +        String rsrc = (path.length() > 0 ? "META-INF/namedservices/" + path + "/" : "META-INF/services/") + xface;
   5.143 +        {
   5.144 +            Map<String,List<Element>> originatingElements = originatingElementsByProcessor.get(processingEnv);
   5.145 +            if (originatingElements == null) {
   5.146 +                originatingElements = new HashMap<String,List<Element>>();
   5.147 +                originatingElementsByProcessor.put(processingEnv, originatingElements);
   5.148 +            }
   5.149 +            List<Element> origEls = originatingElements.get(rsrc);
   5.150 +            if (origEls == null) {
   5.151 +                origEls = new ArrayList<Element>();
   5.152 +                originatingElements.put(rsrc, origEls);
   5.153 +            }
   5.154 +            origEls.add(clazz);
   5.155 +        }
   5.156 +        Map<String,List<String>> outputFiles = outputFilesByProcessor.get(processingEnv);
   5.157 +        if (outputFiles == null) {
   5.158 +            outputFiles = new HashMap<String,List<String>>();
   5.159 +            outputFilesByProcessor.put(processingEnv, outputFiles);
   5.160 +        }
   5.161 +        List<String> lines = outputFiles.get(rsrc);
   5.162 +        if (lines == null) {
   5.163 +            lines = new ArrayList<String>();
   5.164 +            try {
   5.165 +                try {
   5.166 +                    FileObject in = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", rsrc);
   5.167 +                    in.openInputStream().close();
   5.168 +                    processingEnv.getMessager().printMessage(Kind.ERROR,
   5.169 +                            "Cannot generate " + rsrc + " because it already exists in sources: " + in.toUri());
   5.170 +                    return;
   5.171 +                } catch (FileNotFoundException x) {
   5.172 +                    // Good.
   5.173 +                }
   5.174 +                try {
   5.175 +                    FileObject in = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", rsrc);
   5.176 +                    InputStream is = in.openInputStream();
   5.177 +                    try {
   5.178 +                        BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
   5.179 +                        String line;
   5.180 +                        while ((line = r.readLine()) != null) {
   5.181 +                            lines.add(line);
   5.182 +                        }
   5.183 +                    } finally {
   5.184 +                        is.close();
   5.185 +                    }
   5.186 +                } catch (FileNotFoundException x) {
   5.187 +                    // OK, created for the first time
   5.188 +                }
   5.189 +            } catch (IOException x) {
   5.190 +                processingEnv.getMessager().printMessage(Kind.ERROR, x.toString());
   5.191 +                return;
   5.192 +            }
   5.193 +            outputFiles.put(rsrc, lines);
   5.194 +        }
   5.195 +        int idx = lines.indexOf(impl);
   5.196 +        if (idx != -1) {
   5.197 +            lines.remove(idx);
   5.198 +            while (lines.size() > idx && lines.get(idx).matches("#position=.+|#-.+")) {
   5.199 +                lines.remove(idx);
   5.200 +            }
   5.201 +        }
   5.202 +        lines.add(impl);
   5.203 +        if (position != Integer.MAX_VALUE) {
   5.204 +            lines.add("#position=" + position);
   5.205 +        }
   5.206 +        for (String exclude : supersedes) {
   5.207 +            lines.add("#-" + exclude);
   5.208 +        }
   5.209 +    }
   5.210 +
   5.211 +    /**
   5.212 +     * @param element a source element
   5.213 +     * @param annotation a type of annotation
   5.214 +     * @return the instance of that annotation on the element, or null if not found
   5.215 +     */
   5.216 +    private AnnotationMirror findAnnotationMirror(Element element, Class<? extends Annotation> annotation) {
   5.217 +        for (AnnotationMirror ann : element.getAnnotationMirrors()) {
   5.218 +            if (processingEnv.getElementUtils().getBinaryName((TypeElement) ann.getAnnotationType().asElement()).
   5.219 +                    contentEquals(annotation.getName())) {
   5.220 +                return ann;
   5.221 +            }
   5.222 +        }
   5.223 +        return null;
   5.224 +    }
   5.225 +
   5.226 +    /**
   5.227 +     * @param annotation an annotation instance (null permitted)
   5.228 +     * @param name the name of an attribute of that annotation
   5.229 +     * @return the corresponding value if found
   5.230 +     */
   5.231 +    private AnnotationValue findAnnotationValue(AnnotationMirror annotation, String name) {
   5.232 +        if (annotation != null) {
   5.233 +            for (Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : annotation.getElementValues().entrySet()) {
   5.234 +                if (entry.getKey().getSimpleName().contentEquals(name)) {
   5.235 +                    return entry.getValue();
   5.236 +                }
   5.237 +            }
   5.238 +        }
   5.239 +        return null;
   5.240 +    }
   5.241 +
   5.242 +    private final boolean verifyServiceProviderSignature(TypeElement clazz, Class<? extends Annotation> annotation) {
   5.243 +        AnnotationMirror ann = findAnnotationMirror(clazz, annotation);
   5.244 +        if (!clazz.getModifiers().contains(Modifier.PUBLIC)) {
   5.245 +            processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must be public", clazz, ann);
   5.246 +            return false;
   5.247 +        }
   5.248 +        if (clazz.getModifiers().contains(Modifier.ABSTRACT)) {
   5.249 +            processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must not be abstract", clazz, ann);
   5.250 +            return false;
   5.251 +        }
   5.252 +        {
   5.253 +            boolean hasDefaultCtor = false;
   5.254 +            for (ExecutableElement constructor : ElementFilter.constructorsIn(clazz.getEnclosedElements())) {
   5.255 +                if (constructor.getModifiers().contains(Modifier.PUBLIC) && constructor.getParameters().isEmpty()) {
   5.256 +                    hasDefaultCtor = true;
   5.257 +                    break;
   5.258 +                }
   5.259 +            }
   5.260 +            if (!hasDefaultCtor) {
   5.261 +                processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must have a public no-argument constructor", clazz, ann);
   5.262 +                return false;
   5.263 +            }
   5.264 +        }
   5.265 +        return true;
   5.266 +    }
   5.267 +
   5.268 +    private void writeServices() {
   5.269 +        for (Map.Entry<ProcessingEnvironment,Map<String,List<String>>> outputFiles : outputFilesByProcessor.entrySet()) {
   5.270 +            for (Map.Entry<String, List<String>> entry : outputFiles.getValue().entrySet()) {
   5.271 +                try {
   5.272 +                    FileObject out = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", entry.getKey(),
   5.273 +                            originatingElementsByProcessor.get(outputFiles.getKey()).get(entry.getKey()).toArray(new Element[0]));
   5.274 +                    OutputStream os = out.openOutputStream();
   5.275 +                    try {
   5.276 +                        PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
   5.277 +                        for (String line : entry.getValue()) {
   5.278 +                            w.println(line);
   5.279 +                        }
   5.280 +                        w.flush();
   5.281 +                        w.close();
   5.282 +                    } finally {
   5.283 +                        os.close();
   5.284 +                    }
   5.285 +                } catch (IOException x) {
   5.286 +                    processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString());
   5.287 +                }
   5.288 +            }
   5.289 +        }
   5.290 +    }
   5.291 +
   5.292 +}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/openide.util/src/org/netbeans/modules/openide/util/ProxyURLStreamHandlerFactory.java	Fri Oct 30 13:29:24 2009 -0400
     6.3 @@ -0,0 +1,83 @@
     6.4 +/*
     6.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     6.6 + *
     6.7 + * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
     6.8 + *
     6.9 + * The contents of this file are subject to the terms of either the GNU
    6.10 + * General Public License Version 2 only ("GPL") or the Common
    6.11 + * Development and Distribution License("CDDL") (collectively, the
    6.12 + * "License"). You may not use this file except in compliance with the
    6.13 + * License. You can obtain a copy of the License at
    6.14 + * http://www.netbeans.org/cddl-gplv2.html
    6.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    6.16 + * specific language governing permissions and limitations under the
    6.17 + * License.  When distributing the software, include this License Header
    6.18 + * Notice in each file and include the License file at
    6.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    6.20 + * particular file as subject to the "Classpath" exception as provided
    6.21 + * by Sun in the GPL Version 2 section of the License file that
    6.22 + * accompanied this code. If applicable, add the following below the
    6.23 + * License Header, with the fields enclosed by brackets [] replaced by
    6.24 + * your own identifying information:
    6.25 + * "Portions Copyrighted [year] [name of copyright owner]"
    6.26 + *
    6.27 + * If you wish your version of this file to be governed by only the CDDL
    6.28 + * or only the GPL Version 2, indicate your decision by adding
    6.29 + * "[Contributor] elects to include this software in this distribution
    6.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    6.31 + * single choice of license, a recipient has the option to distribute
    6.32 + * your version of this file under either the CDDL, the GPL Version 2 or
    6.33 + * to extend the choice of license to its licensees as provided above.
    6.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    6.35 + * Version 2 license, then the option applies only if the new code is
    6.36 + * made subject to such option by the copyright holder.
    6.37 + *
    6.38 + * Contributor(s):
    6.39 + *
    6.40 + * Portions Copyrighted 2009 Sun Microsystems, Inc.
    6.41 + */
    6.42 +
    6.43 +package org.netbeans.modules.openide.util;
    6.44 +
    6.45 +import java.net.URLStreamHandler;
    6.46 +import java.net.URLStreamHandlerFactory;
    6.47 +import java.util.Collection;
    6.48 +import java.util.HashMap;
    6.49 +import java.util.Map;
    6.50 +import org.openide.util.Lookup;
    6.51 +import org.openide.util.LookupEvent;
    6.52 +import org.openide.util.LookupListener;
    6.53 +import org.openide.util.URLStreamHandlerRegistration;
    6.54 +import org.openide.util.lookup.Lookups;
    6.55 +
    6.56 +/**
    6.57 + * @see URLStreamHandlerRegistration
    6.58 + */
    6.59 +public final class ProxyURLStreamHandlerFactory implements URLStreamHandlerFactory {
    6.60 +
    6.61 +    /** prevents GC only */
    6.62 +    private final Map<String, Lookup.Result<URLStreamHandler>> results = new HashMap<String, Lookup.Result<URLStreamHandler>>();
    6.63 +    private final Map<String, URLStreamHandler> handlers = new HashMap<String, URLStreamHandler>();
    6.64 +
    6.65 +    /** for lookup */
    6.66 +    public ProxyURLStreamHandlerFactory() {}
    6.67 +
    6.68 +    public synchronized URLStreamHandler createURLStreamHandler(final String protocol) {
    6.69 +        if (!results.containsKey(protocol)) {
    6.70 +            final Lookup.Result<URLStreamHandler> result = Lookups.forPath(URLStreamHandlerRegistrationProcessor.REGISTRATION_PREFIX + protocol).lookupResult(URLStreamHandler.class);
    6.71 +            LookupListener listener = new LookupListener() {
    6.72 +
    6.73 +                public void resultChanged(LookupEvent ev) {
    6.74 +                    synchronized (ProxyURLStreamHandlerFactory.this) {
    6.75 +                        Collection<? extends URLStreamHandler> instances = result.allInstances();
    6.76 +                        handlers.put(protocol, instances.isEmpty() ? null : instances.iterator().next());
    6.77 +                    }
    6.78 +                }
    6.79 +            };
    6.80 +            result.addLookupListener(listener);
    6.81 +            listener.resultChanged(null);
    6.82 +            results.put(protocol, result);
    6.83 +        }
    6.84 +        return handlers.get(protocol);
    6.85 +    }
    6.86 +}
     7.1 --- a/openide.util/src/org/netbeans/modules/openide/util/ServiceProviderProcessor.java	Fri Oct 30 10:41:18 2009 -0400
     7.2 +++ b/openide.util/src/org/netbeans/modules/openide/util/ServiceProviderProcessor.java	Fri Oct 30 13:29:24 2009 -0400
     7.3 @@ -39,240 +39,58 @@
     7.4  
     7.5  package org.netbeans.modules.openide.util;
     7.6  
     7.7 -import java.io.BufferedReader;
     7.8 -import java.io.FileNotFoundException;
     7.9 -import java.io.IOException;
    7.10 -import java.io.InputStream;
    7.11 -import java.io.InputStreamReader;
    7.12 -import java.io.OutputStream;
    7.13 -import java.io.OutputStreamWriter;
    7.14 -import java.io.PrintWriter;
    7.15  import java.lang.annotation.Annotation;
    7.16 -import java.util.ArrayList;
    7.17  import java.util.Collection;
    7.18  import java.util.Collections;
    7.19 -import java.util.HashMap;
    7.20  import java.util.LinkedList;
    7.21  import java.util.List;
    7.22 -import java.util.Map;
    7.23  import java.util.Set;
    7.24 -import javax.annotation.processing.AbstractProcessor;
    7.25  import javax.annotation.processing.Completion;
    7.26  import javax.annotation.processing.RoundEnvironment;
    7.27  import javax.annotation.processing.SupportedAnnotationTypes;
    7.28  import javax.annotation.processing.SupportedSourceVersion;
    7.29  import javax.lang.model.SourceVersion;
    7.30  import javax.lang.model.element.AnnotationMirror;
    7.31 -import javax.lang.model.element.AnnotationValue;
    7.32  import javax.lang.model.element.Element;
    7.33  import javax.lang.model.element.ExecutableElement;
    7.34 -import javax.lang.model.element.Modifier;
    7.35  import javax.lang.model.element.TypeElement;
    7.36  import javax.lang.model.type.MirroredTypeException;
    7.37  import javax.lang.model.type.TypeKind;
    7.38  import javax.lang.model.type.TypeMirror;
    7.39 -import javax.lang.model.util.ElementFilter;
    7.40 -import javax.tools.Diagnostic.Kind;
    7.41 -import javax.tools.FileObject;
    7.42 -import javax.tools.StandardLocation;
    7.43  import org.openide.util.lookup.ServiceProvider;
    7.44  import org.openide.util.lookup.ServiceProviders;
    7.45  
    7.46  @SupportedSourceVersion(SourceVersion.RELEASE_6)
    7.47  @SupportedAnnotationTypes({"org.openide.util.lookup.ServiceProvider", "org.openide.util.lookup.ServiceProviders"})
    7.48 -public class ServiceProviderProcessor extends AbstractProcessor {
    7.49 +public class ServiceProviderProcessor extends AbstractServiceProviderProcessor {
    7.50  
    7.51      /** public for ServiceLoader */
    7.52      public ServiceProviderProcessor() {}
    7.53  
    7.54 -    private final Map<String, List<String>> outputFiles = new HashMap<String,List<String>>();
    7.55 -    private final Map<String, List<Element>> originatingElements = new HashMap<String,List<Element>>();
    7.56 -
    7.57 -    @Override
    7.58 -    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    7.59 -        if (roundEnv.errorRaised()) {
    7.60 -            return false;
    7.61 +    protected boolean handleProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    7.62 +        for (Element el : roundEnv.getElementsAnnotatedWith(ServiceProvider.class)) {
    7.63 +            TypeElement clazz = (TypeElement) el;
    7.64 +            ServiceProvider sp = clazz.getAnnotation(ServiceProvider.class);
    7.65 +            register(clazz, ServiceProvider.class, sp);
    7.66          }
    7.67 -        if (roundEnv.processingOver()) {
    7.68 -            writeServices();
    7.69 -            return false;
    7.70 -        } else {
    7.71 -            for (Element el : roundEnv.getElementsAnnotatedWith(ServiceProvider.class)) {
    7.72 -                TypeElement clazz = (TypeElement) el;
    7.73 -                if (!verifyServiceProviderSignature(clazz)) {
    7.74 -                    continue;
    7.75 -                }
    7.76 -                ServiceProvider sp = clazz.getAnnotation(ServiceProvider.class);
    7.77 -                register(clazz, sp);
    7.78 +        for (Element el : roundEnv.getElementsAnnotatedWith(ServiceProviders.class)) {
    7.79 +            TypeElement clazz = (TypeElement) el;
    7.80 +            ServiceProviders spp = clazz.getAnnotation(ServiceProviders.class);
    7.81 +            for (ServiceProvider sp : spp.value()) {
    7.82 +                register(clazz, ServiceProviders.class, sp);
    7.83              }
    7.84 -            for (Element el : roundEnv.getElementsAnnotatedWith(ServiceProviders.class)) {
    7.85 -                TypeElement clazz = (TypeElement) el;
    7.86 -                if (!verifyServiceProviderSignature(clazz)) {
    7.87 -                    continue;
    7.88 -                }
    7.89 -                ServiceProviders spp = clazz.getAnnotation(ServiceProviders.class);
    7.90 -                for (ServiceProvider sp : spp.value()) {
    7.91 -                    register(clazz, sp);
    7.92 -                }
    7.93 -            }
    7.94 -            return true;
    7.95          }
    7.96 +        return true;
    7.97      }
    7.98  
    7.99 -    private void register(TypeElement clazz, ServiceProvider svc) {
   7.100 -        TypeMirror type;
   7.101 +    private void register(TypeElement clazz, Class<? extends Annotation> annotation, ServiceProvider svc) {
   7.102          try {
   7.103              svc.service();
   7.104              assert false;
   7.105              return;
   7.106          } catch (MirroredTypeException e) {
   7.107 -            type = e.getTypeMirror();
   7.108 +            register(clazz, annotation, e.getTypeMirror(), svc.path(), svc.position(), svc.supersedes());
   7.109          }
   7.110 -        String impl = processingEnv.getElementUtils().getBinaryName(clazz).toString();
   7.111 -        String xface = processingEnv.getElementUtils().getBinaryName((TypeElement) processingEnv.getTypeUtils().asElement(type)).toString();
   7.112 -        if (!processingEnv.getTypeUtils().isAssignable(clazz.asType(), type)) {
   7.113 -            AnnotationMirror ann = findAnnotationMirror(clazz, ServiceProvider.class);
   7.114 -            processingEnv.getMessager().printMessage(Kind.ERROR, impl + " is not assignable to " + xface,
   7.115 -                    clazz, ann, findAnnotationValue(ann, "service"));
   7.116 -            return;
   7.117 -        }
   7.118 -        processingEnv.getMessager().printMessage(Kind.NOTE, impl + " to be registered as a " + xface);
   7.119 -        String rsrc = (svc.path().length() > 0 ? "META-INF/namedservices/" + svc.path() + "/" : "META-INF/services/") + xface;
   7.120 -        {
   7.121 -            List<Element> origEls = originatingElements.get(rsrc);
   7.122 -            if (origEls == null) {
   7.123 -                origEls = new ArrayList<Element>();
   7.124 -                originatingElements.put(rsrc, origEls);
   7.125 -            }
   7.126 -            origEls.add(clazz);
   7.127 -        }
   7.128 -        List<String> lines = outputFiles.get(rsrc);
   7.129 -        if (lines == null) {
   7.130 -            lines = new ArrayList<String>();
   7.131 -            try {
   7.132 -                try {
   7.133 -                    FileObject in = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", rsrc);
   7.134 -                    in.openInputStream().close();
   7.135 -                    processingEnv.getMessager().printMessage(Kind.ERROR,
   7.136 -                            "Cannot generate " + rsrc + " because it already exists in sources: " + in.toUri());
   7.137 -                    return;
   7.138 -                } catch (FileNotFoundException x) {
   7.139 -                    // Good.
   7.140 -                }
   7.141 -                try {
   7.142 -                    FileObject in = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", rsrc);
   7.143 -                    InputStream is = in.openInputStream();
   7.144 -                    try {
   7.145 -                        BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
   7.146 -                        String line;
   7.147 -                        while ((line = r.readLine()) != null) {
   7.148 -                            lines.add(line);
   7.149 -                        }
   7.150 -                    } finally {
   7.151 -                        is.close();
   7.152 -                    }
   7.153 -                } catch (FileNotFoundException x) {
   7.154 -                    // OK, created for the first time
   7.155 -                }
   7.156 -            } catch (IOException x) {
   7.157 -                processingEnv.getMessager().printMessage(Kind.ERROR, x.toString());
   7.158 -                return;
   7.159 -            }
   7.160 -            outputFiles.put(rsrc, lines);
   7.161 -        }
   7.162 -        int idx = lines.indexOf(impl);
   7.163 -        if (idx != -1) {
   7.164 -            lines.remove(idx);
   7.165 -            while (lines.size() > idx && lines.get(idx).matches("#position=.+|#-.+")) {
   7.166 -                lines.remove(idx);
   7.167 -            }
   7.168 -        }
   7.169 -        lines.add(impl);
   7.170 -        if (svc.position() != Integer.MAX_VALUE) {
   7.171 -            lines.add("#position=" + svc.position());
   7.172 -        }
   7.173 -        for (String exclude : svc.supersedes()) {
   7.174 -            lines.add("#-" + exclude);
   7.175 -        }
   7.176 -    }
   7.177 -
   7.178 -    private boolean verifyServiceProviderSignature(TypeElement clazz) {
   7.179 -        AnnotationMirror ann = findAnnotationMirror(clazz, ServiceProvider.class);
   7.180 -        if (!clazz.getModifiers().contains(Modifier.PUBLIC)) {
   7.181 -            processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must be public", clazz, ann);
   7.182 -            return false;
   7.183 -        }
   7.184 -        if (clazz.getModifiers().contains(Modifier.ABSTRACT)) {
   7.185 -            processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must not be abstract", clazz, ann);
   7.186 -            return false;
   7.187 -        }
   7.188 -        {
   7.189 -            boolean hasDefaultCtor = false;
   7.190 -            for (ExecutableElement constructor : ElementFilter.constructorsIn(clazz.getEnclosedElements())) {
   7.191 -                if (constructor.getModifiers().contains(Modifier.PUBLIC) && constructor.getParameters().isEmpty()) {
   7.192 -                    hasDefaultCtor = true;
   7.193 -                    break;
   7.194 -                }
   7.195 -            }
   7.196 -            if (!hasDefaultCtor) {
   7.197 -                processingEnv.getMessager().printMessage(Kind.ERROR, clazz + " must have a public no-argument constructor", clazz, ann);
   7.198 -                return false;
   7.199 -            }
   7.200 -        }
   7.201 -        return true;
   7.202 -    }
   7.203 -
   7.204 -    private void writeServices() {
   7.205 -        for (Map.Entry<String, List<String>> entry : outputFiles.entrySet()) {
   7.206 -            try {
   7.207 -                FileObject out = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", entry.getKey(),
   7.208 -                        originatingElements.get(entry.getKey()).toArray(new Element[0]));
   7.209 -                OutputStream os = out.openOutputStream();
   7.210 -                try {
   7.211 -                    PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
   7.212 -                    for (String line : entry.getValue()) {
   7.213 -                        w.println(line);
   7.214 -                    }
   7.215 -                    w.flush();
   7.216 -                    w.close();
   7.217 -                } finally {
   7.218 -                    os.close();
   7.219 -                }
   7.220 -            } catch (IOException x) {
   7.221 -                processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString());
   7.222 -            }
   7.223 -        }
   7.224 -    }
   7.225 -
   7.226 -    /**
   7.227 -     * @param element a source element
   7.228 -     * @param annotation a type of annotation
   7.229 -     * @return the instance of that annotation on the element, or null if not found
   7.230 -     */
   7.231 -    private AnnotationMirror findAnnotationMirror(Element element, Class<? extends Annotation> annotation) {
   7.232 -        for (AnnotationMirror ann : element.getAnnotationMirrors()) {
   7.233 -            if (processingEnv.getElementUtils().getBinaryName((TypeElement) ann.getAnnotationType().asElement()).
   7.234 -                    contentEquals(annotation.getName())) {
   7.235 -                return ann;
   7.236 -            }
   7.237 -        }
   7.238 -        return null;
   7.239 -    }
   7.240 -
   7.241 -    /**
   7.242 -     * @param annotation an annotation instance (null permitted)
   7.243 -     * @param name the name of an attribute of that annotation
   7.244 -     * @return the corresponding value if found
   7.245 -     */
   7.246 -    private AnnotationValue findAnnotationValue(AnnotationMirror annotation, String name) {
   7.247 -        if (annotation != null) {
   7.248 -            for (Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : annotation.getElementValues().entrySet()) {
   7.249 -                if (entry.getKey().getSimpleName().contentEquals(name)) {
   7.250 -                    return entry.getValue();
   7.251 -                }
   7.252 -            }
   7.253 -        }
   7.254 -        return null;
   7.255      }
   7.256  
   7.257      @Override
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/openide.util/src/org/netbeans/modules/openide/util/URLStreamHandlerRegistrationProcessor.java	Fri Oct 30 13:29:24 2009 -0400
     8.3 @@ -0,0 +1,78 @@
     8.4 +/*
     8.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     8.6 + *
     8.7 + * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
     8.8 + *
     8.9 + * The contents of this file are subject to the terms of either the GNU
    8.10 + * General Public License Version 2 only ("GPL") or the Common
    8.11 + * Development and Distribution License("CDDL") (collectively, the
    8.12 + * "License"). You may not use this file except in compliance with the
    8.13 + * License. You can obtain a copy of the License at
    8.14 + * http://www.netbeans.org/cddl-gplv2.html
    8.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    8.16 + * specific language governing permissions and limitations under the
    8.17 + * License.  When distributing the software, include this License Header
    8.18 + * Notice in each file and include the License file at
    8.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    8.20 + * particular file as subject to the "Classpath" exception as provided
    8.21 + * by Sun in the GPL Version 2 section of the License file that
    8.22 + * accompanied this code. If applicable, add the following below the
    8.23 + * License Header, with the fields enclosed by brackets [] replaced by
    8.24 + * your own identifying information:
    8.25 + * "Portions Copyrighted [year] [name of copyright owner]"
    8.26 + *
    8.27 + * If you wish your version of this file to be governed by only the CDDL
    8.28 + * or only the GPL Version 2, indicate your decision by adding
    8.29 + * "[Contributor] elects to include this software in this distribution
    8.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    8.31 + * single choice of license, a recipient has the option to distribute
    8.32 + * your version of this file under either the CDDL, the GPL Version 2 or
    8.33 + * to extend the choice of license to its licensees as provided above.
    8.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    8.35 + * Version 2 license, then the option applies only if the new code is
    8.36 + * made subject to such option by the copyright holder.
    8.37 + *
    8.38 + * Contributor(s):
    8.39 + *
    8.40 + * Portions Copyrighted 2009 Sun Microsystems, Inc.
    8.41 + */
    8.42 +
    8.43 +package org.netbeans.modules.openide.util;
    8.44 +
    8.45 +import java.net.URLStreamHandler;
    8.46 +import java.util.Set;
    8.47 +import javax.annotation.processing.RoundEnvironment;
    8.48 +import javax.annotation.processing.SupportedAnnotationTypes;
    8.49 +import javax.annotation.processing.SupportedSourceVersion;
    8.50 +import javax.lang.model.SourceVersion;
    8.51 +import javax.lang.model.element.Element;
    8.52 +import javax.lang.model.element.TypeElement;
    8.53 +import javax.lang.model.type.TypeMirror;
    8.54 +import org.openide.util.URLStreamHandlerRegistration;
    8.55 +
    8.56 +@SupportedSourceVersion(SourceVersion.RELEASE_6)
    8.57 +@SupportedAnnotationTypes({
    8.58 +    "org.openide.util.URLStreamHandlerRegistration"
    8.59 +})
    8.60 +public class URLStreamHandlerRegistrationProcessor extends AbstractServiceProviderProcessor {
    8.61 +
    8.62 +    public static final String REGISTRATION_PREFIX = "URLStreamHandler/"; // NOI18N
    8.63 +
    8.64 +    /** public for ServiceLoader */
    8.65 +    public URLStreamHandlerRegistrationProcessor() {}
    8.66 +
    8.67 +    protected boolean handleProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    8.68 +        for (Element el : roundEnv.getElementsAnnotatedWith(URLStreamHandlerRegistration.class)) {
    8.69 +            TypeElement clazz = (TypeElement) el;
    8.70 +            URLStreamHandlerRegistration r = clazz.getAnnotation(URLStreamHandlerRegistration.class);
    8.71 +            TypeMirror type = processingEnv.getTypeUtils().getDeclaredType(
    8.72 +                    processingEnv.getElementUtils().getTypeElement(URLStreamHandler.class.getName()));
    8.73 +            for (String protocol : r.protocol()) {
    8.74 +                register(clazz, URLStreamHandlerRegistration.class, type,
    8.75 +                        REGISTRATION_PREFIX + protocol, r.position(), new String[0]);
    8.76 +            }
    8.77 +        }
    8.78 +        return true;
    8.79 +    }
    8.80 +
    8.81 +}
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/openide.util/src/org/openide/util/URLStreamHandlerRegistration.java	Fri Oct 30 13:29:24 2009 -0400
     9.3 @@ -0,0 +1,78 @@
     9.4 +/*
     9.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     9.6 + *
     9.7 + * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
     9.8 + *
     9.9 + * The contents of this file are subject to the terms of either the GNU
    9.10 + * General Public License Version 2 only ("GPL") or the Common
    9.11 + * Development and Distribution License("CDDL") (collectively, the
    9.12 + * "License"). You may not use this file except in compliance with the
    9.13 + * License. You can obtain a copy of the License at
    9.14 + * http://www.netbeans.org/cddl-gplv2.html
    9.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    9.16 + * specific language governing permissions and limitations under the
    9.17 + * License.  When distributing the software, include this License Header
    9.18 + * Notice in each file and include the License file at
    9.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    9.20 + * particular file as subject to the "Classpath" exception as provided
    9.21 + * by Sun in the GPL Version 2 section of the License file that
    9.22 + * accompanied this code. If applicable, add the following below the
    9.23 + * License Header, with the fields enclosed by brackets [] replaced by
    9.24 + * your own identifying information:
    9.25 + * "Portions Copyrighted [year] [name of copyright owner]"
    9.26 + *
    9.27 + * If you wish your version of this file to be governed by only the CDDL
    9.28 + * or only the GPL Version 2, indicate your decision by adding
    9.29 + * "[Contributor] elects to include this software in this distribution
    9.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    9.31 + * single choice of license, a recipient has the option to distribute
    9.32 + * your version of this file under either the CDDL, the GPL Version 2 or
    9.33 + * to extend the choice of license to its licensees as provided above.
    9.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    9.35 + * Version 2 license, then the option applies only if the new code is
    9.36 + * made subject to such option by the copyright holder.
    9.37 + *
    9.38 + * Contributor(s):
    9.39 + *
    9.40 + * Portions Copyrighted 2009 Sun Microsystems, Inc.
    9.41 + */
    9.42 +
    9.43 +package org.openide.util;
    9.44 +
    9.45 +import java.lang.annotation.ElementType;
    9.46 +import java.lang.annotation.Retention;
    9.47 +import java.lang.annotation.RetentionPolicy;
    9.48 +import java.lang.annotation.Target;
    9.49 +import java.net.URL;
    9.50 +import java.net.URLStreamHandler;
    9.51 +import java.net.URLStreamHandlerFactory;
    9.52 +
    9.53 +/**
    9.54 + * Replacement for {@link URLStreamHandlerFactory} within the NetBeans platform.
    9.55 + * (The JVM only permits one global factory to be set at a time,
    9.56 + * whereas various independent modules may wish to register handlers.)
    9.57 + * May be placed on a {@link URLStreamHandler} implementation to register it.
    9.58 + * Your handler will be loaded and used if and when a URL of a matching protocol is created.
    9.59 + * <p>A {@link URLStreamHandlerFactory} which uses these registrations may be found in {@link Lookup#getDefault}.
    9.60 + * This factory is active whenever the module system is loaded.
    9.61 + * You may also wish to call {@link URL#setURLStreamHandlerFactory}
    9.62 + * from a unit test or otherwise without the module system active.
    9.63 + * @since org.openide.util 7.31
    9.64 + */
    9.65 +@Retention(RetentionPolicy.SOURCE)
    9.66 +@Target(ElementType.TYPE)
    9.67 +public @interface URLStreamHandlerRegistration {
    9.68 +
    9.69 +    /**
    9.70 +     * URL protocol(s) which are handled.
    9.71 +     * {@link URLStreamHandler#openConnection} will be called with a matching {@link URL#getProtocol}.
    9.72 +     */
    9.73 +    String[] protocol();
    9.74 +
    9.75 +    /**
    9.76 +     * An optional position in which to register this handler relative to others.
    9.77 +     * The lowest-numbered handler is used in favor of any others, including unnumbered handlers.
    9.78 +     */
    9.79 +    int position() default Integer.MAX_VALUE;
    9.80 +
    9.81 +}
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/openide.util/test/unit/src/org/netbeans/modules/openide/util/ProxyURLStreamHandlerFactoryTest.java	Fri Oct 30 13:29:24 2009 -0400
    10.3 @@ -0,0 +1,70 @@
    10.4 +/*
    10.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    10.6 + *
    10.7 + * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
    10.8 + *
    10.9 + * The contents of this file are subject to the terms of either the GNU
   10.10 + * General Public License Version 2 only ("GPL") or the Common
   10.11 + * Development and Distribution License("CDDL") (collectively, the
   10.12 + * "License"). You may not use this file except in compliance with the
   10.13 + * License. You can obtain a copy of the License at
   10.14 + * http://www.netbeans.org/cddl-gplv2.html
   10.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   10.16 + * specific language governing permissions and limitations under the
   10.17 + * License.  When distributing the software, include this License Header
   10.18 + * Notice in each file and include the License file at
   10.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
   10.20 + * particular file as subject to the "Classpath" exception as provided
   10.21 + * by Sun in the GPL Version 2 section of the License file that
   10.22 + * accompanied this code. If applicable, add the following below the
   10.23 + * License Header, with the fields enclosed by brackets [] replaced by
   10.24 + * your own identifying information:
   10.25 + * "Portions Copyrighted [year] [name of copyright owner]"
   10.26 + *
   10.27 + * If you wish your version of this file to be governed by only the CDDL
   10.28 + * or only the GPL Version 2, indicate your decision by adding
   10.29 + * "[Contributor] elects to include this software in this distribution
   10.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   10.31 + * single choice of license, a recipient has the option to distribute
   10.32 + * your version of this file under either the CDDL, the GPL Version 2 or
   10.33 + * to extend the choice of license to its licensees as provided above.
   10.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   10.35 + * Version 2 license, then the option applies only if the new code is
   10.36 + * made subject to such option by the copyright holder.
   10.37 + *
   10.38 + * Contributor(s):
   10.39 + *
   10.40 + * Portions Copyrighted 2009 Sun Microsystems, Inc.
   10.41 + */
   10.42 +
   10.43 +package org.netbeans.modules.openide.util;
   10.44 +
   10.45 +import java.io.IOException;
   10.46 +import java.net.URL;
   10.47 +import java.net.URLConnection;
   10.48 +import java.net.URLStreamHandler;
   10.49 +import java.net.URLStreamHandlerFactory;
   10.50 +import org.netbeans.junit.NbTestCase;
   10.51 +import org.openide.util.URLStreamHandlerRegistration;
   10.52 +
   10.53 +public class ProxyURLStreamHandlerFactoryTest extends NbTestCase {
   10.54 +
   10.55 +    public ProxyURLStreamHandlerFactoryTest(String n) {
   10.56 +        super(n);
   10.57 +    }
   10.58 +
   10.59 +    public void testURLStreamHandlerRegistration() throws Exception {
   10.60 +        URLStreamHandlerFactory factory = new ProxyURLStreamHandlerFactory();
   10.61 +        assertEquals(MyHandler.class, factory.createURLStreamHandler("stuff").getClass());
   10.62 +        assertEquals(MyHandler.class, factory.createURLStreamHandler("stuff").getClass());
   10.63 +        assertNull(factory.createURLStreamHandler("whatever"));
   10.64 +    }
   10.65 +
   10.66 +    @URLStreamHandlerRegistration(protocol="stuff")
   10.67 +    public static class MyHandler extends URLStreamHandler {
   10.68 +        protected URLConnection openConnection(URL u) throws IOException {
   10.69 +            throw new IOException("unsupported");
   10.70 +        }
   10.71 +    }
   10.72 +
   10.73 +}