This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 150194
Collapse All | Expand All

(-)a/projectapi/src/org/netbeans/modules/projectapi/LazyLookupProviders.java (+174 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * If you wish your version of this file to be governed by only the CDDL
25
 * or only the GPL Version 2, indicate your decision by adding
26
 * "[Contributor] elects to include this software in this distribution
27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
28
 * single choice of license, a recipient has the option to distribute
29
 * your version of this file under either the CDDL, the GPL Version 2 or
30
 * to extend the choice of license to its licensees as provided above.
31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
32
 * Version 2 license, then the option applies only if the new code is
33
 * made subject to such option by the copyright holder.
34
 *
35
 * Contributor(s):
36
 *
37
 * Portions Copyrighted 2008 Sun Microsystems, Inc.
38
 */
39
40
package org.netbeans.modules.projectapi;
41
42
import java.lang.reflect.Constructor;
43
import java.util.ArrayList;
44
import java.util.List;
45
import java.util.Map;
46
import org.netbeans.api.project.Project;
47
import org.netbeans.spi.project.LookupMerger;
48
import org.netbeans.spi.project.LookupProvider;
49
import org.netbeans.spi.project.ProjectServiceProvider;
50
import org.openide.util.Exceptions;
51
import org.openide.util.Lookup;
52
import org.openide.util.Lookup.Template;
53
import org.openide.util.lookup.Lookups;
54
import org.openide.util.lookup.ProxyLookup;
55
56
/**
57
 * Factory methods for lazy {@link LookupProvider} registration.
58
 */
59
public class LazyLookupProviders {
60
61
    private LazyLookupProviders() {}
62
63
    /**
64
     * @see ProjectServiceProvider
65
     */
66
    public static LookupProvider forProjectServiceProvider(Map<String,Object> attrs) throws ClassNotFoundException {
67
        Class<?> service = Thread.currentThread().getContextClassLoader().loadClass((String) attrs.get("service"));
68
        String implName = (String) attrs.get("class");
69
        return new LazyLookupProvider(service, implName, false);
70
    }
71
72
    /**
73
     * @see org.netbeans.spi.project.LookupMerger.Registration
74
     */
75
    public static LookupProvider forLookupMerger(Map<String,Object> attrs) throws ClassNotFoundException {
76
        Class<?> service = Thread.currentThread().getContextClassLoader().loadClass((String) attrs.get("service"));
77
        String implName = (String) attrs.get("class");
78
        return new LazyLookupProvider(service, implName, true);
79
    }
80
81
    private static class LazyLookupProvider implements LookupProvider {
82
        private final Class<?> service;
83
        private final String implName;
84
        private final boolean merger;
85
        LazyLookupProvider(Class<?> service, String implName, boolean merger) {
86
            this.service = service;
87
            this.implName = implName;
88
            this.merger = merger;
89
        }
90
        public Lookup createAdditionalLookup(final Lookup lkp) {
91
            if (merger) {
92
                // must be a separate method so that it can capture the type parameter
93
                return proxyMergerLookup(service, implName, lkp);
94
            } else {
95
                return new ProxyLookup() {
96
                    boolean inited = false;
97
                    protected @Override void beforeLookup(Template<?> template) {
98
                        System.err.println("XXX template.type=" + template.getType());
99
                        if (!inited && template.getType() == service) {
100
                            inited = true;
101
                            try {
102
                                Class<?> impl = Thread.currentThread().getContextClassLoader().loadClass(implName);
103
                                CONSTRUCTOR: for (Constructor c : impl.getConstructors()) {
104
                                    Class[] params = c.getParameterTypes();
105
                                    if (params.length > 2) {
106
                                        continue;
107
                                    }
108
                                    List<Object> values = new ArrayList<Object>();
109
                                    for (Class param : params) {
110
                                        if (param == Lookup.class) {
111
                                            values.add(lkp);
112
                                        } else if (param == Project.class) {
113
                                            values.add(lkp.lookup(Project.class));
114
                                        } else {
115
                                            continue CONSTRUCTOR;
116
                                        }
117
                                    }
118
                                    Object instance = c.newInstance(values.toArray());
119
                                    service.cast(instance);
120
                                    System.err.println("XXX adding " + instance);
121
                                    setLookups(Lookups.singleton(instance));
122
                                }
123
                            } catch (Exception x) {
124
                                Exceptions.printStackTrace(x);
125
                            }
126
                        }
127
                    }
128
                };
129
            }
130
        }
131
    }
132
133
    private static <T> Lookup proxyMergerLookup(final Class<T> service, final String implName, final Lookup lkp) {
134
        return new ProxyLookup() {
135
            boolean mergerInited = false;
136
            boolean serviceInited = false;
137
            protected @Override void beforeLookup(Template<?> template) {
138
                System.err.println("XXX (merger) template.type=" + template.getType());
139
                if (template.getType() == service) {
140
                    System.err.println("XXX service inited");
141
                    serviceInited = true;
142
                }
143
                if (!mergerInited && template.getType() == LookupMerger.class) {
144
                    mergerInited = true;
145
                    System.err.println("XXX adding proxy merger");
146
                    setLookups(Lookups.fixed(new LookupMerger<T>() {
147
                        public Class<T> getMergeableClass() {
148
                            return service;
149
                        }
150
                        public T merge(Lookup lookup) {
151
                            if (!serviceInited) {
152
                                System.err.println("XXX service not inited yet, not merging anything");
153
                                return null;
154
                            }
155
                            System.err.println("XXX merging " + service);
156
                            try {
157
                                Class<?> impl = Thread.currentThread().getContextClassLoader().loadClass(implName);
158
                                LookupMerger<?> m = (LookupMerger<?>) impl.newInstance();
159
                                if (m.getMergeableClass() != service) {
160
                                    throw new ClassCastException();
161
                                }
162
                                return service.cast(m.merge(lkp));
163
                            } catch (Exception x) {
164
                                Exceptions.printStackTrace(x);
165
                                return null;
166
                            }
167
                        }
168
                    }));
169
                }
170
            }
171
        };
172
    }
173
174
}
(-)a/projectapi/src/org/netbeans/modules/projectapi/LookupProviderAnnotationProcessor.java (-1 / +98 lines)
Lines 39-44 Link Here
39
39
40
package org.netbeans.modules.projectapi;
40
package org.netbeans.modules.projectapi;
41
41
42
import java.util.List;
42
import java.util.Set;
43
import java.util.Set;
43
import javax.annotation.processing.Processor;
44
import javax.annotation.processing.Processor;
44
import javax.annotation.processing.RoundEnvironment;
45
import javax.annotation.processing.RoundEnvironment;
Lines 46-55 Link Here
46
import javax.annotation.processing.SupportedSourceVersion;
47
import javax.annotation.processing.SupportedSourceVersion;
47
import javax.lang.model.SourceVersion;
48
import javax.lang.model.SourceVersion;
48
import javax.lang.model.element.Element;
49
import javax.lang.model.element.Element;
50
import javax.lang.model.element.ExecutableElement;
51
import javax.lang.model.element.Modifier;
49
import javax.lang.model.element.TypeElement;
52
import javax.lang.model.element.TypeElement;
53
import javax.lang.model.element.VariableElement;
54
import javax.lang.model.type.DeclaredType;
55
import javax.lang.model.type.MirroredTypeException;
56
import javax.lang.model.type.TypeMirror;
57
import javax.lang.model.util.ElementFilter;
58
import org.netbeans.api.project.Project;
59
import org.netbeans.spi.project.LookupMerger;
50
import org.netbeans.spi.project.LookupProvider;
60
import org.netbeans.spi.project.LookupProvider;
61
import org.netbeans.spi.project.ProjectServiceProvider;
51
import org.openide.filesystems.annotations.LayerGeneratingProcessor;
62
import org.openide.filesystems.annotations.LayerGeneratingProcessor;
52
import org.openide.filesystems.annotations.LayerGenerationException;
63
import org.openide.filesystems.annotations.LayerGenerationException;
64
import org.openide.util.Lookup;
53
import org.openide.util.lookup.ServiceProvider;
65
import org.openide.util.lookup.ServiceProvider;
54
66
55
/**
67
/**
Lines 58-64 Link Here
58
 */
70
 */
59
@ServiceProvider(service=Processor.class)
71
@ServiceProvider(service=Processor.class)
60
@SupportedSourceVersion(SourceVersion.RELEASE_6)
72
@SupportedSourceVersion(SourceVersion.RELEASE_6)
61
@SupportedAnnotationTypes("org.netbeans.spi.project.LookupProvider.Registration")
73
@SupportedAnnotationTypes({
74
    "org.netbeans.spi.project.LookupProvider.Registration",
75
    "org.netbeans.spi.project.ProjectServiceProvider",
76
    "org.netbeans.spi.project.LookupMerger.Registration"
77
})
62
public class LookupProviderAnnotationProcessor extends LayerGeneratingProcessor {
78
public class LookupProviderAnnotationProcessor extends LayerGeneratingProcessor {
63
79
64
    @Override
80
    @Override
Lines 72-78 Link Here
72
                layer(e).instanceFile("Projects/" + type + "/Lookup", null, LookupProvider.class).write();
88
                layer(e).instanceFile("Projects/" + type + "/Lookup", null, LookupProvider.class).write();
73
            }
89
            }
74
        }
