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 |
} |