2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.apisupport.beanbrowser;
44
import java.awt.Container;
45
import java.awt.datatransfer.Clipboard;
46
import java.beans.IntrospectionException;
47
import java.beans.PropertyChangeEvent;
48
import java.beans.PropertyChangeListener;
50
import java.lang.reflect.Method;
51
import java.util.ArrayList;
52
import java.util.Collection;
53
import java.util.Collections;
54
import java.util.LinkedList;
55
import java.util.List;
56
import java.util.StringTokenizer;
57
import org.openide.ErrorManager;
58
import org.openide.cookies.InstanceCookie;
59
import org.openide.filesystems.FileObject;
60
import org.openide.filesystems.FileSystem;
61
import org.openide.filesystems.LocalFileSystem;
62
import org.openide.filesystems.MultiFileSystem;
63
import org.openide.filesystems.XMLFileSystem;
64
import org.openide.loaders.DataObject;
65
import org.openide.nodes.AbstractNode;
66
import org.openide.nodes.BeanNode;
67
import org.openide.nodes.Children;
68
import org.openide.nodes.FilterNode;
69
import org.openide.nodes.Node;
70
import org.openide.nodes.NodeAdapter;
71
import org.openide.nodes.NodeListener;
72
import org.openide.nodes.NodeMemberEvent;
73
import org.openide.nodes.NodeReorderEvent;
74
import org.openide.util.HelpCtx;
75
import org.openide.util.Lookup;
76
import org.openide.util.lookup.Lookups;
79
* Represents all the children of a wrapper node, including
80
* lots of special items for certain node types.
82
class WrapperKids extends Children.Keys implements Cloneable {
85
private static final Object instanceKey = new Object() {
86
public String toString() {
87
return "Key for instance cookie.";
90
private static final Object rawBeanPropsKey = new Object() {
91
public String toString() {
92
return "Key for raw bean properties.";
95
private static final class NormalChildKey {
96
private final Node child;
97
public NormalChildKey(Node child) {
101
return Wrapper.make(child);
104
private static final class LookupProviderKey {
105
public final Lookup.Provider p;
106
public LookupProviderKey(Lookup.Provider p) {
111
private Node original;
112
private NodeListener nListener = null;
113
private PropertyChangeListener fsListener = null;
114
private FileSystem fileSystemToListenOn = null;
116
WrapperKids(Node orig) {
119
// Probably not needed:
120
public Object clone() {
121
return new WrapperKids(original);
127
* <li> normalKey, for the original node's children.
128
* <li> A node property set--i.e. Properties, Expert.
129
* <li> instanceKey, if it is an instance.
130
* <li> A {@link Method} for cookies, representing the method to get a cookie from the object.
131
* <li> Itself (the instance) if a Node, Container, FileSystem, FileObject, or Clipboard.
134
private void updateKeys(final boolean addListeners) {
135
//Thread.dumpStack ();
136
//System.err.println ("original's class: " + original.getClass ().getName ());
137
Children.MUTEX.postWriteRequest(new Runnable() { public void run() {
138
List newkeys = new ArrayList();
139
Node[] children = original.getChildren().getNodes(/*intentionally:*/false);
140
for (int i = 0; i < children.length; i++) {
141
newkeys.add(new NormalChildKey(children[i]));
143
newkeys.addAll(makePSKeys());
144
// For BeanNode, we assume that we already are displaying the "instance" right here anyway.
145
if (! (original instanceof BeanNode) && original.getCookie(InstanceCookie.class) != null)
146
newkeys.add(instanceKey);
147
// BeanNode's which are actually representing interesting objects:
148
if (original instanceof BeanNode) {
149
newkeys.add(rawBeanPropsKey);
151
InstanceCookie cookie = (InstanceCookie) original.getCookie(InstanceCookie.class);
152
Object instance = cookie.instanceCreate();
153
Class[] recognized = { Node.class, Container.class, FileSystem.class, FileObject.class, Clipboard.class };
154
for (int i = 0; i < recognized.length; i++)
155
if (recognized[i].isInstance(instance))
156
newkeys.add(instance);
157
// Special listener handling:
158
if (instance instanceof FileSystem)
159
fileSystemToListenOn = (FileSystem) instance;
160
if (instance instanceof Lookup.Provider) {
161
newkeys.add(new LookupProviderKey((Lookup.Provider)instance));
163
} catch (Exception e) {
164
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
167
newkeys.add(new LookupProviderKey(original));
170
//System.err.println ("Setting keys for wrapper of " + original.getDisplayName () + "; count: " + newkeys.size ());
172
nListener = new NodeAdapter() {
173
public void propertyChange(PropertyChangeEvent ev) {
174
if (Node.PROP_PROPERTY_SETS.equals(ev.getPropertyName())) {
178
// Could instead override filterChildren* methods:
179
public void childrenAdded(NodeMemberEvent ev) {
182
public void childrenRemoved(NodeMemberEvent ev) {
185
public void childrenReordered(NodeReorderEvent ev) {
189
original.addNodeListener(nListener);
190
if (fileSystemToListenOn != null) {
191
fsListener = new PropertyChangeListener() {
192
public void propertyChange(PropertyChangeEvent ev) {
193
if (FileSystem.PROP_ROOT.equals(ev.getPropertyName())) {
198
fileSystemToListenOn.addPropertyChangeListener(fsListener);
204
/** Set the keys and attach a listener to the original node.
205
* If its list of children is changed, the normalKey
206
* may be added or removed.
208
protected void addNotify() {
209
//System.err.println ("addNotify called for wrapper of " + original.getDisplayName ());
213
protected void removeNotify() {
214
if (nListener != null) {
215
original.removeNodeListener(nListener);
218
if (fsListener != null) {
219
fileSystemToListenOn.removePropertyChangeListener(fsListener);
221
fileSystemToListenOn = null;
223
setKeys(Collections.EMPTY_SET);
226
/** Make a list of property set keys.
227
* One key (a Node.PropertySet) is added for every property set
228
* which contains at least one property which is not a primitive
229
* or of String or Class type.
230
* <p> Note that it is possible for a property to be of e.g. Object type,
231
* and have a displayed node, even though the actual value is a String, e.g.
232
* @return a list of keys
234
private Collection makePSKeys() {
235
Collection toret = new ArrayList();
236
Node.PropertySet[] pss = original.getPropertySets();
237
for (int i = 0; i < pss.length; i++) {
238
Node.PropertySet ps = pss[i];
239
Node.Property[] props = ps.getProperties();
240
boolean useme = false;
241
for (int j = 0; j < props.length; j++) {
242
Node.Property prop = props[j];
243
if (prop.canRead()) {
244
Class type = prop.getValueType();
245
if (! (type.isPrimitive() || type == String.class || type == Class.class)) {
250
if (useme) toret.add(ps);
255
/** Actual interpret a key.
256
* Creates a node representing each key, e.g. a BeanNode for instanceKey,
257
* or for a Node.PropertySet, a PropSet node.
258
* @param key the key to interpret
259
* @return the (one) node to display for it
261
protected Node[] createNodes(Object key) {
262
if (key instanceof NormalChildKey) {
263
return new Node[] {((NormalChildKey) key).wrap()};
264
} else if (key instanceof Node.PropertySet) {
265
// A property set with subnodes for the properties.
266
return new Node[] { new PropSet(original, (Node.PropertySet) key) };
267
} else if (key == rawBeanPropsKey) {
268
// Raw bean properties, unfiltered by BeanInfo etc.
270
InstanceCookie inst = (InstanceCookie) original.getCookie(InstanceCookie.class);
271
AbstractNode n = new AbstractNode(new RawBeanPropKids(inst.instanceCreate())) {
272
public HelpCtx getHelpCtx() {
273
return new HelpCtx("org.netbeans.modules.apisupport.beanbrowser");
276
n.setName("Raw bean properties...");
277
n.setIconBaseWithExtension("org/netbeans/modules/apisupport/beanbrowser/BeanBrowserIcon.gif");
278
return new Node[] { n };
279
} catch (Exception e) {
280
return new Node[] { Wrapper.make(PropSetKids.makeErrorNode(e)) };
282
} else if (key == instanceKey) {
283
// Something which can provide an instance object--e.g. the deserialized object
286
InstanceCookie inst = (InstanceCookie) original.getCookie(InstanceCookie.class);
287
Node node = new RefinedBeanNode(inst.instanceCreate());
288
node.setShortDescription("Instance name: `" + inst.instanceName() +
289
"'; normal node name: `" + node.getDisplayName() + "'; normal description: `" +
290
node.getShortDescription() + "'");
291
node.setDisplayName("Instance of class " + inst.instanceClass().getName());
292
return new Node[] { Wrapper.make(node) };
293
} catch (Exception e) {
294
return new Node[] { Wrapper.make(PropSetKids.makeErrorNode(e)) };
296
} else if (key instanceof Node) {
297
List toret = new LinkedList(); // List<Node>
298
// Show the actual node itself.
299
AbstractNode marker = new AbstractNode(new Children.Array()) {
300
public HelpCtx getHelpCtx() {
301
return new HelpCtx("org.netbeans.modules.apisupport.beanbrowser");
304
marker.setName("An actual node here:");
305
marker.setIconBaseWithExtension("org/netbeans/modules/apisupport/beanbrowser/BeanBrowserIcon.gif");
306
marker.getChildren().add(new Node[] { Wrapper.make((Node) key) });
308
if (key instanceof FilterNode) {
309
// Try to separately show the original too:
311
Method m = FilterNode.class.getDeclaredMethod("getOriginal", new Class[] { });
312
m.setAccessible(true);
314
Node orig = (Node) m.invoke(key, new Object[] { });
315
AbstractNode marker2 = new AbstractNode(new Children.Array()) {
316
public HelpCtx getHelpCtx() {
317
return new HelpCtx("org.netbeans.modules.apisupport.beanbrowser");
320
marker2.setName("The original from the filter node:");
321
marker2.setIconBaseWithExtension("org/netbeans/modules/apisupport/beanbrowser/BeanBrowserIcon.gif");
322
marker2.getChildren().add(new Node[] { PropSetKids.makeObjectNode(orig) });
325
m.setAccessible(false);
327
} catch (Exception e) {
328
toret.add(Wrapper.make(PropSetKids.makeErrorNode(e)));
331
return (Node[]) toret.toArray(new Node[toret.size()]);
332
// XXX should show getActions(true/false) rather than getActions()/getContextActions()
333
} else if (key instanceof Container) {
334
// An AWT Container with its subcomponents.
335
Children kids = new ContainerKids((Container) key);
336
AbstractNode n = new AbstractNode(kids) {
337
public HelpCtx getHelpCtx() {
338
return new HelpCtx("org.netbeans.modules.apisupport.beanbrowser");
341
n.setName("Components...");
342
n.setIconBaseWithExtension("org/netbeans/modules/apisupport/beanbrowser/BeanBrowserIcon.gif");
343
return new Node[] { n };
344
} else if (key instanceof FileSystem) {
345
// "root" is not a declared Bean property of FileSystem's, so specially display it.
347
Node fsn = new RefinedBeanNode(((FileSystem) key).getRoot());
348
fsn.setDisplayName("[root] " + fsn.getDisplayName());
349
return new Node[] { Wrapper.make(fsn) };
350
} catch (IntrospectionException e) {
351
return new Node[] { Wrapper.make(PropSetKids.makeErrorNode(e)) };
353
} else if (key instanceof FileObject) {
354
FileObject fo = (FileObject)key;
355
// Try to show: data object; attributes; possibly provenance.
356
List l = new LinkedList(); // List<Node>
357
// Display the corresponding DataObject.
358
// The node delegate is also available as a Bean property of the DO.
360
Node fsn = new RefinedBeanNode(DataObject.find(fo));
361
fsn.setDisplayName("[data object] " + fsn.getDisplayName());
362
l.add(Wrapper.make(fsn));
363
} catch (Exception e) { // DataObjectNotFoundException, IntrospectionException
364
l.add(Wrapper.make(PropSetKids.makeErrorNode(e)));
366
Children kids = new FileAttrKids(fo);
367
AbstractNode attrnode = new AbstractNode(kids) {
368
public HelpCtx getHelpCtx() {
369
return new HelpCtx("org.netbeans.modules.apisupport.beanbrowser");
372
attrnode.setName("Attributes...");
373
attrnode.setIconBaseWithExtension("org/netbeans/modules/apisupport/beanbrowser/BeanBrowserIcon.gif");
376
FileSystem apparentFS = fo.getFileSystem();
377
if (apparentFS instanceof MultiFileSystem) {
378
// #18698: try to show provenance of the file object.
379
FileSystem fs = apparentFS;
380
FileObject workingFO = fo;
381
boolean project = false;
382
while (fs instanceof MultiFileSystem) {
383
if (fs.getClass().getName().equals("org.netbeans.core.projects.FilterFileSystem")) {
384
// We could traverse it to the original filesystem, but that would
385
// be the $userdir/system/ LFS, which is not what we want to see.
389
Method m = MultiFileSystem.class.getDeclaredMethod("findSystem", new Class[] {FileObject.class});
390
m.setAccessible(true);
392
//FileObject workingFO = fs.findResource(path);
393
FileSystem foundFS = (FileSystem)m.invoke(fs, new Object[] {workingFO});
396
//System.err.println("no delegate for " + workingFO + " on " + fs);
399
Method m2 = MultiFileSystem.class.getDeclaredMethod("findResourceOn", new Class[] {FileSystem.class, String.class});
400
m2.setAccessible(true);
402
FileObject newFO = (FileObject)m2.invoke(fs, new Object[] {foundFS, workingFO.getPath()});
404
//System.err.println("delegating " + fs + "/" + workingFO + " -> " + foundFS + "/" + newFO);
408
//System.err.println("findResourceOn for " + fs + " with " + foundFS + " and " + workingFO + " -> null");
411
m2.setAccessible(false);
415
m.setAccessible(false);
418
if (project || fs != apparentFS) {
419
String provenance = null;
421
provenance = "project";
422
} else if (fs instanceof LocalFileSystem) {
423
File dir = ((LocalFileSystem)fs).getRootDirectory();
424
// #27151: netbeans.dirs
425
StringTokenizer tok = new StringTokenizer(System.getProperty("netbeans.dirs", ""), File.pathSeparator);
426
while (tok.hasMoreTokens()) {
427
File system = new File(new File(tok.nextToken()), "system"); // NOI18N
428
if (dir.equals(system)) {
429
provenance = "NetBeans installation";
433
if (provenance == null) {
434
File system1 = new File(new File(System.getProperty("netbeans.home")), "system");
435
File system2 = new File(new File(System.getProperty("netbeans.user")), "system");
436
if (dir.equals(system1)) {
437
provenance = "NetBeans installation";
438
} else if (dir.equals(system2)) {
439
provenance = "user directory";
442
} else if (fs instanceof XMLFileSystem) {
443
// Well, a good guess at least. Note merged XMLFS's and
444
// module cache mean we cannot easily do better.
445
provenance = "module";
447
if (provenance != null) {
448
//System.err.println("filesystem: " + fs + " path: " + workingFO);
449
l.add(PropSetKids.makePlainNode("Provenance: " + provenance));
451
// No well-known origin, just show the FS.
452
Node rfsn = new RefinedBeanNode(fs);
453
rfsn.setDisplayName("[original file system] " + rfsn.getDisplayName());
454
l.add(Wrapper.make(rfsn));
458
} catch (Exception e) {
459
l.add(Wrapper.make(PropSetKids.makeErrorNode(e)));
461
return (Node[])l.toArray(new Node[l.size()]);
462
} else if (key instanceof Clipboard) {
463
Children kids = new ClipboardKids((Clipboard) key);
464
AbstractNode n = new AbstractNode(kids) {
465
public HelpCtx getHelpCtx() {
466
return new HelpCtx("org.netbeans.modules.apisupport.beanbrowser");
469
n.setName("Transferables...");
470
n.setIconBaseWithExtension("org/netbeans/modules/apisupport/beanbrowser/BeanBrowserIcon.gif");
471
return new Node[] { n };
472
} else if (key instanceof LookupProviderKey) { // #26617
473
Node n = LookupNode.localLookupNode(Lookups.proxy(((LookupProviderKey) key).p));
474
n.setDisplayName("Cookies...");
475
return new Node[] { n };
477
throw new RuntimeException("Weird key: " + key);