90
        }
91
        for (Element e : roundEnv.getElementsAnnotatedWith(ProjectServiceProvider.class)) {
92
            ProjectServiceProvider psp = e.getAnnotation(ProjectServiceProvider.class);
93
            TypeElement clazz = (TypeElement) e;
94
            TypeMirror service;
95
            try {
96
                psp.service();
97
                assert false;
98
                continue;
99
            } catch (MirroredTypeException x) {
100
                service = x.getTypeMirror();
101
            }
102
            if (!processingEnv.getTypeUtils().isAssignable(clazz.asType(), service)) {
103
                throw new LayerGenerationException("Not assignable to " + service, e);
104
            }
105
            int constructorCount = 0;
106
            CONSTRUCTOR: for (ExecutableElement constructor : ElementFilter.constructorsIn(clazz.getEnclosedElements())) {
107
                if (!constructor.getModifiers().contains(Modifier.PUBLIC)) {
108
                    continue;
109
                }
110
                List<? extends VariableElement> params = constructor.getParameters();
111
                if (params.size() > 2) {
112
                    continue;
113
                }
114
                for (VariableElement param : params) {
115
                    if (!param.asType().equals(processingEnv.getElementUtils().getTypeElement(Project.class.getCanonicalName()).asType()) &&
116
                            !param.asType().equals(processingEnv.getElementUtils().getTypeElement(Lookup.class.getCanonicalName()).asType())) {
117
                        continue CONSTRUCTOR;
118
                    }
119
                }
120
                constructorCount++;
121
            }
122
            if (constructorCount != 1) {
123
                throw new LayerGenerationException("Must have exactly one public constructor optionally taking Project and/or Lookup", e);
124
            }
125
            String binName = processingEnv.getElementUtils().getBinaryName(clazz).toString();
126
            String serviceBinName = processingEnv.getElementUtils().getBinaryName((TypeElement) processingEnv.getTypeUtils().asElement(service)).toString();
127
            for (String type : psp.projectType()) {
128
                layer(e).file("Projects/" + type + "/Lookup/" + binName.replace('.', '-') + ".instance").
129
                        methodvalue("instanceCreate", LazyLookupProviders.class.getName(), "forProjectServiceProvider").
130
                        stringvalue("class", binName).
131
                        stringvalue("service", serviceBinName).
132
                        write();
133
            }
134
        }
