~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to apisupport/beanbrowser/src/org/netbeans/modules/apisupport/beanbrowser/WrapperKids.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
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]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
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.
 
29
 *
 
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.
 
40
 */
 
41
 
 
42
package org.netbeans.modules.apisupport.beanbrowser;
 
43
 
 
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;
 
49
import java.io.File;
 
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;
 
77
 
 
78
/** The fun stuff.
 
79
 * Represents all the children of a wrapper node, including
 
80
 * lots of special items for certain node types.
 
81
 */
 
82
class WrapperKids extends Children.Keys implements Cloneable {
 
83
    
 
84
    // Special keys:
 
85
    private static final Object instanceKey = new Object() {
 
86
        public String toString() {
 
87
            return "Key for instance cookie.";
 
88
        }
 
89
    };
 
90
    private static final Object rawBeanPropsKey = new Object() {
 
91
        public String toString() {
 
92
            return "Key for raw bean properties.";
 
93
        }
 
94
    };
 
95
    private static final class NormalChildKey {
 
96
        private final Node child;
 
97
        public NormalChildKey(Node child) {
 
98
            this.child = child;
 
99
        }
 
100
        public Node wrap() {
 
101
            return Wrapper.make(child);
 
102
        }
 
103
    }
 
104
    private static final class LookupProviderKey {
 
105
        public final Lookup.Provider p;
 
106
        public LookupProviderKey(Lookup.Provider p) {
 
107
            this.p = p;
 
108
        }
 
109
    }
 
110
    
 
111
    private Node original;
 
112
    private NodeListener nListener = null;
 
113
    private PropertyChangeListener fsListener = null;
 
114
    private FileSystem fileSystemToListenOn = null;
 
115
    
 
116
    WrapperKids(Node orig) {
 
117
        original = orig;
 
118
    }
 
119
    // Probably not needed:
 
120
    public Object clone() {
 
121
        return new WrapperKids(original);
 
122
    }
 
123
    
 
124
    /** Update all keys.
 
125
     * Keys may be:
 
126
     * <ol>
 
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.
 
132
     * </ol>
 
133
     */
 
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]));
 
142
            }
 
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);
 
150
                try {
 
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));
 
162
                    }
 
163
                } catch (Exception e) {
 
164
                    ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
 
165
                }
 
166
            } else {
 
167
                newkeys.add(new LookupProviderKey(original));
 
168
            }
 
169
            setKeys(newkeys);
 
170
            //System.err.println ("Setting keys for wrapper of " + original.getDisplayName () + "; count: " + newkeys.size ());
 
171
            if (addListeners) {
 
172
                nListener = new NodeAdapter() {
 
173
                    public void propertyChange(PropertyChangeEvent ev) {
 
174
                        if (Node.PROP_PROPERTY_SETS.equals(ev.getPropertyName())) {
 
175
                            updateKeys(false);
 
176
                        }
 
177
                    }
 
178
                    // Could instead override filterChildren* methods:
 
179
                    public void childrenAdded(NodeMemberEvent ev) {
 
180
                        updateKeys(false);
 
181
                    }
 
182
                    public void childrenRemoved(NodeMemberEvent ev) {
 
183
                        updateKeys(false);
 
184
                    }
 
185
                    public void childrenReordered(NodeReorderEvent ev) {
 
186
                        updateKeys(false);
 
187
                    }
 
188
                };
 
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())) {
 
194
                                updateKeys(false);
 
195
                            }
 
196
                        }
 
197
                    };
 
198
                    fileSystemToListenOn.addPropertyChangeListener(fsListener);
 
199
                }
 
200
            }
 
201
        }});
 
202
    }
 
203
    
 
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.
 
207
     */
 
208
    protected void addNotify() {
 
209
        //System.err.println ("addNotify called for wrapper of " + original.getDisplayName ());
 
210
        updateKeys(true);
 
211
    }
 
212
    
 
213
    protected void removeNotify() {
 
214
        if (nListener != null) {
 
215
            original.removeNodeListener(nListener);
 
216
            nListener = null;
 
217
        }
 
218
        if (fsListener != null) {
 
219
            fileSystemToListenOn.removePropertyChangeListener(fsListener);
 
220
            fsListener = null;
 
221
            fileSystemToListenOn = null;
 
222
        }
 
223
        setKeys(Collections.EMPTY_SET);
 
224
    }
 
225
    
 
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
 
