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 83343
Collapse All | Expand All

(-)projectapi/apichanges.xml (+20 lines)
Lines 81-86 Link Here
81
    <!-- ACTUAL CHANGES BEGIN HERE: -->
81
    <!-- ACTUAL CHANGES BEGIN HERE: -->
82
82
83
    <changes>
83
    <changes>
84
        <change id="lookup-provider">
85
            <api name="general"/>
86
            <summary>Added support for composing project's lookup from multiple sources.</summary>
87
            <version major="1" minor="12"/>
88
            <date day="18" month="9" year="2006"/>
89
            <author login="mkleint"/>
90
            <compatibility addition="yes" binary="compatible" deletion="no" deprecation="no" modification="no" semantic="compatible" source="compatible">
91
            </compatibility>
92
            <description>
93
	        <p>
94
                    Added interfaces and support classes that can be used to compose the project's lookup
95
                    from multiple 3rd party sources. Framework for creating merged instances included. 
96
                    <code>LookupMerger</code> implementation for<code>Sources</code> added.
97
		</p>
98
            </description>
99
            <class package="org.netbeans.spi.project" name="LookupProvider"/>
100
            <class package="org.netbeans.spi.project" name="LookupMerger"/>
101
            <class package="org.netbeans.spi.project.support" name="LookupProviderSupport"/>
102
            <issue number="83343"/>
103
        </change>
84
        
104
        
85
        <change id="ProjectConfigurationProvider">
105
        <change id="ProjectConfigurationProvider">
86
            <api name="general"/>
106
            <api name="general"/>
(-)projectapi/arch.xml (+56 lines)
Lines 936-939 Link Here
936
 </answer>
936
 </answer>
937
937
938
938
939
940
941
942
<!--
943
        <question id="arch-where" when="impl">
944
            Where one can find sources for your module?
945
            <hint>
946
                Please provide link to the CVS web client at
947
                http://www.netbeans.org/download/source_browse.html
948
                or just use tag defaultanswer generate='here'
949
            </hint>
950
        </question>
951
-->
952
 <answer id="arch-where">
953
  <defaultanswer generate='here' />
954
 </answer>
955
956
957
958
<!--
959
        <question id="compat-deprecation" when="init">
960
            How the introduction of your project influences functionality
961
            provided by previous version of the product?
962
            <hint>
963
            If you are planning to deprecate/remove/change any existing APIs,
964
            list them here accompanied with the reason explaining why you
965
            are doing so.
966
            </hint>
967
        </question>
968
-->
969
 <answer id="compat-deprecation">
970
  <p>
971
   XXX no answer for compat-deprecation
972
  </p>
973
 </answer>
974
975
976
977
<!--
978
        <question id="exec-ant-tasks" when="impl">
979
            Do you define or register any ant tasks that other can use?
980
            
981
            <hint>
982
            If you provide an ant task that users can use, you need to be very
983
            careful about its syntax and behaviour, as it most likely forms an
984
	          API for end users and as there is a lot of end users, their reaction
985
            when such API gets broken can be pretty strong.
986
            </hint>
987
        </question>
988
-->
989
 <answer id="exec-ant-tasks">
990
  <p>
991
   XXX no answer for exec-ant-tasks
992
  </p>
993
 </answer>
994
939
</api-answers>
995
</api-answers>
(-)projectapi/manifest.mf (-1 / +1 lines)
Lines 1-5 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.projectapi/1
2
OpenIDE-Module: org.netbeans.modules.projectapi/1
3
OpenIDE-Module-Specification-Version: 1.11
3
OpenIDE-Module-Specification-Version: 1.12
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties
5
5
(-)projectapi/nbproject/project.properties (-1 / +1 lines)
Lines 24-27 Link Here
24
javadoc.apichanges=${basedir}/apichanges.xml
24
javadoc.apichanges=${basedir}/apichanges.xml
25
25
26
# masterfs needed for the usual reasons; core.jar needed for ArchiveURLMapper:
26
# masterfs needed for the usual reasons; core.jar needed for ArchiveURLMapper:
27
test.unit.run.cp.extra=${core/startup.dir}/core/core.jar:${core/bootstrap.dir}/lib/boot.jar
27
#test.unit.run.cp.extra=${core/startup.dir}/core/core.jar:${core/bootstrap.dir}/lib/boot.jar
(-)projectapi/nbproject/project.xml (-18 / +34 lines)
Lines 40-45 Link Here
40
                    </run-dependency>
40
                    </run-dependency>
41
                </dependency>
41
                </dependency>
42
                <dependency>
42
                <dependency>
43
                    <code-name-base>org.openide.loaders</code-name-base>
44
                    <build-prerequisite/>
45
                    <compile-dependency/>
46
                    <run-dependency>
47
                        <specification-version>5.11</specification-version>
48
                    </run-dependency>
49
                </dependency>
50
                <dependency>
51
                    <code-name-base>org.openide.nodes</code-name-base>
52
                    <build-prerequisite/>
53
                    <compile-dependency/>
54
                    <run-dependency>
55
                        <specification-version>6.9</specification-version>