135
        for (Element e : roundEnv.getElementsAnnotatedWith(LookupMerger.Registration.class)) {
136
            LookupMerger.Registration lmr = e.getAnnotation(LookupMerger.Registration.class);
137
            TypeElement clazz = (TypeElement) e;
138
            String binName = processingEnv.getElementUtils().getBinaryName(clazz).toString();
139
            DeclaredType service = findLookupMergerType((DeclaredType) clazz.asType());
140
            if (service == null) {
141
                throw new LayerGenerationException("Not assignable to LookupMerger<T> for some T", e);
142
            }
143
            String serviceBinName = processingEnv.getElementUtils().getBinaryName((TypeElement) service.asElement()).toString();
144
            for (String type : lmr.projectType()) {
145
                layer(e).file("Projects/" + type + "/Lookup/" + binName.replace('.', '-') + ".instance").
146
                        methodvalue("instanceCreate", LazyLookupProviders.class.getName(), "forLookupMerger").
147
                        // XXX if supporting also factory methods, could use instanceAttribute here so that attr value is actually a LookupMerger
148
                        stringvalue("class", binName).
149
                        stringvalue("service", serviceBinName).
150
                        write();
151
            }
152
        }
75
        return true;
153
        return true;
76
    }
154
    }
77
155
156
    private DeclaredType findLookupMergerType(DeclaredType t) {
157
        String rawName = processingEnv.getTypeUtils().erasure(t).toString();
158
        if (rawName.equals(LookupMerger.class.getName())) {
159
            List<? extends TypeMirror> args = t.getTypeArguments();
160
            if (args.size() == 1) {
161
                return (DeclaredType) args.get(0);
162
            } else {
163
                return null;
164
            }
165
        }
166
        for (TypeMirror supe : processingEnv.getTypeUtils().directSupertypes(t)) {
167
            DeclaredType result = findLookupMergerType((DeclaredType) supe);
168
            if (result != null) {
169
                return result;
170
            }
171
        }
172
        return null;
173
    }
