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.windows;
042:
043: import org.openide.nodes.Node;
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.AbstractLookup;
049: import org.openide.util.lookup.Lookups;
050: import org.openide.util.lookup.ProxyLookup;
051:
052: import java.util.ArrayList;
053: import java.util.Collection;
054: import java.util.Collections;
055: import java.util.HashMap;
056: import java.util.IdentityHashMap;
057: import java.util.Iterator;
058: import java.util.List;
059: import java.util.Map;
060: import java.util.Set;
061: import java.util.WeakHashMap;
062:
063: /**
064: * Contents of the lookup for a top component.
065: * Should contain its activated nodes, as well as their lookups merged.
066: * Also contains an ActionMap instance which is a {@link DelegateActionMap}.
067: * If there is no selection (as opposed to an empty selection), the lookup on Node
068: * nonetheless contains one item assignable to Node but with a null instance (!).
069: * If a node contains itself or another node in its lookup, this does not produce
070: * any duplication in the top component lookup.
071: * Queries on Node will return only nodes actually in the activated node list.
072: * @author Jaroslav Tulach
073: */
074: final class DefaultTopComponentLookup extends ProxyLookup implements
075: LookupListener {
076: private static final Object PRESENT = new Object();
077:
078: /** component to work with */
079: private TopComponent tc;
080:
081: /** lookup listener that is attached to all subnodes */
082: private LookupListener listener;
083:
084: /** Map of (Lookup -> node Lookup.Result) the above lookup listener is attached to */
085: private Map<Lookup, Lookup.Result> attachedTo;
086:
087: /** action map for the top component */
088: private Lookup actionMap;
089:
090: /** Creates the lookup.
091: * @param tc component to work on
092: */
093: public DefaultTopComponentLookup(TopComponent tc) {
094: super ();
095:
096: this .tc = tc;
097: this .listener = WeakListeners.create(LookupListener.class,
098: this , null);
099: this .actionMap = Lookups.singleton(new DelegateActionMap(tc));
100:
101: updateLookups(tc.getActivatedNodes());
102: }
103:
104: /** Extracts activated nodes from a top component and
105: * returns their lookups.
106: */
107: public void updateLookups(Node[] arr) {
108: if (arr == null) {
109: AbstractLookup.Content c = new AbstractLookup.Content();
110: AbstractLookup l = new AbstractLookup(c);
111: c.addPair(new NoNodesPair());
112: setLookups(new Lookup[] { l, actionMap });
113:
114: return;
115: }
116:
117: Lookup[] lookups = new Lookup[arr.length];
118:
119: Map<Lookup, Lookup.Result> copy;
120:
121: synchronized (this ) {
122: if (attachedTo == null) {
123: copy = Collections.emptyMap();
124: } else {
125: copy = new HashMap<Lookup, Lookup.Result>(attachedTo);
126: }
127: }
128:
129: for (int i = 0; i < arr.length; i++) {
130: lookups[i] = arr[i].getLookup();
131:
132: if (copy != null) {
133: // node arr[i] remains there, so do not remove it
134: copy.remove(arr[i]);
135: }
136: }
137:
138: for (Iterator<Lookup.Result> it = copy.values().iterator(); it
139: .hasNext();) {
140: Lookup.Result res = it.next();
141: res.removeLookupListener(listener);
142: }
143:
144: synchronized (this ) {
145: attachedTo = null;
146: }
147:
148: setLookups(new Lookup[] {
149: new NoNodeLookup(new ProxyLookup(lookups), arr),
150: Lookups.fixed((Object[]) arr), actionMap, });
151: }
152:
153: /** Change in one of the lookups we delegate to */
154: public void resultChanged(LookupEvent ev) {
155: updateLookups(tc.getActivatedNodes());
156: }
157:
158: /** Finds out whether a query for a class can be influenced
159: * by a state of the "nodes" lookup and whether we should
160: * initialize listening
161: */
162: private static boolean isNodeQuery(Class<?> c) {
163: return Node.class.isAssignableFrom(c)
164: || c.isAssignableFrom(Node.class);
165: }
166:
167: @Override
168: protected synchronized void beforeLookup(Template<?> t) {
169: if ((attachedTo == null) && isNodeQuery(t.getType())) {
170: Lookup[] arr = getLookups();
171:
172: attachedTo = new WeakHashMap<Lookup, Lookup.Result>(
173: arr.length * 2);
174:
175: for (int i = 0; i < (arr.length - 2); i++) {
176: Lookup.Result<?> res = arr[i].lookup(t);
177: res.addLookupListener(listener);
178: attachedTo.put(arr[i], res);
179: }
180: }
181: }
182:
183: private static final class NoNodesPair extends AbstractLookup.Pair {
184: public NoNodesPair() {
185: }
186:
187: protected boolean creatorOf(Object obj) {
188: return false;
189: }
190:
191: public String getDisplayName() {
192: return getId();
193: }
194:
195: public String getId() {
196: return "none"; // NOI18N
197: }
198:
199: public Object getInstance() {
200: return null;
201: }
202:
203: public Class getType() {
204: return org.openide.nodes.Node.class;
205: }
206:
207: protected boolean instanceOf(Class c) {
208: return Node.class.isAssignableFrom(c);
209: }
210: }
211:
212: // end of NoNodesPair
213:
214: // XXX try to use Lookups.exclude; cf. comments in #53058
215:
216: /**
217: * A proxying Lookup impl which yields no results when queried for Node,
218: * and will never return any of the listed objects.
219: */
220: private static final class NoNodeLookup extends Lookup {
221: private final Lookup delegate;
222: private final Map<Object, Object> verboten;
223:
224: public NoNodeLookup(Lookup del, Object[] exclude) {
225: delegate = del;
226: verboten = new IdentityHashMap<Object, Object>();
227:
228: for (int i = 0; i < exclude.length; verboten.put(
229: exclude[i++], PRESENT)) {
230: }
231: }
232:
233: public <T> T lookup(Class<T> clazz) {
234: if (clazz == Node.class) {
235: return null;
236: } else {
237: T o = delegate.lookup(clazz);
238:
239: if (verboten.containsKey(o)) {
240: // There might be another one of the same class.
241: for (T o2 : lookupAll(clazz)) {
242:
243: if (!verboten.containsKey(o2)) {
244: // OK, use this one.
245: return o2;
246: }
247: }
248:
249: // All such instances were excluded.
250: return null;
251: } else {
252: return o;
253: }
254: }
255: }
256:
257: @SuppressWarnings("unchecked")
258: public <T> Lookup.Result<T> lookup(Lookup.Template<T> template) {
259: if (template.getType() == Node.class) {
260: return Lookup.EMPTY.lookup(new Lookup.Template(
261: Node.class));
262: } else {
263: return new ExclusionResult<T>(
264: delegate.lookup(template), verboten);
265: }
266: }
267:
268: /**
269: * A lookup result excluding some instances.
270: */
271: private static final class ExclusionResult<T> extends
272: Lookup.Result<T> implements LookupListener {
273: private final Lookup.Result<T> delegate;
274: private final Map<Object, Object> verboten;
275: private final List<LookupListener> listeners = new ArrayList<LookupListener>();
276:
277: public ExclusionResult(Lookup.Result<T> delegate,
278: Map<Object, Object> verboten) {
279: this .delegate = delegate;
280: this .verboten = verboten;
281: }
282:
283: public Collection<? extends T> allInstances() {
284: Collection<? extends T> c = delegate.allInstances();
285: List<T> ret = new ArrayList<T>(c.size()); // upper bound
286:
287: for (Iterator<? extends T> it = c.iterator(); it
288: .hasNext();) {
289: T o = it.next();
290:
291: if (!verboten.containsKey(o)) {
292: ret.add(o);
293: }
294: }
295:
296: return ret;
297: }
298:
299: @Override
300: public Set<Class<? extends T>> allClasses() {
301: return delegate.allClasses(); // close enough
302: }
303:
304: @Override
305: public Collection<? extends Lookup.Item<T>> allItems() {
306: Collection<? extends Lookup.Item<T>> c = delegate
307: .allItems();
308: List<Lookup.Item<T>> ret = new ArrayList<Lookup.Item<T>>(
309: c.size()); // upper bound
310:
311: for (Iterator<? extends Lookup.Item<T>> it = c
312: .iterator(); it.hasNext();) {
313: Lookup.Item<T> i = it.next();
314:
315: if (!verboten.containsKey(i.getInstance())) {
316: ret.add(i);
317: }
318: }
319:
320: return ret;
321: }
322:
323: public void addLookupListener(LookupListener l) {
324: synchronized (listeners) {
325: if (listeners.isEmpty()) {
326: delegate.addLookupListener(this );
327: }
328:
329: listeners.add(l);
330: }
331: }
332:
333: public void removeLookupListener(LookupListener l) {
334: synchronized (listeners) {
335: listeners.remove(l);
336:
337: if (listeners.isEmpty()) {
338: delegate.removeLookupListener(this );
339: }
340: }
341: }
342:
343: public void resultChanged(LookupEvent ev) {
344: LookupEvent ev2 = new LookupEvent(this );
345: LookupListener[] ls;
346:
347: synchronized (listeners) {
348: ls = listeners.toArray(new LookupListener[listeners
349: .size()]);
350: }
351:
352: for (int i = 0; i < ls.length; i++) {
353: ls[i].resultChanged(ev2);
354: }
355: }
356: }
357: }
358: }
|