56
                    </run-dependency>
57
                </dependency>
58
                <dependency>
43
                    <code-name-base>org.openide.util</code-name-base>
59
                    <code-name-base>org.openide.util</code-name-base>
44
                    <build-prerequisite/>
60
                    <build-prerequisite/>
45
                    <compile-dependency/>
61
                    <compile-dependency/>
Lines 48-71 Link Here
48
                    </run-dependency>
64
                    </run-dependency>
49
                </dependency>
65
                </dependency>
50
            </module-dependencies>
66
            </module-dependencies>
51
          <test-dependencies>
67
            <test-dependencies>
52
              <test-type>
68
                <test-type>
53
                  <name>unit</name>
69
                    <name>unit</name>
54
                  <test-dependency>
70
                    <test-dependency>
55
                      <code-name-base>org.netbeans.modules.projectapi</code-name-base>
71
                        <code-name-base>org.netbeans.modules.projectapi</code-name-base>
56
                      <recursive/>
72
                        <recursive/>
57
                      <compile-dependency/>
73
                        <compile-dependency/>
58
                  </test-dependency>
74
                    </test-dependency>
59
                  <test-dependency>
75
                    <test-dependency>
60
                      <code-name-base>org.openide.modules</code-name-base>
76
                        <code-name-base>org.openide.modules</code-name-base>
61
                  </test-dependency>
77
                    </test-dependency>
62
                  <test-dependency>
78
                    <test-dependency>
63
                      <code-name-base>org.netbeans.modules.masterfs</code-name-base>
79
                        <code-name-base>org.netbeans.modules.masterfs</code-name-base>
64
                  </test-dependency>
80
                    </test-dependency>
65
              </test-type>
81
                </test-type>
66
              <test-type>
82
                <test-type>
67
                  <name>qa-functional</name>
83
                    <name>qa-functional</name>
68
              </test-type>
84
                </test-type>
69
            </test-dependencies>
85
            </test-dependencies>
70
            <public-packages>
86
            <public-packages>
71
                <package>org.netbeans.api.project</package>
87
                <package>org.netbeans.api.project</package>
(-)projectapi/src/org/netbeans/spi/project/LookupMerger.java (+52 lines)
Added Link Here
1
/*
2
 * The contents of this file are subject to the terms of the Common Development
3
 * and Distribution License (the License). You may not use this file except in
4
 * compliance with the License.
5
 *
6
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7
 * or http://www.netbeans.org/cddl.txt.
8
 *
9
 * When distributing Covered Code, include this CDDL Header Notice in each file
10
 * and include the License file at http://www.netbeans.org/cddl.txt.
11
 * If applicable, add the following below the CDDL Header, with the fields
12
 * enclosed by brackets [] replaced by your own identifying information:
13
 * "Portions Copyrighted [year] [name of copyright owner]"
14
 *
15
 * The Original Software is NetBeans. The Initial Developer of the Original
16
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17
 * Microsystems, Inc. All Rights Reserved.
18
 */
19
20
package org.netbeans.spi.project;
21
22
import org.openide.util.Lookup;
23
24
/**
25
 * Allows project lookup to merge instances of known classes and replace them
26
 * with single instance. To be used in conjunction with the {@link org.netbeans.spi.project.LookupProvider}
27
 * and {@link org.netbeans.spi.project.support.LookupProviderSupport}
28
 * The interface is to be implemented by the project owner which decides which contracts make sense to have merged and
29
 * how they are to be merged.
30
 * The 3rd party {@link org.netbeans.spi.project.LookupProvider} implementors provide instances of mergeableClass.
31
 * {@link org.netbeans.spi.project.support.LookupProviderSupport#createCompositeLookup} handles the hiding of individual mergeable instances 
32
 * and exposing the merged instance created by the <code>LookupMerger</code>.
33
 * @author mkleint
34
 * @since org.netbeans.modules.projectapi 1.12
35
 */
36
public interface LookupMerger<T> {
37
    
38
    /**
39
     * Returns a class which is merged by this implementation of LookupMerger
40
     * @return Class instance
41
     */
42
    Class<T> getMergeableClass();
43
    
44
    /**
45
     * Merge instances of the given class in the given lookup and return merged 
46
     * object which substitutes them.
47
     * @param lookup lookup with the instances
48
     * @return object to be used instead of instances in the lookup
49
     */
50
    T merge(Lookup lookup);
51
52
}
(-)projectapi/src/org/netbeans/spi/project/LookupProvider.java (+45 lines)
Added Link Here
1
/*
2
 * The contents of this file are subject to the terms of the Common Development
3
 * and Distribution License (the License). You may not use this file except in
4
 * compliance with the License.
5
 *
6
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7
 * or http://www.netbeans.org/cddl.txt.
8
 *
9
 * When distributing Covered Code, include this CDDL Header Notice in each file
10
 * and include the License file at http://www.netbeans.org/cddl.txt.
11
 * If applicable, add the following below the CDDL Header, with the fields
12
 * enclosed by brackets [] replaced by your own identifying information:
13
 * "Portions Copyrighted [year] [name of copyright owner]"
14
 *
15
 * The Original Software is NetBeans. The Initial Developer of the Original
16
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17
 * Microsystems, Inc. All Rights Reserved.
18
 */