174
78
}
175
}
(-)a/projectapi/src/org/netbeans/spi/project/LookupMerger.java (+20 lines)
Lines 41-46 Link Here
41
41
42
package org.netbeans.spi.project;
42
package org.netbeans.spi.project;
43
43
44
import java.lang.annotation.ElementType;
45
import java.lang.annotation.Retention;
46
import java.lang.annotation.RetentionPolicy;
47
import java.lang.annotation.Target;
44
import org.openide.util.Lookup;
48
import org.openide.util.Lookup;
45
49
46
/**
50
/**
Lines 72-75 Link Here
72
     */
76
     */
73
    T merge(Lookup lookup);
77
    T merge(Lookup lookup);
74
78
79
    /**
80
     * Registers a lookup merger for some project types.
81
     * The annotated class must be assignable to {@link LookupMerger} with a type parameter.
82
     * @since XXX
83
     */
84
    @Retention(RetentionPolicy.SOURCE)
85
    @Target(ElementType.TYPE) // XXX support static factory methods too
86
    @interface Registration {
87
88
        /**
89
         * Token(s) denoting one or more project types, e.g. {@code "org-netbeans-modules-java-j2seproject"}
90
         */
91
        String[] projectType();
92
93
    }
94
75
}
95
}
(-)a/projectapi/src/org/netbeans/spi/project/ProjectServiceProvider.java (+69 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * If you wish your version of this file to be governed by only the CDDL
25
 * or only the GPL Version 2, indicate your decision by adding
26
 * "[Contributor] elects to include this software in this distribution
27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
28
 * single choice of license, a recipient has the option to distribute
29
 * your version of this file under either the CDDL, the GPL Version 2 or
30
 * to extend the choice of license to its licensees as provided above.
31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
32
 * Version 2 license, then the option applies only if the new code is
33
 * made subject to such option by the copyright holder.
34
 *
35
 * Contributor(s):
36
 *
37
 * Portions Copyrighted 2008 Sun Microsystems, Inc.
38
 */
39
40
package org.netbeans.spi.project;
41
42
import java.lang.annotation.ElementType;
43
import java.lang.annotation.Retention;
44
import java.lang.annotation.RetentionPolicy;
45
import java.lang.annotation.Target;
46
import org.netbeans.api.project.Project;
47
import org.openide.util.Lookup;
48
49
/**
50
 * Like {@link LookupProvider} but registers a single object into a project's lookup.
51
 * The annotated class must have one public constructor, which may take {@link Project} and/or {@link Lookup} parameters.
52
 * @since XXX
53
 */
54
@Retention(RetentionPolicy.SOURCE)
55
@Target(ElementType.TYPE) // XXX support static factory methods too
56
public @interface ProjectServiceProvider {
57
58
    /**
59
     * Service class to be registered.
60
     * The annotated class must be assignable to the service class.
61
     */
62
    Class<?> service();
63
64
    /**
65
     * Token(s) denoting one or more project types, e.g. {@code "org-netbeans-modules-java-j2seproject"}
66
     */
67
    String[] projectType();
68
69
}
(-)a/projectapi/src/org/netbeans/spi/project/support/LookupProviderSupport.java (-1 / +4 lines)
Lines 195-201 Link Here
195
                    continue;
195
                    continue;
196
                }
196
                }
197
                filteredClasses.add(c);
197
                filteredClasses.add(c);
198
                mergedInstances.add(lm.merge(lkp));
198
                Object instance = lm.merge(lkp);
199
                if (instance != null) {
200
                    mergedInstances.add(instance);
201
                }
199
                
202
                
200
                Lookup.Result<?> result = lkp.lookupResult(c);
203
                Lookup.Result<?> result = lkp.lookupResult(c);
201
                
204
                
