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: package org.openide.explorer;
042:
043: import org.openide.nodes.*;
044: import org.openide.util.Lookup;
045: import org.openide.util.LookupEvent;
046: import org.openide.util.LookupListener;
047: import org.openide.util.WeakListeners;
048: import org.openide.util.lookup.Lookups;
049: import org.openide.util.lookup.ProxyLookup;
050:
051: import java.beans.*;
052:
053: import java.util.*;
054:
055: /**
056: * Contents of the lookup for a ExplorerManager, is copied
057: * from org.openide.windows.DefaultTopComponentLookup and shares
058: * its test. If updating, please update both.
059: * @author Jaroslav Tulach
060: */
061: final class DefaultEMLookup extends ProxyLookup implements
062: LookupListener, PropertyChangeListener {
063: private static final Object PRESENT = new Object();
064:
065: /** component to work with */
066: private ExplorerManager tc;
067:
068: /** lookup listener that is attached to all subnodes */
069: private LookupListener listener;
070:
071: /** Map of (Node -> node Lookup.Result) the above lookup listener is attached to */
072: private Map<Lookup, Lookup.Result> attachedTo;
073:
074: /** action map for the top component */
075: private Lookup actionMap;
076:
077: /** Creates the lookup.
078: * @param tc component to work on
079: * @param map action map to add to the lookup
080: */
081: public DefaultEMLookup(ExplorerManager tc, javax.swing.ActionMap map) {
082: super ();
083:
084: this .tc = tc;
085: this .listener = WeakListeners.create(LookupListener.class,
086: this , null);
087: this .actionMap = Lookups.singleton(map);
088:
089: tc.addPropertyChangeListener(WeakListeners.propertyChange(this ,
090: tc));
091:
092: updateLookups(tc.getSelectedNodes());
093: }
094:
095: /** Extracts activated nodes from a top component and
096: * returns their lookups.
097: */
098: public void updateLookups(Node[] arr) {
099: if (arr == null) {
100: arr = new Node[0];
101: }
102:
103: Lookup[] lookups = new Lookup[arr.length];
104:
105: Map<Lookup, Lookup.Result> copy;
106:
107: synchronized (this ) {
108: if (attachedTo == null) {
109: copy = Collections.<Lookup, Lookup.Result> emptyMap();
110: } else {
111: copy = new HashMap<Lookup, Lookup.Result>(attachedTo);
112: }
113: }
114:
115: for (int i = 0; i < arr.length; i++) {
116: lookups[i] = arr[i].getLookup();
117:
118: if (copy != null) {
119: // node arr[i] remains there, so do not remove it
120: copy.remove(arr[i]);
121: }
122: }
123:
124: for (Iterator<Lookup.Result> it = copy.values().iterator(); it
125: .hasNext();) {
126: Lookup.Result res = it.next();
127: res.removeLookupListener(listener);
128: }
129:
130: synchronized (this ) {
131: attachedTo = null;
132: }
133:
134: setLookups(new Lookup[] {
135: new NoNodeLookup(new ProxyLookup(lookups), arr),
136: Lookups.fixed((Object[]) arr), actionMap, });
137: }
138:
139: /** Change in one of the lookups we delegate to */
140: public void resultChanged(LookupEvent ev) {
141: updateLookups(tc.getSelectedNodes());
142: }
143:
144: /** Finds out whether a query for a class can be influenced
145: * by a state of the "nodes" lookup and whether we should
146: * initialize listening
147: */
148: private static boolean isNodeQuery(Class<?> c) {
149: return Node.class.isAssignableFrom(c)
150: || c.isAssignableFrom(Node.class);
151: }
152:
153: protected synchronized void beforeLookup(Template<?> t) {
154: if ((attachedTo == null) && isNodeQuery(t.getType())) {
155: Lookup[] arr = getLookups();
156:
157: attachedTo = new WeakHashMap<Lookup, Lookup.Result>(
158: arr.length * 2);
159:
160: for (int i = 0; i < (arr.length - 2); i++) {
161: Lookup.Result res = arr[i].lookup(t);
162: res.addLookupListener(listener);
163: attachedTo.put(arr[i], res);
164: }
165: }
166: }
167:
168: public void propertyChange(PropertyChangeEvent evt) {
169: if (ExplorerManager.PROP_SELECTED_NODES == evt
170: .getPropertyName()) {
171: updateLookups((Node[]) evt.getNewValue());
172: }
173: }
174:
175: /**
176: * A proxying Lookup impl which yields no results when queried for Node,
177: * and will never return any of the listed objects.
178: */
179: private static final class NoNodeLookup extends Lookup {
180: private final Lookup delegate;
181: private final Map<Node, Object> verboten;
182:
183: public NoNodeLookup(Lookup del, Node[] exclude) {
184: delegate = del;
185: verboten = new IdentityHashMap<Node, Object>();
186:
187: for (int i = 0; i < exclude.length; verboten.put(
188: exclude[i++], PRESENT))
189: ;
190: }
191:
192: public <T> T lookup(Class<T> clazz) {
193: if (clazz == Node.class) {
194: return null;
195: } else {
196: T o = delegate.lookup(clazz);
197:
198: if (verboten.containsKey(o)) {
199: // There might be another one of the same class.
200: Iterator<? extends T> it = lookup(
201: new Lookup.Template<T>(clazz))
202: .allInstances().iterator();
203:
204: while (it.hasNext()) {
205: T o2 = it.next();
206:
207: if (!verboten.containsKey(o2)) {
208: // OK, use this one.
209: return o2;
210: }
211: }
212:
213: // All such instances were excluded.
214: return null;
215: } else {
216: return o;
217: }
218: }
219: }
220:
221: public <T> Lookup.Result<T> lookup(Lookup.Template<T> template) {
222: Class<T> clz = template.getType();
223: if (clz == Node.class) {
224: return Lookup.EMPTY.lookup(new Lookup.Template<T>(clz));
225: } else {
226: return new ExclusionResult<T>(
227: delegate.lookup(template), verboten);
228: }
229: }
230:
231: /**
232: * A lookup result excluding some instances.
233: */
234: private static final class ExclusionResult<T> extends
235: Lookup.Result<T> implements LookupListener {
236: private final Lookup.Result<T> delegate;
237: private final Map<Node, Object> verboten;
238: private final List<LookupListener> listeners = new ArrayList<LookupListener>();
239:
240: public ExclusionResult(Lookup.Result<T> delegate,
241: Map<Node, Object> verboten) {
242: this .delegate = delegate;
243: this .verboten = verboten;
244: }
245:
246: public Collection<? extends T> allInstances() {
247: Collection<? extends T> c = delegate.allInstances();
248: List<T> ret = new ArrayList<T>(c.size()); // upper bound
249:
250: for (T o : c) {
251: if (!verboten.containsKey(o)) {
252: ret.add(o);
253: }
254: }
255:
256: return ret;
257: }
258:
259: public Set<Class<? extends T>> allClasses() {
260: return delegate.allClasses(); // close enough
261: }
262:
263: public Collection<? extends Item<T>> allItems() {
264: Collection<? extends Item<T>> c = delegate.allItems();
265: List<Item<T>> ret = new ArrayList<Item<T>>(c.size()); // upper bound
266:
267: for (Lookup.Item<T> i : c) {
268:
269: if (!verboten.containsKey(i.getInstance())) {
270: ret.add(i);
271: }
272: }
273:
274: return ret;
275: }
276:
277: public void addLookupListener(LookupListener l) {
278: synchronized (listeners) {
279: if (listeners.isEmpty()) {
280: delegate.addLookupListener(this );
281: }
282:
283: listeners.add(l);
284: }
285: }
286:
287: public void removeLookupListener(LookupListener l) {
288: synchronized (listeners) {
289: listeners.remove(l);
290:
291: if (listeners.isEmpty()) {
292: delegate.removeLookupListener(this );
293: }
294: }
295: }
296:
297: public void resultChanged(LookupEvent ev) {
298: LookupEvent ev2 = new LookupEvent(this );
299: LookupListener[] ls;
300:
301: synchronized (listeners) {
302: ls = listeners.toArray(new LookupListener[listeners
303: .size()]);
304: }
305:
306: for (int i = 0; i < ls.length; i++) {
307: ls[i].resultChanged(ev2);
308: }
309: }
310: }
311: }
312: // end of NoNodeLookup
313: }
|