19
20
package org.netbeans.spi.project;
21
22
import org.openide.util.Lookup;
23
24
/**
25
 * interface for inclusion of 3rd party content in project's lookup. Typically, if the 
26
 * project type allows composition of lookup from multiple sources, it will make a layer
27
 * location public where 3rd parties will register implementations of this interface.
28
 * @author mkleint
29
 * @since org.netbeans.modules.projectapi 1.12
30
 */
31
public interface LookupProvider {
32
    
33
    /**
34
     * implementations will be asked to create their additional project lookup based on the baseContext
35
     * passed as parameter. The content of baseLookup is undefined on this level, is a contract
36
     * of the actual project type. Can be complete lookup of the project type, a portion of it or
37
     * something completely different that won't appear in the final project lookup.
38
     * Each implementation is only asked once for it's lookup for a given project instance at the time 
39
     * when project's lookup is being created.
40
     * @param baseContext implementation shall decide what to return for a given project instance based on context
41
     *  passed in.
42
     * @return a {@link org.openide.util.Lookup} instance that is to be added to the project's lookup, never null.
43
     */ 
44
    Lookup createAdditionalLookup(Lookup baseContext);
45
}
(-)projectapi/src/org/netbeans/spi/project/support/LookupProviderSupport.java (+283 lines)
Added Link Here
1
/*
2
 * The contents of this file are subject to the terms of the Common Development
3
 * and Distribution License (the License). You may not use this file except in
4
 * compliance with the License.
5
 *
6
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7
 * or http://www.netbeans.org/cddl.txt.
8
 *
9
 * When distributing Covered Code, include this CDDL Header Notice in each file
10
 * and include the License file at http://www.netbeans.org/cddl.txt.
11
 * If applicable, add the following below the CDDL Header, with the fields
12
 * enclosed by brackets [] replaced by your own identifying information:
13
 * "Portions Copyrighted [year] [name of copyright owner]"
14
 *
15
 * The Original Software is NetBeans. The Initial Developer of the Original
16
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17
 * Microsystems, Inc. All Rights Reserved.
18
 */
19
package org.netbeans.spi.project.support;
20
21
import com.sun.corba.se.impl.orbutil.LogKeywords;
22
import java.lang.ref.Reference;
23
import java.lang.ref.WeakReference;
24
import java.util.ArrayList;
25
import java.util.Arrays;
26
import java.util.Collection;
27
import java.util.Collections;
28
import java.util.Iterator;
29
import java.util.List;
30
import java.util.logging.Level;
31
import java.util.logging.Logger;
32
import javax.swing.event.ChangeEvent;
33
import javax.swing.event.ChangeListener;
34
import org.netbeans.api.project.Project;
35
import org.netbeans.api.project.SourceGroup;
36
import org.netbeans.api.project.Sources;
37
import org.netbeans.spi.project.LookupMerger;
38
import org.netbeans.spi.project.LookupProvider;
39
import org.openide.ErrorManager;
40
import org.openide.filesystems.FileObject;
41
import org.openide.filesystems.Repository;
42
import org.openide.loaders.DataFolder;
43
import org.openide.loaders.FolderLookup;
44
import org.openide.util.Lookup;
45
import org.openide.util.LookupEvent;
46
import org.openide.util.LookupListener;
47
import org.openide.util.WeakListeners;
48
import org.openide.util.lookup.Lookups;
49
import org.openide.util.lookup.ProxyLookup;
50
51
/**
52
 * Factory for lookup capable of merging content from registered 
53
 * {@link org.netbeans.spi.project.LookupProvider} instances.
54
 * @author mkleint
55
 * @since org.netbeans.modules.projectapi 1.12
56
 */