233
     */
 
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)) {
 
246
                        useme = true;
 
247
                    }
 
248
                }
 
249
            }
 
250
            if (useme) toret.add(ps);
 
251
        }
 
252
        return toret;
 
253
    }
 
254
    
 
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
 
260
     */
 
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.
 
269
            try {
 
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");
 
274
                    }
 
275
                };
 
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)) };
 
281
            }
 
282
        } else if (key == instanceKey) {
 
283
            // Something which can provide an instance object--e.g. the deserialized object
 
284
            // from a .ser file.
 
285
            try {
 
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)) };
 
295
            }
 
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");
 
302
                }
 
303
            };
 
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) });
 
307
            toret.add(marker);
 
308
            if (key instanceof FilterNode) {
 
309
                // Try to separately show the original too:
 
310
                try {
 
311
                    Method m = FilterNode.class.getDeclaredMethod("getOriginal", new Class[] { });
 
312
                    m.setAccessible(true);
 
313
                    try {
 
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");
 
318
                            }
 
319
                        };
 
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) });
 
323
                        toret.add(marker2);
 
324
                    } finally {
 
325
                        m.setAccessible(false);
 
326
                    }
 
327
                } catch (Exception e) {
 
328
                    toret.add(Wrapper.make(PropSetKids.makeErrorNode(e)));
 
329
                }
 
330
            }
 
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");
 
339
                }
 
340
            };
 
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.
 
346
            try {
 
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)) };
 
352
            }
 
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.
 
359
            try {
 
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)));
 
365
            }
 
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");
 
370
                }
 
371
            };
 
372
            attrnode.setName("Attributes...");
 
373
            attrnode.setIconBaseWithExtension("org/netbeans/modules/apisupport/beanbrowser/BeanBrowserIcon.gif");
 
374
            l.add(attrnode);
 
375
            try {
 
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.
 
386
                            project = true;
 
387
                            break;
 
388
                        }
 
389
                        Method m = MultiFileSystem.class.getDeclaredMethod("findSystem", new Class[] {FileObject.class});
 
390
                        m.setAccessible(true);
 
391
                        try {
 
392
                            //FileObject workingFO = fs.findResource(path);
 
393
                            FileSystem foundFS = (FileSystem)m.invoke(fs, new Object[] {workingFO});
 
394
                            if (foundFS == fs) {
 
395
                                // no delegate
 
396
                                //System.err.println("no delegate for " + workingFO + " on " + fs);
 
397
                                break;
 
398
                            } else {
 
399
                                Method m2 = MultiFileSystem.class.getDeclaredMethod("findResourceOn", new Class[] {FileSystem.class, String.class});
 
400
                                m2.setAccessible(true);
 
401
                                try {
 
402
                                    FileObject newFO = (FileObject)m2.invoke(fs, new Object[] {foundFS, workingFO.getPath()});
 
403
                                    if (newFO != null) {
 
404
                                        //System.err.println("delegating " + fs + "/" + workingFO + " -> " + foundFS + "/" + newFO);
 
405
                                        fs = foundFS;
 
406
                                        workingFO = newFO;
 
407
                                    } else {
 
408
                                        //System.err.println("findResourceOn for " + fs + " with " + foundFS + " and " + workingFO + " -> null");
 
409
                                    }
 
410
                                } finally {
 
411
                                    m2.setAccessible(false);
 
412
                                }
 
413
                            }
 
414
                        } finally {
 
415
                            m.setAccessible(false);
 
416
                        }
 
417
                    }
 
418
                    if (project || fs != apparentFS) {
 
419
                        String provenance = null;
 
420
                        if (project) {
 
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";
 
430
                                    break;
 
431
                                }
 
432
                            }
 
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";
 
440
                                }
 
441
                            }
 
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";
 
446
                        }
 
447
                        if (provenance != null) {
 
448
                            //System.err.println("filesystem: " + fs + " path: " + workingFO);
 
449
                            l.add(PropSetKids.makePlainNode("Provenance: " + provenance));
 
450
                        } else {
 
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));
 
455
                        }
 
456
                    }
 
457
                }
 
458
            } catch (Exception e) {
 
459
                l.add(Wrapper.make(PropSetKids.makeErrorNode(e)));
 
460
            }
 
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");
 
467
                }
 
468
            };
 
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 };
 
476
        } else {
 
477
            throw new RuntimeException("Weird key: " + key);
 
478
        }
 
479
    }
 
480
    
 
481
}