org.openide.windows.TopComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.openide.windows.TopComponent.java

Source

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.windows;

import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.*;
import java.beans.*;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.Externalizable;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.lang.reflect.Method;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.text.Keymap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openide.ErrorManager;
import org.openide.awt.UndoRedo;
////import org.openide.loaders.*;
import org.openide.actions.*;
import org.openide.util.actions.SystemAction;
import org.openide.nodes.*;
import org.openide.util.ContextAwareAction;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.HelpCtx;
import org.openide.util.Utilities;

////import org.openide.modules.Dependency;
////import org.openide.modules.SpecificationVersion;

/** Embeddable visual component to be displayed in the IDE.
 * This is the basic unit of display in the IDE--windows should not be
 * created directly, but rather use this class.
 * A top component may correspond to a single window, but may also
 * be a tab (e.g.) in a window. It may be docked or undocked,
 * have selected nodes, supply actions, etc.
 *
 * Important serialization note: Serialization of this TopComponent is designed
 * in a way that it's not desired to override writeReplace method. If you would
 * like to resolve to something, please implement readResolve() method directly
 * on your top component.
 *
 * @author Jaroslav Tulach, Petr Hamernik, Jan Jancura
 */
public class TopComponent extends JComponent
        implements Externalizable, Accessible, HelpCtx.Provider, Lookup.Provider {
    /** generated Serialized Version UID */
    static final long serialVersionUID = -3022538025284122942L;
    private final static Log log = LogFactory.getLog(TopComponent.class);

    /** Behavior in which a top component closed (by the user) in one workspace
    * will be removed from <em>every</em> workspace.
    * Also, {@link #close} is called.
    * This is appropriate for top components such as Editor panes which
    * the user expects to really close (and prompt to save) when closed
    * in any workspace.
    */
    public static final int CLOSE_EACH = 0;
    /** Behavior in which a top component closed (by the user) in one workspace
    * may be left in other workspaces.
    * Only when the last remaining manifestation in any workspace is closed
    * will the object be deleted using {@link #close}.
    * Appropriate for components containing no user data, for which closing
    * the component is only likely to result from the user's wanting to remove
    * it from active view (on the current workspace).
    */
    public static final int CLOSE_LAST = 1;

    /** a set of actions of this component */
    private static SystemAction[] DEFAULT_ACTIONS;

    /** a lock for operations in default impl of getLookup */
    private static Object defaultLookupLock = new Object();

    /** reference to Lookup with default implementation for the 
     * component
     */
    private java.lang.ref.Reference defaultLookupRef = new WeakReference(null);

    /** Listener to the data object's node or null */
    private NodeName nodeName;

    /** manager for the component */
    private final WindowManager.Component manager;

    /** constant for desired close operation */
    private int closeOperation = CLOSE_LAST;

    /** identification of serialization version
    * Used in CloneableTopComponent readObject method.
    */
    short serialVersion = 1;

    /** Create a top component.
    */
    public TopComponent() {
        log.debug("using stand-alone GP TopComponent");
        enableEvents(java.awt.AWTEvent.KEY_EVENT_MASK);

        // there is no reason why a top component should have a focus
        // => let's disable it
        ////        if (Dependency.JAVA_SPEC.compareTo(new SpecificationVersion("1.4")) >= 0) {
        if (true) ////
            try {
                Method method = getClass().getMethod("setFocusable", new Class[] { Boolean.TYPE });
                method.invoke(this, new Object[] { new Boolean(false) });
            } catch (Exception ex) {
                ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
            }
        ////        } else {
        ////            setRequestFocusEnabled (false);
        ////        }
        // request creating of our manager - it's here to avoid
        // problems with recreating the connections between top components
        // and their managers during deserialization
        manager = WindowManager.getDefault().createTopComponentManager(this);
    }

    /** Create a top component associated with a data object.
    * Currently the data object is used to set the component's name
    * (which will be updated according to the object's node delegate) by
    * installing NodeName inner class and attaching it to the node delegate.
    * 
    * @param obj the data object
    */
    ////    public TopComponent (DataObject obj) {
    ////        this ();
    ////        Node n = obj.getNodeDelegate ();
    ////
    ////        nodeName = new NodeName (this);
    ////        nodeName.attach (n);
    ////
    ////        getAccessibleContext().setAccessibleDescription(n.getDisplayName());
    ////    }

    /** Getter for class that allows obtaining of information about components.
    * It allows to find out which component is selected, which nodes are 
    * currently or has been activated and list of all components.
    * 
    * @return the registry of components
    */
    public static final Registry getRegistry() {
        return WindowManager.getDefault().getRegistry();
    }

    /** Get the set of activated nodes in this component.
    * @return the activated nodes for this component
    */
    public final Node[] getActivatedNodes() {
        return getManager().getActivatedNodes();
    }

    /** Set the set of activated nodes in this component.
    * @param nodes activated nodes for this component
    */
    public final void setActivatedNodes(Node[] nodes) {
        getManager().setActivatedNodes(nodes);
        firePropertyChange("activatedNodes", null, null); // NOI18N
    }

    /** Get the undo/redo support for this component.
    * The default implementation returns a dummy support that cannot
    * undo anything.
    *
    * @return undoable edit for this component
    */
    public UndoRedo getUndoRedo() {
        return UndoRedo.NONE;
    }

    /** Show the component on current workspace.
    * Note that this method only makes it visible, but does not
    * give it focus. Implemented via call to open(null).
    * @see #requestFocus
    */
    public void open() {
        open(null);
    }

    /** Show the component on given workspace. If given workspace is
    * not active, component will be shown only after given workspace
    * will become visible.
    * Note that this method only makes it visible, but does not
    * give it focus.
    * @param workspace Workspace on which component should be opened.
    * Parameter can be null -> means current workspace.
    * @see #requestFocus
    */
    public void open(Workspace workspace) {
        getManager().open(workspace);
    }

    /** Finds out if this top component is opened at least on one workspace.
    * @return true if given top component is opened on at least
    * one workspace, false otherwise */
    public final boolean isOpened() {
        return getManager().whereOpened().size() > 0;
    }

    /** Finds out whether this top component is opened or not on specified
    * workspace.
    * @return true if given top component is opened on given workspace,
    * false otherwise */
    public final boolean isOpened(Workspace workspace) {
        return getManager().whereOpened().contains(workspace);
    }

    public void addNotify() {
        super.addNotify();
        if (!(getParent() instanceof TopComponent)) {
            //(TDB) Note: The constant below is defined for reference in NbTheme, but
            //since NbTheme is in core, cannot create a dependancy.
            setBorder(javax.swing.UIManager.getBorder("nb.TopComponent.border"));
        }
    }

    /** Closes the top component on current workspace.
    * First asks canClose() method to see if it is
    * possible to close now. If canClose() returns false, component will not
    * be closed.
    * Semantics of this method depends on top component's closeOperation
    * state. If closeOperation is set to CLOSE_LAST (default), top component
    * will be closed only on current workspace. If it is set to
    * CLOSE_EACH, if will be closed on all workspaces at once.
    *
    * @return true if top component was succesfully closed, false if 
    * top component for some reason refused to close.
    */
    public final boolean close() {
        return close(WindowManager.getDefault().getCurrentWorkspace());
    }

    /** Closes the top component on given workspace, if closeOperation
    * is set to CLOSE_LAST. If it is set to CLOSE_EACH, given parameter 
    * will be ignored and component will be closed on all workspaces
    * at once.
    *
    * @param workspace Workspace on which component should be closed.
    * @return true if top component was succesfully closed, false if 
    * top component for some reason refused to close.
    */
    public final boolean close(Workspace workspace) {
        Set whereOpened = getManager().whereOpened();
        // don't close multiple times
        if ((closeOperation != CLOSE_EACH) && !whereOpened.contains(workspace))
            return true;
        boolean result;
        switch (closeOperation) {
        case CLOSE_LAST:
            result = canClose(workspace, whereOpened.size() == 1);
            break;
        case CLOSE_EACH:
            result = canClose(null, true);
            break;
        default:
            throw new IllegalStateException("closeOperation=" + closeOperation); // NOI18N
        }
        if (result)
            getManager().close(workspace);
        return result;
    }

    /** This method is called when top component is about to close.
    * Allows subclasses to decide if top component is ready for closing
    * or not.<br>
    * Default implementation always return true.
    * 
    * @param workspace the workspace on which we are about to close or
    *                  null which means that component will be closed
    *                  on all workspaces where it is opened (CLOSE_EACH mode)
    * @param last true if this is last workspace where top component is
    *             opened, false otherwise. If close operation is set to
    *             CLOSE_EACH, then this param is always true
    * @return true if top component is ready to close, false otherwise.
    */
    public boolean canClose(Workspace workspace, boolean last) {
        return true;
    }

    /** Called only when top component was closed on all workspaces before and
     * now is opened for the first time on some workspace. The intent is to
     * provide subclasses information about TopComponent's life cycle across
     * all existing workspaces.
     * Subclasses will usually perform initializing tasks here.
     * @deprecated Use {@link #componentOpened} instead. */
    protected void openNotify() {
    }

    /** Called only when top component was closed so that now it is closed
     * on all workspaces in the system. The intent is to provide subclasses
     * information about TopComponent's life cycle across workspaces.
     * Subclasses will usually perform cleaning tasks here.
     * @deprecated Use {@link #componentClosed} instead.
     */
    protected void closeNotify() {
    }

    /** Gets the system actions which will appear in the popup menu of this component.
     * @return array of system actions for this component
     * @deprecated Use {@link #getActions()} instead.
     */
    public SystemAction[] getSystemActions() {
        // lazy inicialization
        synchronized (TopComponent.class) {
            if (DEFAULT_ACTIONS == null) {
                DEFAULT_ACTIONS = new SystemAction[] {
                        ////                    SystemAction.get(SaveAction.class),
                        SystemAction.get(CloneViewAction.class), null, SystemAction.get(CloseViewAction.class) };
            }
        }
        return DEFAULT_ACTIONS;
    }

    /** Gets the actions which will appear in the popup menu of this component.
     * <p>Subclasses are encouraged to override this method to specify
     * their own sets of actions.
     * <p>Remember to call the super method when overriding and add your actions
     * to the superclass' ones (in some order),
     * because the default implementation provides support for standard
     * component actions like save, close, and clone.
     * @return array of actions for this component
     * @since 3.32
     */
    public javax.swing.Action[] getActions() {
        return getSystemActions();
    }

    /** Set the close mode for the component.
    * @param closeOperation one of {@link #CLOSE_EACH} or {@link #CLOSE_LAST}
    * @throws IllegalArgumentException if an unrecognized close mode was supplied
    * @see #close()
    */
    public final void setCloseOperation(final int closeOperation) {
        if ((closeOperation != CLOSE_EACH) && (closeOperation != CLOSE_LAST))
            throw new IllegalArgumentException(
                    NbBundle.getBundle(TopComponent.class).getString("EXC_UnknownOperation"));
        if (this.closeOperation == closeOperation)
            return;
        this.closeOperation = closeOperation;
        firePropertyChange("closeOperation", null, null); // NOI18N
    }

    /** Get the current close mode for this component.
    * @return one of {@link #CLOSE_EACH} or {@link #CLOSE_LAST}
    */
    public final int getCloseOperation() {
        return closeOperation;
    }

    /** Called only when top component was closed on all workspaces before and
     * now is opened for the first time on some workspace. The intent is to
     * provide subclasses information about TopComponent's life cycle across
     * all existing workspaces.
     * Subclasses will usually perform initializing tasks here.
     * @since 2.18 */
    protected void componentOpened() {
        openNotify();
    }

    /** Called only when top component was closed so that now it is closed
     * on all workspaces in the system. The intent is to provide subclasses
     * information about TopComponent's life cycle across workspaces.
     * Subclasses will usually perform cleaning tasks here.
     * @since 2.18 */
    protected void componentClosed() {
        closeNotify();
    }

    /** Called when <code>TopComponent</code> is about to be shown.
     * Shown here means the component is selected or resides in it own cell
     * in container in its <code>Mode</code>. The container is visible and not minimized.
     * <p><em>Note:</em> component
     * is considered to be shown, even its container window
     * is overlapped by another window.</p>
     * @since 2.18 */
    protected void componentShowing() {
    }

    /** Called when <code>TopComponent</code> was hidden. <em>Nore</em>:
     * <p><em>Note:</em> Beside typical situations when component is hidden,
     * it is considered to be hidden even in that case
     * the component is in <code>Mode</code> container hierarchy,
     * the cointainer is visible, not minimized,
     * but the component is neither selected nor in its own cell,
     * i.e. it has it's own tab, but is not the selected one.
     * @since 2.18 */
    protected void componentHidden() {
    }

    /** Called when this component is activated.
    * This happens when the parent window of this component gets focus
    * (and this component is the preferred one in it), <em>or</em> when
    * this component is selected in its window (and its window was already focussed).
    * Remember to call the super method.
    * The default implementation does nothing.
    */
    protected void componentActivated() {
    }

    /** Called when this component is deactivated.
    * This happens when the parent window of this component loses focus
    * (and this component is the preferred one in the parent),
    * <em>or</em> when this component loses preference in the parent window
    * (and the parent window is focussed).
    * Remember to call the super method.
    * The default implementation does nothing.
    */
    protected void componentDeactivated() {
    }

    /** Request focus for the window holding this top component.
    * Also makes the component preferred in that window.
    * The component will <em>not</em> be automatically {@link #open opened} first
    * if it is not already.
    * <p>Subclasses should override this method to transfer focus to desired
    * focusable component. <code>TopComponent</code> itself is not focusable.
    * See for example {@link org.openide.text.CloneableEditor#requestFocus}.
    */
    public void requestFocus() {
        getManager().requestFocus();
        super.requestFocus();
    }

    /** Set this component visible but not selected or focused if possible.
    * If focus is in other container (multitab) or other pane (split) in 
    * the same container it makes this component only visible eg. it selects 
    * tab with this component.
    * If focus is in the same container (multitab) or in the same pane (split)
    * it has the same effect as requestFocus().
    */
    public void requestVisible() {
        getManager().requestVisible();
    }

    /** Set the name of this top component.
    * The default implementation just notifies the window manager.
    * @param name the new display name
    */
    public void setName(final String name) {
        String old = getName();
        if ((name != null) && (name.equals(old)))
            return;
        super.setName(name);
        firePropertyChange("name", old, name);

        getManager().nameChanged();
    }

    /** Sets toolTip for this <code>TopComponent</code>, adds notification
     * about the change to its <code>WindowManager.TopComponentManager</code>. */
    public void setToolTipText(String toolTip) {
        if (toolTip != null && toolTip.equals(getToolTipText())) {
            return;
        }

        super.setToolTipText(toolTip);
        // XXX #19428. Container updates name and tooltip in the same handler.
        getManager().nameChanged();
    }

    /** Set the icon of this top component.
    * The icon will be used for
    * the component's representation on the screen, e.g. in a multiwindow's tab.
    * The default implementation just notifies the window manager.
    * @param icon New components' icon.
    */
    public void setIcon(final Image icon) {
        getManager().setIcon(icon);
        firePropertyChange("icon", null, null); // NOI18N
    }

    /** @return The icon of the top component */
    public Image getIcon() {
        return getManager().getIcon();
    }

    /** Get the help context for this component.
    * Subclasses should generally override this to return specific help.
    * @return the help context
    */
    public HelpCtx getHelpCtx() {
        return new HelpCtx(TopComponent.class);
    }

    /** Allows top component to specify list of modes into which can be docked
     * by end user. Subclasses should override this method if they want to 
     * alter docking policy of top component. <p>
     * So for example, by returning empty list, top component refuses
     * to be docked anywhere. <p>
     * Default implementation allows docking anywhere by returning
     * input list unchanged.
     *
     * @param modes list of {@link Mode} which represent all modes of current
     * workspace, can contain nulls. Items are structured in logical groups
     * separated by null entries. <p>
     * Input array also contains special constant modes for docking
     * into newly created frames. Their names are "SingleNewMode", 
     * "MultiNewMode", "SplitNewMode", can be used for their
     * recognition. Please note that names and existence of special modes
     * can change in future releases.
     *
     * @return list of {@link Mode} which are available for dock, can contain nulls 
     * @since 2.14
     */
    public List availableModes(List modes) {
        return modes;
    }

    /** Overrides superclass method, adds possible additional handling of global keystrokes
     * in case this <code>TopComoponent</code> is ancestor of focused component. */
    ////    protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
    ////    int condition, boolean pressed) {
    ////        boolean ret = super.processKeyBinding(ks, e, condition, pressed);
    ////        
    ////        // XXX #30189 Reason of overriding: to process global shortcut.
    ////        if(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT == condition
    ////        && ret == false && !e.isConsumed()
    ////////        && (Dependency.JAVA_SPEC.compareTo(new SpecificationVersion("1.4")) >= 0)
    ////              ) { // NOI18N
    ////            Keymap km = (Keymap)Lookup.getDefault().lookup(Keymap.class);
    ////            Action action = km.getAction(ks);
    ////            
    ////            if(action == null) {
    ////                return false;
    ////            }
    ////            
    ////            // If necessary create context aware instance.
    ////            if(action instanceof ContextAwareAction) {
    ////                action = ((ContextAwareAction)action).createContextAwareInstance(getLookup());
    ////            } else if(SwingUtilities.getWindowAncestor(e.getComponent())
    ////            instanceof java.awt.Dialog) {
    ////                // #30303 For 'old type' actions check the transmodal flag,
    ////                // if invoked in dialog. See ShorcutAndMenuKeyEventProcessor in core.
    ////                Object value = action.getValue("OpenIDE-Transmodal-Action"); // NOI18N
    ////                if(!Boolean.TRUE.equals(value)) {
    ////                    return false;
    ////                }
    ////            }
    ////                
    ////            // #30600 We have to use ActionManager, which replanes performation
    ////            // of action to another thread, since our actions rely on that.
    //////            return SwingUtilities.notifyAction(
    //////                        action, ks, e, this, e.getModifiers());
    ////            ActionManager am = (ActionManager)Lookup.getDefault().lookup(ActionManager.class);
    ////            am.invokeAction(
    ////                action,
    ////                new ActionEvent(this, ActionEvent.ACTION_PERFORMED, Utilities.keyToString(ks))
    ////            );
    ////            
    ////            return true;
    ////        } else {
    ////            return ret;
    ////        }
    ////    }

    /** Getter for manager for this component. This manager allows to
    * control where is the component shown can be used to destroy and show the
    * component, etc.
    */
    final WindowManager.Component getManager() {
        return manager;
    }

    /** Serialize this top component.
    * Subclasses wishing to store state must call the super method, then write to the stream.
    * @param out the stream to serialize to
    */
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(new Short(serialVersion));

        out.writeInt(closeOperation);
        out.writeObject(getName());
        out.writeObject(getToolTipText());

        Node.Handle h = nodeName == null ? null : nodeName.node.getHandle();
        out.writeObject(h);
    }

    /** Deserialize this top component.
    * Subclasses wishing to store state must call the super method, then read from the stream.
    * @param in the stream to deserialize from
    */
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        throw new IOException("GP: restore not supported"); ////
        ////        Object firstObject = in.readObject ();
        ////        if (firstObject instanceof Integer) {
        ////            // backward compatibility read
        ////            serialVersion = 0;
        ////
        ////            closeOperation = ((Integer)firstObject).intValue();
        ////            DataObject obj = (DataObject)in.readObject();
        ////
        ////            super.setName((String)in.readObject());
        ////            setToolTipText((String)in.readObject());
        ////
        ////            // initialize the connection to a data object
        ////            if (obj != null) {
        ////                nodeName = new NodeName (this);
        ////                nodeName.attach (obj.getNodeDelegate ());
        ////            }
        ////        } else {
        ////            // new serialization
        ////            serialVersion = ((Short)firstObject).shortValue ();
        ////
        ////            closeOperation = in.readInt ();
        ////            super.setName ((String)in.readObject ());
        ////            setToolTipText ((String)in.readObject ());
        ////
        ////            Node.Handle h = (Node.Handle)in.readObject ();
        ////            if (h != null) {
        ////                Node n = h.getNode ();
        ////                nodeName = new NodeName (this);
        ////                nodeName.attach (n);
        ////            }
        ////        }
        ////        if (closeOperation != CLOSE_EACH && closeOperation != CLOSE_LAST) {
        ////            throw new IOException ("invalid closeOperation: " + closeOperation); // NOI18N
        ////        }
    }

    /** Delegates instance of replacer class to be serialized instead
    * of top component itself. Replacer class calls writeExternal and
    * constructor, readExternal and readResolve methods properly, so
    8 any top component can behave like any other externalizable object.
    * Subclasses can override this method to perform their
    * serialization differentrly */
    protected Object writeReplace() throws ObjectStreamException {
        return new Replacer(this);
    }

    /** Each top component that wishes to be cloned should implement
    * this interface, so CloneAction can check it and call the cloneComponent
    * method.
    */
    public static interface Cloneable {
        /** Creates a clone of this component
        * @return cloned component.
        */
        public TopComponent cloneComponent();
    }

    /* Read accessible context
     * @return - accessible context
     */
    public AccessibleContext getAccessibleContext() {
        if (accessibleContext == null) {
            accessibleContext = new AccessibleJComponent() {
                public AccessibleRole getAccessibleRole() {
                    return AccessibleRole.PANEL;
                }

                public String getAccessibleName() {
                    if (accessibleName != null) {
                        return accessibleName;
                    }
                    return getName();
                }

                /* Fix for 19344: Null accessible decription of all TopComponents on JDK1.4 */
                public String getToolTipText() {
                    return TopComponent.this.getToolTipText();
                }
            };
        }
        return accessibleContext;
    }

    /** Gets lookup which represents context of this component. By default
     * the lookup delegates to result of <code>getActivatedNodes</code>
     * method and result of this component <code>ActionMap</code> delegate.
     *
     * @return a lookup with designates context of this component
     * @see org.openide.util.ContextAwareAction
     * @see org.openide.util.Utilities#actionsToPopup(Action[], Lookup)
     * @since 3.29
     */
    public Lookup getLookup() {
        synchronized (defaultLookupLock) {
            Object l = defaultLookupRef.get();
            if (l instanceof Lookup) {
                return (Lookup) l;
            }

            Lookup lookup = new ProxyLookup(new Lookup[] { new DefaultTopComponentLookup(this), // Lookup of activated nodes.
                    Lookups.singleton(new DelegateActionMap(this)) // Action map lookup.
            });

            defaultLookupRef = new java.lang.ref.WeakReference(lookup);
            return lookup;
        }
    }

    /** This class provides the connection between the node name and
    * a name of the component.
    */
    public static class NodeName extends NodeAdapter {
        /** weak reference to the top component */
        private transient Reference top;
        /** node we are attached to or null */
        private transient Node node;

        /** Constructs new name adapter that
        * can be attached to any node and will listen on changes 
        * of its display name and modify the name of the component.
        *
        * @param top top compoonent to modify its name
        */
        public NodeName(TopComponent top) {
            this.top = new WeakReference(top);
        }

        /** Attaches itself to a given node.
        */
        final void attach(Node n) {
            TopComponent top = (TopComponent) this.top.get();
            if (top != null) {
                synchronized (top) {
                    // ok no change
                    if (n == node)
                        return;

                    // change the node we are attached to
                    if (node != null) {
                        node.removeNodeListener(this);
                    }
                    node = n;

                    if (n != null) {
                        n.addNodeListener(this);
                        top.setActivatedNodes(new Node[] { n });
                        top.setName(n.getDisplayName());
                    }
                }
            }
        }

        /** Listens to Node.PROP_DISPLAY_NAME.
        */
        public void propertyChange(PropertyChangeEvent ev) {
            TopComponent top = (TopComponent) this.top.get();
            if (top == null) {
                // stop listening if top component no longer exists
                if (ev.getSource() instanceof Node) {
                    Node n = (Node) ev.getSource();
                    n.removeNodeListener(this);
                }
                return;
            }

            // ensure we are attached
            attach(node);

            if (ev.getPropertyName().equals(Node.PROP_DISPLAY_NAME)) {
                top.setName(node.getDisplayName());
            }
        }
    } // end of NodeName

    /** Registry of all top components.
    * There is one instance that can be obtained via {@link TopComponent#getRegistry}
    * and it permits listening to the currently selected element, and to
    * the activated nodes assigned to it.
    */
    public static interface Registry {
        /** Name of property for the set of opened components. */
        public static final String PROP_OPENED = "opened"; // NOI18N
        /** Name of property for the selected top component. */
        public static final String PROP_ACTIVATED = "activated"; // NOI18N
        /** Name of property for currently selected nodes. */
        public static final String PROP_CURRENT_NODES = "currentNodes"; // NOI18N
        /** Name of property for lastly activated nodes nodes. */
        public static final String PROP_ACTIVATED_NODES = "activatedNodes"; // NOI18N

        /** Get reference to a set of all opened componets in the system.
        *
        * @return live read-only set of {@link TopComponent}s
        */
        public Set getOpened();

        /** Get the currently selected element.
        * @return the selected top component, or <CODE>null</CODE> if there is none
        */
        public TopComponent getActivated();

        /** Getter for the currently selected nodes.
        * @return array of nodes or null if no component activated or it returns
        *   null from getActivatedNodes ().
        */
        public Node[] getCurrentNodes();

        /** Getter for the lastly activated nodes. Comparing
        * to previous method it always remembers the selected nodes
        * of the last component that had ones.
        *
        * @return array of nodes (not null)
        */
        public Node[] getActivatedNodes();

        /** Add a property change listener.
        * @param l the listener to add
        */
        public void addPropertyChangeListener(PropertyChangeListener l);

        /** Remove a property change listener.
        * @param l the listener to remove
        */
        public void removePropertyChangeListener(PropertyChangeListener l);
    }

    /** Instance of this class is serialized instead of TopComponent itself.
    * Emulates behaviour of serialization of externalizable objects
    * to keep TopComponent serialization compatible with previous versions. */
    private static final class Replacer implements Serializable {
        /** SUID */
        static final long serialVersionUID = -8897067133215740572L;

        /** Asociation with top component which is to be serialized using
        * this replacer */
        transient TopComponent tc;

        public Replacer(TopComponent tc) {
            this.tc = tc;
        }

        private void writeObject(ObjectOutputStream oos) throws IOException, ClassNotFoundException {
            // write the name of the top component first
            oos.writeObject(tc.getClass().getName());
            // and now let top component to serialize itself
            tc.writeExternal(oos);
        }

        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
            // read the name of top component's class, instantiate it
            // and read its attributes from the stream
            String name = (String) ois.readObject();
            name = org.openide.util.Utilities.translate(name);
            try {
                Class tcClass = Class.forName(name, true,
                        (ClassLoader) Lookup.getDefault().lookup(ClassLoader.class));
                // instantiate class event if it has protected or private
                // default constructor
                java.lang.reflect.Constructor con = tcClass.getDeclaredConstructor(new Class[0]);
                con.setAccessible(true);
                try {
                    tc = (TopComponent) con.newInstance(new Object[0]);
                } finally {
                    con.setAccessible(false);
                }
                tc.readExternal(ois);
                // call readResolve() if present and use resolved value
                Method resolveMethod = findReadResolveMethod(tcClass);
                if (resolveMethod != null) {
                    // check exceptions clause
                    Class[] result = resolveMethod.getExceptionTypes();
                    if ((result.length == 1) && ObjectStreamException.class.equals(result[0])) {
                        // returned value type
                        if (Object.class.equals(resolveMethod.getReturnType())) {
                            // make readResolve accessible (it can have any access modifier)
                            resolveMethod.setAccessible(true);
                            // invoke resolve method and accept its result
                            try {
                                TopComponent unresolvedTc = tc;
                                tc = (TopComponent) resolveMethod.invoke(tc, new Class[0]);
                                if (tc == null) {
                                    throw new java.io.InvalidObjectException(
                                            "TopComponent.readResolve() cannot return null." // NOI18N
                                                    + " See http://www.netbeans.org/issues/show_bug.cgi?id=27849 for more info." // NOI18N
                                                    + " TopComponent:" + unresolvedTc); // NOI18N
                                }
                            } finally {
                                resolveMethod.setAccessible(false);
                            }
                        }
                    }
                }
            } catch (ClassNotFoundException exc) {
                //Bugfix #16408: Ignore missing classes from objectbrowser and icebrowser module
                if ((exc.getMessage().indexOf("org.netbeans.modules.objectbrowser") != -1)
                        || (exc.getMessage().indexOf("org.netbeans.modules.icebrowser") != -1)) {
                    tc = null;
                } else {
                    // turn all troubles into IOException
                    IOException newEx = new IOException(exc.getMessage());
                    ErrorManager.getDefault().annotate(newEx, exc);
                    throw newEx;
                }
            } catch (Exception exc) {
                Throwable th = exc;
                // Extract target exception.
                if (th instanceof InvocationTargetException) {
                    th = ((InvocationTargetException) th).getTargetException();
                }
                // IOException throw directly.
                if (th instanceof IOException) {
                    throw (IOException) th;
                }
                // All others wrap into IOException.
                IOException newEx = new IOException(th.getMessage());
                ErrorManager.getDefault().annotate(newEx, th);
                throw newEx;
            }
        }

        /** Resolve to original top component instance */
        private Object readResolve() throws ObjectStreamException {
            return tc;
        }

        /** Tries to find readResolve method in given class. Finds
        * both public and non-public occurences of the method and
        * searches also in superclasses */
        private static Method findReadResolveMethod(Class clazz) {
            Method result = null;
            // first try public occurences
            try {
                result = clazz.getMethod("readResolve", new Class[0]); // NOI18N
            } catch (NoSuchMethodException exc) {
                // public readResolve does not exist
            }
            // now try non-public occurences; search also in superclasses
            for (Class i = clazz; i != null; i = i.getSuperclass()) {
                try {
                    result = i.getDeclaredMethod("readResolve", new Class[0]); // NOI18N
                    // get out of cycle if method found
                    break;
                } catch (NoSuchMethodException exc) {
                    // readResolve does not exist in current class
                }
            }
            return result;
        }

    } // end of Replacer inner class

}