57
public final class LookupProviderSupport {
58
    
59
    /** Creates a new instance of LookupProviderSupport */
60
    private LookupProviderSupport() {
61
    }
62
    
63
    /**
64
     * Creates a project lookup instance that combines the content from multiple sources. 
65
     * A convenience factory method for implementors of Project
66
     * 
67
     * @param baseLookup initial, base content of the project lookup created by the project owner
68
     * @param folderPath the path in the System Filesystem that is used as root for lookup composition.
69
     *        The content of the folder is assumed to be {@link org.netbeans.spi.project.LookupProvider} instances
70
     * @return a lookup to be used in project
71
     */ 
72
    public static Lookup createCompositeLookup(Lookup baseLookup, String folderPath) {
73
        return new DelegatingLookupImpl(baseLookup, folderPath);
74
    }
75
    
76
    /**
77
     * Factory method for creating {@link org.netbeans.spi.project.LookupMerger} instance that merges
78
     * {@link org.netbeans.api.project.Sources} instances in the project lookup. 
79
     * Allows to compose the {@link org.netbeans.api.project.Sources}
80
     * content from multiple sources.
81
     * @return instance to include in project lookup
82
     */
83
    public static LookupMerger createSourcesMerger() {
84
        return new SourcesMerger();
85
    }
86
    
87
    //TODO maybe have just one single instance for a given path?
88
    private static Lookup createLookup(String folderPath) {
89
        FileObject root = Repository.getDefault().getDefaultFileSystem().findResource(folderPath);
90
        DataFolder folder = DataFolder.findFolder(root);
91
        return new FolderLookup(folder).getLookup();
92
    }
93
    
94
    static class DelegatingLookupImpl extends ProxyLookup implements LookupListener {
95
        private Lookup baseLookup;
96
        private Lookup.Result<LookupProvider> providerResult;
97
        private LookupListener providerListener;
98
        private List<LookupProvider> old = Collections.emptyList();
99
        private List<Lookup> currentLookups;
100
        
101
        private Lookup.Result<LookupMerger> mergers;
102
        private Reference<LookupListener> listenerRef;
103
        //#68623: the proxy lookup fires changes only if someone listens on a particular template:
104
        private List<Lookup.Result<?>> results = new ArrayList<Lookup.Result<?>>();
105
        
106
        public DelegatingLookupImpl(Lookup base, String path) {
107
            this(base, createLookup(path));
108
        }
109
        
110
        public DelegatingLookupImpl(Lookup base, Lookup providerLookup) {
111
            super();
112
            assert base != null;
113
            baseLookup = base;
114
            providerResult = providerLookup.lookup(new Lookup.Template<LookupProvider>(LookupProvider.class));
115
            doDelegate(providerResult.allInstances());
116
            providerListener = new LookupListener() {
117
                public void resultChanged(LookupEvent ev) {
118
                    doDelegate(providerResult.allInstances());
119
                }
120
            };
121
            providerResult.addLookupListener(providerListener);
122
        }
123
        
124
        
125
        public void resultChanged(LookupEvent ev) {
126
            doDelegate(providerResult.allInstances());
127
        }
128
        
129
        
130
        private synchronized void doDelegate(Collection<? extends LookupProvider> providers) {
131
            //unregister listeners from the old results:
132
            for (Lookup.Result<?> r : results) {
133
                r.removeLookupListener(this);
134
            }
135
            
136
            List<Lookup> newLookups = new ArrayList<Lookup>();
137
            for (LookupProvider elem : providers) {
138
                if (old.contains(elem)) {
139
                    int index = old.indexOf(elem);
140
                    newLookups.add(currentLookups.get(index));
141
                } else {
142
                    Lookup newone = elem.createAdditionalLookup(baseLookup);
143
                    assert newone != null;
144
                    LookupMerger merg = newone.lookup(LookupMerger.class);
145
                    if (merg != null) {
146
                        ErrorManager.getDefault().log(ErrorManager.WARNING,
147
                                "LookupProvider " + elem.getClass().getName() + " provides LookupMerger for " +
148
                                merg.getMergeableClass().getName() + 
149
                                ". That can cause project behaviour changes not anticipated by the project type owner." +
150
                                "Please consider making the LookupMerger a contract of the project type." );
151
                    }
152
                    newLookups.add(newone);
153
                }
154
            }
155
            old = (List<LookupProvider>) providers;
156
            currentLookups = newLookups;
157
            newLookups.add(baseLookup);
158
            Lookup lkp = new ProxyLookup(newLookups.toArray(new Lookup[newLookups.size()]));
159
            
160
            //merge:
161
            List<Class<?>> filteredClasses = new ArrayList<Class<?>>();
162
            List<Object> mergedInstances = new ArrayList<Object>();
163
            LookupListener l = listenerRef != null ? listenerRef.get() : null;
164
            if (l != null) {
165
                mergers.removeLookupListener(l);
166
            }
167
            mergers = lkp.lookupResult(LookupMerger.class);
168
            l = WeakListeners.create(LookupListener.class, this, mergers);
169
            listenerRef = new WeakReference<LookupListener>(l);
170
            mergers.addLookupListener(l);
171
            for (LookupMerger lm : mergers.allInstances()) {
172
                Class<?> c = lm.getMergeableClass();
173
                if (filteredClasses.contains(c)) {
174
                    ErrorManager.getDefault().log(ErrorManager.WARNING,
175
                            "Two LookupMerger registered for class " + c +
176
                            ". Only first one will be used"); // NOI18N
177
                    continue;
178
                }
179
                filteredClasses.add(c);
180
                mergedInstances.add(lm.merge(lkp));
181
                
182
                Lookup.Result<?> result = lkp.lookupResult(c);
183
                
184
                result.addLookupListener(this);
185
                results.add(result);
186
            }
187
            lkp = Lookups.exclude(lkp, filteredClasses.toArray(new Class<?>[filteredClasses.size()]));
188
            Lookup fixed = Lookups.fixed(mergedInstances.toArray(new Object[mergedInstances.size()]));
189
            setLookups(fixed, lkp);
190
        }
191
    }
192
    
193
    
194
    private static class SourcesMerger implements LookupMerger {
195
        private SourcesImpl merger;
196
        
197
        public Class getMergeableClass() {
198
            return Sources.class;
199
        }
200
201
        public Object merge(Lookup lookup) {
202
            if (merger == null) {
203
                merger = new SourcesImpl();
204
            } 
205
            merger.setLookup(lookup);
206
            return merger;
207
        }
208
    }
209
    
210
    private static class SourcesImpl implements Sources, ChangeListener, LookupListener {
211
        private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
212
        private Lookup.Result<Sources> delegates;
213
        private Collection<Sources> currentDelegates = new ArrayList<Sources>();
214
        
215
        public SourcesImpl() {
216
        }
217
218
        private void setLookup(Lookup lookup) {
219
            if (currentDelegates.size() > 0) {
220
                for (Sources old : currentDelegates) {
221
                    old.removeChangeListener(this);
222
                }
223
                currentDelegates.clear();
224
            }
225
            if (delegates != null) {
226
                delegates.removeLookupListener(this);
227
            }
228
            Lookup.Result<Sources> srcs = lookup.lookup(new Lookup.Template(Sources.class));
229
            for (Sources ns : srcs.allInstances()) {
230
                ns.addChangeListener(this);
231
                currentDelegates.add(ns);
232
            }
233
            srcs.addLookupListener(this);
234
            delegates = srcs;
235
            fireChange();
236
        }
237
238
        public SourceGroup[] getSourceGroups(String type) {
239
            assert delegates != null;
240
            Collection<SourceGroup> result = new ArrayList<SourceGroup>();
241
            for (Sources ns : delegates.allInstances()) {
242
                SourceGroup[] grps = ns.getSourceGroups(type);
243
                if (grps != null) {
244
                    result.addAll(Arrays.asList(grps));
245
                }
246
            }
247
            return result.toArray(new SourceGroup[result.size()]);
248
        }
249
250
        public void addChangeListener(ChangeListener listener) {
251
            listeners.add(listener);
252
        }
253
254
        public void removeChangeListener(ChangeListener listener) {
255
            listeners.remove(listener);
256
        }
257
258
        public void stateChanged(ChangeEvent e) {
259
            fireChange();
260
        }
261
262
        private void fireChange() {
263
            for (ChangeListener listener : listeners) {
264
                listener.stateChanged(new ChangeEvent(this));
265
            }
266
        }
267
268
        public void resultChanged(LookupEvent ev) {
269
            if (currentDelegates.size() > 0) {
270
                for (Sources old : currentDelegates) {
271
                    old.removeChangeListener(this);
272
                }
273
                currentDelegates.clear();
274
            }
275
            for (Sources ns : delegates.allInstances()) {
276
                ns.addChangeListener(this);
277
                currentDelegates.add(ns);
278
            }
279
            fireChange();
280
        }
281
    }
282
    
283
}
(-)projectapi/test/unit/src/org/netbeans/spi/project/support/LookupProviderSupportTest.java (+303 lines)
Added Link Here
1
/*
2
 * The contents of this file are subject to the terms of the Common Development
3
 * and Distribution License (the License). You may not use this file except in
4
 * compliance with the License.
5
 *
6
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7
 * or http://www.netbeans.org/cddl.txt.
8
 *
9
 * When distributing Covered Code, include this CDDL Header Notice in each file
10
 * and include the License file at http://www.netbeans.org/cddl.txt.
11
 * If applicable, add the following below the CDDL Header, with the fields
12
 * enclosed by brackets [] replaced by your own identifying information:
13
 * "Portions Copyrighted [year] [name of copyright owner]"
14
 *
15
 * The Original Software is NetBeans. The Initial Developer of the Original
16
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17
 * Microsystems, Inc. All Rights Reserved.
18
 */
