001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.spi.project.support;
043:
044: import java.lang.ref.Reference;
045: import java.lang.ref.WeakReference;
046: import java.util.ArrayList;
047: import java.util.Arrays;
048: import java.util.Collection;
049: import java.util.Collections;
050: import java.util.List;
051: import java.util.Set;
052: import java.util.logging.Logger;
053: import javax.swing.event.ChangeEvent;
054: import javax.swing.event.ChangeListener;
055: import org.netbeans.api.project.SourceGroup;
056: import org.netbeans.api.project.Sources;
057: import org.netbeans.spi.project.LookupMerger;
058: import org.netbeans.spi.project.LookupProvider;
059: import org.openide.ErrorManager;
060: import org.openide.util.ChangeSupport;
061: import org.openide.util.Lookup;
062: import org.openide.util.LookupEvent;
063: import org.openide.util.LookupListener;
064: import org.openide.util.WeakListeners;
065: import org.openide.util.lookup.Lookups;
066: import org.openide.util.lookup.ProxyLookup;
067:
068: /**
069: * Factory for lookup capable of merging content from registered
070: * {@link org.netbeans.spi.project.LookupProvider} instances.
071: * @author mkleint
072: * @since org.netbeans.modules.projectapi 1.12
073: */
074: public final class LookupProviderSupport {
075:
076: private LookupProviderSupport() {
077: }
078:
079: /**
080: * Creates a project lookup instance that combines the content from multiple sources.
081: * A convenience factory method for implementors of Project.
082: *
083: * @param baseLookup initial, base content of the project lookup created by the project owner
084: * @param folderPath the path in the System Filesystem that is used as root for lookup composition, as for {@link Lookups#forPath}.
085: * The content of the folder is assumed to be {@link LookupProvider} instances.
086: * @return a lookup to be used in project
087: */
088: public static Lookup createCompositeLookup(Lookup baseLookup,
089: String folderPath) {
090: return new DelegatingLookupImpl(baseLookup, folderPath);
091: }
092:
093: /**
094: * Factory method for creating {@link org.netbeans.spi.project.LookupMerger} instance that merges
095: * {@link org.netbeans.api.project.Sources} instances in the project lookup.
096: * Allows to compose the {@link org.netbeans.api.project.Sources}
097: * content from multiple sources.
098: * @return instance to include in project lookup
099: */
100: public static LookupMerger<Sources> createSourcesMerger() {
101: return new SourcesMerger();
102: }
103:
104: static class DelegatingLookupImpl extends ProxyLookup implements
105: LookupListener {
106: private Lookup baseLookup;
107: private Lookup.Result<LookupProvider> providerResult;
108: private LookupListener providerListener;
109: private List<LookupProvider> old = Collections.emptyList();
110: private List<Lookup> currentLookups;
111:
112: private Lookup.Result<LookupMerger> mergers;
113: private Reference<LookupListener> listenerRef;
114: //#68623: the proxy lookup fires changes only if someone listens on a particular template:
115: private List<Lookup.Result<?>> results = new ArrayList<Lookup.Result<?>>();
116:
117: public DelegatingLookupImpl(Lookup base, String path) {
118: this (base, Lookups.forPath(path), path);
119: }
120:
121: public DelegatingLookupImpl(Lookup base, Lookup providerLookup,
122: String path) {
123: super ();
124: assert base != null;
125: baseLookup = base;
126: providerResult = providerLookup
127: .lookup(new Lookup.Template<LookupProvider>(
128: LookupProvider.class));
129: assert isAllJustLookupProviders(providerLookup) : "Layer content at "
130: + path
131: + " contains other than LookupProvider instances! See messages.log file for more details."; //NOI18N
132: doDelegate(providerResult.allInstances());
133: providerListener = new LookupListener() {
134: public void resultChanged(LookupEvent ev) {
135: doDelegate(providerResult.allInstances());
136: }
137: };
138: providerResult.addLookupListener(WeakListeners.create(
139: LookupListener.class, providerListener,
140: providerResult));
141: }
142:
143: //just for assertion evaluation.
144: private boolean isAllJustLookupProviders(Lookup lkp) {
145: Lookup.Result<Object> res = lkp.lookupResult(Object.class);
146: Set<Class<?>> set = res.allClasses();
147: for (Class clzz : set) {
148: if (!LookupProvider.class.isAssignableFrom(clzz)) {
149: Logger
150: .getLogger(
151: LookupProviderSupport.class
152: .getName())
153: .warning(
154: ""
155: + clzz.getName()
156: + " is not instance of LookupProvider."); //NOI18N
157: return false;
158: }
159: }
160: return true;
161: }
162:
163: public void resultChanged(LookupEvent ev) {
164: doDelegate(providerResult.allInstances());
165: }
166:
167: private synchronized void doDelegate(
168: Collection<? extends LookupProvider> providers) {
169: //unregister listeners from the old results:
170: for (Lookup.Result<?> r : results) {
171: r.removeLookupListener(this );
172: }
173:
174: List<Lookup> newLookups = new ArrayList<Lookup>();
175: for (LookupProvider elem : providers) {
176: if (old.contains(elem)) {
177: int index = old.indexOf(elem);
178: newLookups.add(currentLookups.get(index));
179: } else {
180: Lookup newone = elem
181: .createAdditionalLookup(baseLookup);
182: assert newone != null;
183: newLookups.add(newone);
184: }
185: }
186: old = new ArrayList<LookupProvider>(providers);
187: currentLookups = newLookups;
188: newLookups.add(baseLookup);
189: Lookup lkp = new ProxyLookup(newLookups
190: .toArray(new Lookup[newLookups.size()]));
191:
192: //merge:
193: List<Class<?>> filteredClasses = new ArrayList<Class<?>>();
194: List<Object> mergedInstances = new ArrayList<Object>();
195: LookupListener l = listenerRef != null ? listenerRef.get()
196: : null;
197: if (l != null) {
198: mergers.removeLookupListener(l);
199: }
200: mergers = lkp.lookupResult(LookupMerger.class);
201: l = WeakListeners.create(LookupListener.class, this ,
202: mergers);
203: listenerRef = new WeakReference<LookupListener>(l);
204: mergers.addLookupListener(l);
205: for (LookupMerger lm : mergers.allInstances()) {
206: Class<?> c = lm.getMergeableClass();
207: if (filteredClasses.contains(c)) {
208: ErrorManager.getDefault().log(
209: ErrorManager.WARNING,
210: "Two LookupMerger registered for class "
211: + c
212: + ". Only first one will be used"); // NOI18N
213: continue;
214: }
215: filteredClasses.add(c);
216: mergedInstances.add(lm.merge(lkp));
217:
218: Lookup.Result<?> result = lkp.lookupResult(c);
219:
220: result.addLookupListener(this );
221: results.add(result);
222: }
223: lkp = Lookups.exclude(lkp, filteredClasses
224: .toArray(new Class<?>[filteredClasses.size()]));
225: Lookup fixed = Lookups.fixed(mergedInstances
226: .toArray(new Object[mergedInstances.size()]));
227: setLookups(fixed, lkp);
228: }
229: }
230:
231: private static class SourcesMerger implements LookupMerger<Sources> {
232: private SourcesImpl merger;
233:
234: public Class<Sources> getMergeableClass() {
235: return Sources.class;
236: }
237:
238: public Sources merge(Lookup lookup) {
239: if (merger == null) {
240: merger = new SourcesImpl();
241: }
242: merger.setLookup(lookup);
243: return merger;
244: }
245: }
246:
247: private static class SourcesImpl implements Sources,
248: ChangeListener, LookupListener {
249: private final ChangeSupport changeSupport = new ChangeSupport(
250: this );
251: private Lookup.Result<Sources> delegates;
252: private Collection<Sources> currentDelegates = new ArrayList<Sources>();
253:
254: public SourcesImpl() {
255: }
256:
257: private void setLookup(Lookup lookup) {
258: if (currentDelegates.size() > 0) {
259: for (Sources old : currentDelegates) {
260: old.removeChangeListener(this );
261: }
262: currentDelegates.clear();
263: }
264: if (delegates != null) {
265: delegates.removeLookupListener(this );
266: }
267: Lookup.Result<Sources> srcs = lookup
268: .lookupResult(Sources.class);
269: for (Sources ns : srcs.allInstances()) {
270: ns.addChangeListener(this );
271: currentDelegates.add(ns);
272: }
273: srcs.addLookupListener(this );
274: delegates = srcs;
275: changeSupport.fireChange();
276: }
277:
278: public SourceGroup[] getSourceGroups(String type) {
279: assert delegates != null;
280: Collection<SourceGroup> result = new ArrayList<SourceGroup>();
281: for (Sources ns : delegates.allInstances()) {
282: SourceGroup[] grps = ns.getSourceGroups(type);
283: if (grps != null) {
284: result.addAll(Arrays.asList(grps));
285: }
286: }
287: return result.toArray(new SourceGroup[result.size()]);
288: }
289:
290: public void addChangeListener(ChangeListener listener) {
291: changeSupport.addChangeListener(listener);
292: }
293:
294: public void removeChangeListener(ChangeListener listener) {
295: changeSupport.removeChangeListener(listener);
296: }
297:
298: public void stateChanged(ChangeEvent e) {
299: changeSupport.fireChange();
300: }
301:
302: public void resultChanged(LookupEvent ev) {
303: if (currentDelegates.size() > 0) {
304: for (Sources old : currentDelegates) {
305: old.removeChangeListener(this );
306: }
307: currentDelegates.clear();
308: }
309: for (Sources ns : delegates.allInstances()) {
310: ns.addChangeListener(this);
311: currentDelegates.add(ns);
312: }
313: changeSupport.fireChange();
314: }
315: }
316:
317: }
|