(-)a/projectapi/test/unit/src/org/netbeans/spi/project/support/LookupProviderSupportTest.java (+65 lines)
Lines 42-50 Link Here
42
package org.netbeans.spi.project.support;
42
package org.netbeans.spi.project.support;
43
43
44
import java.beans.PropertyChangeListener;
44
import java.beans.PropertyChangeListener;
45
import java.util.Arrays;
46
import java.util.Collection;
45
import java.util.Collections;
47
import java.util.Collections;
46
import java.util.HashMap;
48
import java.util.HashMap;
47
import java.util.HashSet;
49
import java.util.HashSet;
50
import java.util.Set;
51
import java.util.SortedSet;
52
import java.util.TreeSet;
48
import javax.swing.Icon;
53
import javax.swing.Icon;
49
import javax.swing.JButton;
54
import javax.swing.JButton;
50
import javax.swing.JCheckBox;
55
import javax.swing.JCheckBox;
Lines 58-63 Link Here
58
import org.netbeans.junit.NbTestCase;
63
import org.netbeans.junit.NbTestCase;
59
import org.netbeans.spi.project.LookupMerger;
64
import org.netbeans.spi.project.LookupMerger;
60
import org.netbeans.spi.project.LookupProvider;
65
import org.netbeans.spi.project.LookupProvider;
66
import org.netbeans.spi.project.ProjectServiceProvider;
61
import org.openide.filesystems.FileObject;
67
import org.openide.filesystems.FileObject;
62
import org.openide.util.Lookup;
68
import org.openide.util.Lookup;
63
import org.openide.util.lookup.AbstractLookup;
69
import org.openide.util.lookup.AbstractLookup;
Lines 285-289 Link Here
285
        public void removePropertyChangeListener(PropertyChangeListener listener) {
291
        public void removePropertyChangeListener(PropertyChangeListener listener) {
286
        }
292
        }
287
    }
293
    }
294
295
    public void testLazyProviders() throws Exception {
296
        assertLoadedClasses();
297
        Lookup all = LookupProviderSupport.createCompositeLookup(Lookups.fixed("hello"), "Projects/x/Lookup");
298
        assertLoadedClasses();
299
        assertEquals("hello", all.lookup(String.class));
300
        assertLoadedClasses();
301
        Collection<? extends Service2> svcs2 = all.lookupAll(Service2.class);
302
        assertLoadedClasses("ServiceImpl2");
303
        assertEquals(1, svcs2.size());
304
        assertEquals(ServiceImpl2.class, svcs2.iterator().next().getClass());
305
        Collection<? extends Service1> svcs1 = all.lookupAll(Service1.class);
306
        assertLoadedClasses("Merger", "ServiceImpl1", "ServiceImpl1a", "ServiceImpl2");
307
        assertEquals(svcs1.toString(), 3, svcs1.size());
308
        assertTrue(svcs1.toString(), svcs1.toString().contains("ServiceImpl1@"));
309
        assertTrue(svcs1.toString(), svcs1.toString().contains("ServiceImpl1a@"));
310
        assertTrue(svcs1.toString(), svcs1.toString().contains("Merge["));
311
    }
312
    private static final Set<Class<?>> loadedClasses = new HashSet<Class<?>>();
313
    private static void assertLoadedClasses(String... names) {
314
        SortedSet<String> actual = new TreeSet<String>();
315
        for (Class<?> clazz : loadedClasses) {
316
            actual.add(clazz.getName().replaceFirst("^\\Q" + LookupProviderSupportTest.class.getName() + "$\\E", ""));
317
        }
318
        assertEquals(Arrays.toString(names), actual.toString());
319
    }
320
    public interface Service1 {}
321
    public interface Service2 {}
322
    @ProjectServiceProvider(projectType="x", service=Service1.class)
323
    public static class ServiceImpl1 implements Service1 {
324
        static {loadedClasses.add(ServiceImpl1.class);}
325
        public ServiceImpl1() {}
326
    }
327
    @ProjectServiceProvider(projectType="x", service=Service1.class)
328
    public static class ServiceImpl1a implements Service1 {
329
        static {loadedClasses.add(ServiceImpl1a.class);}
330
        public ServiceImpl1a() {}
331
    }
332
    @ProjectServiceProvider(projectType="x", service=Service2.class)
333
    public static class ServiceImpl2 implements Service2 {
334
        static {loadedClasses.add(ServiceImpl2.class);}
335
        public ServiceImpl2(Lookup base) {
336
            assertNotNull(base.lookup(String.class));
337
        }
338
    }
339
    @LookupMerger.Registration(projectType="x")
340
    public static class Merger implements LookupMerger<Service1> {
341
        static {loadedClasses.add(Merger.class);}
342
        public Class<Service1> getMergeableClass() {
343
            return Service1.class;
344
        }
345
        public Service1 merge(final Lookup lkp) {
346
            return new Service1() {
347
                public @Override String toString() {
348
                    return "Merge" + lkp.lookupAll(Service1.class);
349
                }
350
            };
351
        }
352
    }
288
    
353
    
289
}
354
}

Return to bug 150194