19
20
21
package org.netbeans.spi.project.support;
22
23
import java.beans.PropertyChangeListener;
24
import java.lang.ref.Reference;
25
import java.lang.ref.WeakReference;
26
import java.util.ArrayList;
27
import java.util.Collection;
28
import java.util.Collections;
29
import java.util.HashMap;
30
import java.util.Iterator;
31
import java.util.List;
32
import javax.swing.Icon;
33
import javax.swing.JButton;
34
import javax.swing.JCheckBox;
35
import javax.swing.JComboBox;
36
import javax.swing.JRadioButton;
37
import javax.swing.JTextArea;
38
import javax.swing.JTextField;
39
import javax.swing.event.ChangeEvent;
40
import javax.swing.event.ChangeListener;
41
import org.netbeans.api.project.ProjectUtils;
42
import org.netbeans.api.project.SourceGroup;
43
import org.netbeans.api.project.Sources;
44
import org.netbeans.junit.NbTestCase;
45
import org.netbeans.spi.project.LookupMerger;
46
import org.netbeans.spi.project.LookupProvider;
47
import org.openide.ErrorManager;
48
import org.openide.filesystems.FileObject;
49
import org.openide.filesystems.Repository;
50
import org.openide.loaders.DataFolder;
51
import org.openide.loaders.FolderLookup;
52
import org.openide.util.Lookup;
53
import org.openide.util.LookupEvent;
54
import org.openide.util.LookupListener;
55
import org.openide.util.WeakListeners;
56
import org.openide.util.lookup.AbstractLookup;
57
import org.openide.util.lookup.InstanceContent;
58
import org.openide.util.lookup.Lookups;
59
import org.openide.util.lookup.ProxyLookup;
60
61
/**
62
 *
63
 * @author mkleint
64
 */
65
public class LookupProviderSupportTest extends NbTestCase {
66
    
67
    public LookupProviderSupportTest(String testName) {
68
        super(testName);
69
    }
70
71
    protected void setUp() throws Exception {
72
    }
73
74
    protected void tearDown() throws Exception {
75
    }
76
77
    /**
78
     * Test of createCompositeLookup method, of class org.netbeans.spi.project.support.LookupProviderSupport.
79
     */
80
    public void testCreateCompositeLookup() {
81
        LookupMergerImpl merger = new LookupMergerImpl();
82
        Lookup base = Lookups.fixed(new JButton(), new JComboBox(), merger);
83
        LookupProviderImpl pro1 = new LookupProviderImpl();
84
        LookupProviderImpl pro2 = new LookupProviderImpl();
85
        LookupProviderImpl pro3 = new LookupProviderImpl();
86
        
87
        InstanceContent provInst = new InstanceContent();
88
        Lookup providers = new AbstractLookup(provInst);
89
        provInst.add(pro1);
90
        provInst.add(pro2);
91
        
92
        pro1.ic.add(new JTextField());
93
        pro2.ic.add(new JTextArea());
94
        
95
        LookupProviderSupport.DelegatingLookupImpl del = new LookupProviderSupport.DelegatingLookupImpl(base, providers);
96
        
97
        assertNotNull(del.lookup(JTextArea.class));
98
        assertNotNull(del.lookup(JComboBox.class));
99
        
100
        // test merger..
101
        JButton butt = del.lookup(JButton.class);
102
        assertNotNull(butt);
103
        assertEquals("CORRECT", butt.getText());
104
        assertEquals(1, del.lookup(new Lookup.Template(JButton.class)).allInstances().size());
105
        assertEquals(1, merger.expectedCount);
106
        
107
        pro3.ic.add(new JButton());
108
        pro3.ic.add(new JRadioButton());
109
        provInst.add(pro3);
110
        assertNotNull(del.lookup(JRadioButton.class));
111
                
112
        // test merger..
113
        butt = del.lookup(JButton.class);
114
        assertNotNull(butt);
115
        assertEquals("CORRECT", butt.getText());
116
        assertEquals(1, del.lookup(new Lookup.Template(JButton.class)).allInstances().size());
117
        assertEquals(2, merger.expectedCount);
118
        
119
        pro1.ic.add(new JButton());
120
        
121
        // test merger..
122
        butt = del.lookup(JButton.class);
123
        assertNotNull(butt);
124
        assertEquals("CORRECT", butt.getText());
125
        assertEquals(1, del.lookup(new Lookup.Template(JButton.class)).allInstances().size());
126
        assertEquals(3, merger.expectedCount);
127
        
128
    }
129
    
130
    private SourcesImpl createImpl(String id) {
131
        SourcesImpl impl0 = new SourcesImpl();
132
        SourceGroupImpl grp0 = new SourceGroupImpl();
133
        grp0.name = id;
134
        impl0.grpMap.put("java", new SourceGroup[] {grp0});
135
        return impl0;
136
    }
137
    
138
    public void testSourcesMerger() {
139
        SourcesImpl impl0 = createImpl("group0");
140
        SourcesImpl impl1 = createImpl("group1");
141
        SourcesImpl impl2 = createImpl("group2");
142
        SourcesImpl impl3 = createImpl("group3");
143
        
144
        Lookup base = Lookups.fixed(impl0, LookupProviderSupport.createSourcesMerger());
145
        LookupProviderImpl2 pro1 = new LookupProviderImpl2();
146
        LookupProviderImpl2 pro2 = new LookupProviderImpl2();
147
        LookupProviderImpl2 pro3 = new LookupProviderImpl2();
148
        
149
        InstanceContent provInst = new InstanceContent();
150
        Lookup providers = new AbstractLookup(provInst);
151
        provInst.add(pro1);
152
        provInst.add(pro2);
153
        
154
        pro1.ic.add(impl1);
155
        pro2.ic.add(impl2);
156
        pro3.ic.add(impl3);
157
        
158
        LookupProviderSupport.DelegatingLookupImpl del = new LookupProviderSupport.DelegatingLookupImpl(base, providers);
159
        
160
        Sources srcs = del.lookup(Sources.class); 
161
        assertNotNull(srcs);
162
        SourceGroup[] grps = srcs.getSourceGroups("java");
163
        assertEquals(3, grps.length);
164
        
165
        //now let's add another module to the bunch and see if the new SG appears
166
        provInst.add(pro3);
167
        
168
        srcs = del.lookup(Sources.class); 
169
        assertNotNull(srcs);
170
        grps = srcs.getSourceGroups("java");
171
        assertEquals(4, grps.length);
172
        
173
        //now let's remove another module to the bunch and see if the SG disappears
174
        provInst.remove(pro2);
175
        
176
        srcs = del.lookup(Sources.class); 
177
        assertNotNull(srcs);
178
        grps = srcs.getSourceGroups("java");
179
        assertEquals(3, grps.length);
180
        
181
        //lets remove one and listen for changes...
182
        srcs = del.lookup(Sources.class); 
183
        ChangeListenerImpl ch = new ChangeListenerImpl();
184
        srcs.addChangeListener(ch);
185
        provInst.remove(pro1);
186
        
187
        assertTrue(ch.pinged);
188
        grps = srcs.getSourceGroups("java");
189
        assertEquals(2, grps.length);
190
        
191
        ch.pinged = false;
192
        provInst.add(pro2);
193
        
194
        assertTrue(ch.pinged);
195
        grps = srcs.getSourceGroups("java");
196
        assertEquals(3, grps.length);
197
        
198
    }
199
    
200
    private class ChangeListenerImpl implements ChangeListener {
201
        boolean pinged = false;
202
        public void stateChanged(ChangeEvent e) {
203
            pinged = true;
204
        }
205
    }
206
    
207
    private class LookupProviderImpl implements LookupProvider {
208
        InstanceContent ic = new InstanceContent();
209
        boolean wasAlreadyCalled = false;
210
        public Lookup createAdditionalLookup(Lookup baseContext) {
211
            assertNotNull(baseContext.lookup(JButton.class));
212
            assertNull(baseContext.lookup(JCheckBox.class));
213
            assertFalse(wasAlreadyCalled);
214
            wasAlreadyCalled = true;
215
            return new AbstractLookup(ic);
216
        }
217
    }
218
219
    private class LookupProviderImpl2 implements LookupProvider {
220
        InstanceContent ic = new InstanceContent();
221
        AbstractLookup l;
222
        public Lookup createAdditionalLookup(Lookup baseContext) {
223
            if (l == null) {
224
                l = new AbstractLookup(ic);
225
            }
226
            return l;
227
        }
228
    }
229
    
230
    private class LookupMergerImpl implements LookupMerger {
231
        
232
        int expectedCount;
233
        
234
        public Class getMergeableClass() {
235
            return JButton.class;
236
        }
237
238
        public Object merge(Lookup lookup) {
239
            Lookup.Result res = lookup.lookup(new Lookup.Template(JButton.class));
240
            int size = res.allInstances().size();
241
            expectedCount = size;
242
            return new JButton("CORRECT");
243
        }
244
        
245
    }
246
    
247
    private static class SourcesImpl implements Sources {
248
        public HashMap<String, SourceGroup[]> grpMap = new HashMap<String, SourceGroup[]>();
249
        private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
250
        
251
        
252
        public SourceGroup[] getSourceGroups(String type) {
253
            return grpMap.get(type);
254
        }
255
256
        public void addChangeListener(ChangeListener listener) {
257
            listeners.add(listener);
258
        }
259
260
        public void removeChangeListener(ChangeListener listener) {
261
            listeners.remove(listener);
262
        }
263
        
264
        public void fireChange() {
265
            for (ChangeListener listener : listeners) {
266
                listener.stateChanged(new ChangeEvent(this));
267
            }
268
        }
269
    }
270
    
271
    private static class SourceGroupImpl implements SourceGroup {
272
273
        String name;
274
275
        String displayName;
276
        public FileObject getRootFolder() {
277
            return null;
278
        }
279
280
        public String getName() {
281
            return name;
282
        }
283
284
        public String getDisplayName() {
285
            return displayName;
286
        }
287
288
        public Icon getIcon(boolean opened) {
289
            return null;
290
        }
291
292
        public boolean contains(FileObject file) throws IllegalArgumentException {
293
            return false;
294
        }
295
296
        public void addPropertyChangeListener(PropertyChangeListener listener) {
297
        }
298
299
        public void removePropertyChangeListener(PropertyChangeListener listener) {
300
        }
301
    }
302
    
303
}
(-)projectuiapi/apichanges.xml (+19 lines)
Lines 81-86 Link Here
81
    <!-- ACTUAL CHANGES BEGIN HERE: -->
81
    <!-- ACTUAL CHANGES BEGIN HERE: -->
82
82
83
    <changes>
83
    <changes>
84
        <change id="lookup-provider">
85
            <api name="general"/>
86
            <summary>Added LookupMerger implementations for PrivilegedTemplates and RecommendedTemplates</summary>
87
            <version major="1" minor="19"/>
88
            <date day="18" month="9" year="2006"/>
89
            <author login="mkleint"/>
90
            <compatibility addition="yes" binary="compatible" deletion="no" deprecation="no" modification="no" semantic="compatible" source="compatible">
91
            </compatibility>
92
            <description>
93
	        <p>
94
                    Related to 1.12 change in Project API.
95
                    <code>LookupMerger</code> implementation for<code>PrivilegedTemplates</code>  and
96
                    <code>RecommendedTemplates</code> added.
97
		</p>
98
            </description>
99
            <class package="org.netbeans.spi.project.ui.support" name="UILookupMergerSupport"/>
100
            <issue number="83343"/>
101
        </change>
102
        
84
        <change id="NodeFactory">
103
        <change id="NodeFactory">
85
            <api name="general"/>
104
            <api name="general"/>
86
            <summary>Ability to construct project node's children from multiple sources.</summary>
105
            <summary>Ability to construct project node's children from multiple sources.</summary>
(-)projectuiapi/src/org/netbeans/spi/project/ui/support/UILookupMergerSupport.java (+119 lines)
Added Link Here
1
/*
2
 * The contents of this file are subject to the terms of the Common Development
3
 * and Distribution License (the License). You may not use this file except in
4
 * compliance with the License.
5
 *
6
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7
 * or http://www.netbeans.org/cddl.txt.
8
 *
9
 * When distributing Covered Code, include this CDDL Header Notice in each file
10
 * and include the License file at http://www.netbeans.org/cddl.txt.
11
 * If applicable, add the following below the CDDL Header, with the fields
12
 * enclosed by brackets [] replaced by your own identifying information:
13
 * "Portions Copyrighted [year] [name of copyright owner]"
14
 *
15
 * The Original Software is NetBeans. The Initial Developer of the Original
16
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17
 * Microsystems, Inc. All Rights Reserved.
18
 */
19
20
21
package org.netbeans.spi.project.ui.support;
22
23
import java.util.Arrays;
24
import java.util.LinkedHashSet;
25
import java.util.Set;
26
import org.netbeans.spi.project.LookupMerger;
27
import org.netbeans.spi.project.ui.PrivilegedTemplates;
28
import org.netbeans.spi.project.ui.RecommendedTemplates;
29
import org.openide.util.Lookup;
30
31
/**
32
 * Factory class for creation of {@link org.netbeans.spi.project.LookupMerger} instances.
33
 * @author mkleint
34
 * @since org.netbeans.modules.projectuiapi 1.19
35
 */
36
public final class UILookupMergerSupport {
37
    
38
    /** Creates a new instance of LookupMergerSupport */
39
    private UILookupMergerSupport() {
40
    }
41
    
42
    /**
43
     * Create a {@link org.netbeans.spi.project.LookupMerger} instance 
44
     * for {@link org.netbeans.spi.project.ui.RecommendedTemplates}. Allows to merge 
45
     * templates from multiple sources. 
46
     * @return instance to include in project lookup
47
     */
48
    public static LookupMerger createRecommendedTemplatesMerger() {
49
        return new RecommendedMerger();
50
    }
51
    
52
    /**
53
     * Create a {@link org.netbeans.spi.project.LookupMerger} instance 
54
     * for {@link org.netbeans.spi.project.ui.PrivilegedTemplates}. Allows to merge 
55
     * templates from multiple sources. 
56
     * @return instance to include in project lookup
57
     */
58
    public static LookupMerger createPrivilegedTemplatesMerger() {
59
        return new PrivilegedMerger();
60
    }
61
    
62
    private static class PrivilegedMerger implements LookupMerger {
63
        public Class getMergeableClass() {
64
            return PrivilegedTemplates.class;
65
        }
66
67
        public Object merge(Lookup lookup) {
68
            return new PrivilegedTemplatesImpl(lookup);
69
        }
70
    }
71
    
72
    private static class RecommendedMerger implements LookupMerger {
73
        
74
        public Class getMergeableClass() {
75
            return RecommendedTemplates.class;
76
        }
77
78
        public Object merge(Lookup lookup) {
79
            return new RecommendedTemplatesImpl(lookup);
80
        }
81
    }
82
    
83
    private static class PrivilegedTemplatesImpl implements PrivilegedTemplates {
84
        
85
        private Lookup lkp;
86
        
87
        public PrivilegedTemplatesImpl(Lookup lkp) {
88
            this.lkp = lkp;
89
        }
90
        
91
        public String[] getPrivilegedTemplates() {
92
            Set<String> templates = new LinkedHashSet<String>();
93
            for (PrivilegedTemplates pt : lkp.lookupAll(PrivilegedTemplates.class)) {
94
                templates.addAll(Arrays.asList(pt.getPrivilegedTemplates()));
95
            }
96
            return templates.toArray(new String[templates.size()]);
97
        }
98
    }
99
    
100
    private static class RecommendedTemplatesImpl implements RecommendedTemplates {
101
        
102
        private Lookup lkp;
103
        
104
        public RecommendedTemplatesImpl(Lookup lkp) {
105
            this.lkp = lkp;
106
        }
107
        
108
        public String[] getRecommendedTypes() {
109
            Set<String> templates = new LinkedHashSet<String>();
110
            for (RecommendedTemplates pt : lkp.lookupAll(RecommendedTemplates.class)) {
111
                templates.addAll(Arrays.asList(pt.getRecommendedTypes()));
112
            }
113
            return templates.toArray(new String[templates.size()]);
114
        }
115
        
116
    }
117
    
118
    
119
}

Return to bug 83343