/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
package org.openide.nodes;

import org.openide.ErrorManager;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.actions.SystemAction;
import org.openide.util.datatransfer.NewType;
import org.openide.util.datatransfer.PasteType;

import java.awt.Image;
import java.awt.datatransfer.Transferable;

import java.beans.FeatureDescriptor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyEditor;

import java.io.IOException;

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;

import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;

import javax.swing.Action;
import javax.swing.JPopupMenu;
import javax.swing.event.EventListenerList;


/** A <em>node</em> represents one element in a hierarchy of objects (beans).
* It provides all methods that are needed for communication between
* the IDE and the bean.
* <P>
* The node has three purposes:
* <OL>
*   <LI>visually represent the object in the tree hierarchy (i.e. Explorer)
*   <LI>provide sets of properties for that object (i.e. Component Inspector, Property Sheet)
*   <LI>offer actions to perform on itself
* </OL>
* <P>
* Frequently nodes are created to represent <code>DataObject</code>s.
* But they may also represent anything to be displayed to the user or manipulated programmatically,
* even if they have no data directly stored behind them; for example, a control panel or debugger
* breakpoint.
* <P>
* There are two listeners in this class: {@link PropertyChangeListener}
* and {@link NodeListener} (which extends <code>PropertyChangeListener</code>). The first
* is designed to listen on properties that can be returned from
* {@link #getPropertySets}, the later for listening on changes in the
* node itself (including the name, children, parent, set of properties,
* icons, etc.). Be sure to distinguish between these two.
* <P>
* The node is cloneable. When a node is cloned, it is initialized
* with an empty set of listeners and no parent. The display name and short description
* are copied to the new node. The set of properties is <em>shared</em>.
* <P>
* Implements {@link org.openide.util.Lookup.Provider} since 3.11.
*
* @author Jaroslav Tulach,
*/
public abstract class Node extends FeatureDescriptor implements Lookup.Provider, HelpCtx.Provider {
    /** An empty leaf node. */
    public static final Node EMPTY = new AbstractNode(Children.LEAF);

    /* here is list of property names that can be changed in the Node object.
    * These properties can be notified to the <CODE>NodeListener</CODE>.
    */

    /** Property for node display name. */
    public static final String PROP_DISPLAY_NAME = "displayName"; // NOI18N

    /** Property for internal (not displayable) name of a node. This name is often used to
    * look up a node in the hierarchy, so it should be chosen to be simple.
    */
    public static final String PROP_NAME = "name"; // NOI18N

    /** Property for short description of a node. */
    public static final String PROP_SHORT_DESCRIPTION = "shortDescription"; // NOI18N

    /** Property for the normal (closed) icon of a node. */
    public static final String PROP_ICON = "icon"; // NOI18N

    /** Property for the opened icon of a node. */
    public static final String PROP_OPENED_ICON = "openedIcon"; // NOI18N

    /** Property for a node's parent. */
    public static final String PROP_PARENT_NODE = "parentNode"; // NOI18N

    /** Property for a node's list of property sets. */
    public static final String PROP_PROPERTY_SETS = "propertySets"; // NOI18N

    /** Property for a node's cookie set. */
    public static final String PROP_COOKIE = "cookie"; // NOI18N

    /** Property saying whether the Node is Leaf
     *@since 3.1
     */
    public static final String PROP_LEAF = "leaf"; // NOI18N

    /** Error manager used for logging and its precomputed doLog and doWarn
     * booleans to speedup the check whether to log or not. See #35039
     */
    private static final ErrorManager err = ErrorManager.getDefault().getInstance("org.openide.nodes.Node"); //NOI18N;
    private static final boolean doLog = err.isLoggable(ErrorManager.INFORMATIONAL);
    private static final boolean doWarn = err.isLoggable(ErrorManager.WARNING);

    /** cache of all created lookups */
    private static WeakHashMap lookups = new WeakHashMap(37);

    /** class.property names we have warned about for #31413 */
    private static final Set warnedBadProperties = new HashSet(100); // Set<String>

    /** template for changes in cookies */
    private static final Lookup.Template TEMPL_COOKIE = new Lookup.Template(Node.Cookie.class);

    /** Lock for initialication */
    private static final Object INIT_LOCK = new Object();

    /** children representing parent node (Children or ChildrenArray),
    * for synchronization reasons must be changed only
    * under the Children.MUTEX lock
    */
    private Object parent;

    /** children list, for synch. reasons change only under Children.MUTEX
    * lock
    */
    Children hierarchy;

    /** listeners for changes in hierarchy.
    */
    private transient EventListenerList listeners;

    /** Creates a new node with a given hierarchy of children.
    * @param h the children to use for this node
    * @exception IllegalStateException if the children object is already in use by
    *   a different node
    */
    protected Node(Children h) throws IllegalStateException {
        this(h, null);
    }

    /** Creates a new node with a given hierarchy of children and a lookup
    * providing content for {@link #getCookie} and {@link #getLookup} methods.
    * <p>
    * As the lookup needs to be constucted before Node's constructor is called,
    * it might not be obvious how to add Node or other objects into it without
    * type casting. Here is the recommended suggestion that uses public/private
    * pair of constructors:
    * <PRE>
    <span class="keyword">public</span> <span class="function-name">MyNode</span><span class="constant">(</span><span class="variable-name">Children</span> <span class="variable-name">ch</span><span class="constant">,</span> <span class="variable-name">FileObject</span> <span class="variable-name">fil</span><span class="variable-name">e</span><span class="constant">)</span> <span class="constant">{</span>
        <span class="keyword">this</span><span class="constant">(</span><span class="variable-name">ch</span><span class="constant">, </span><span class="variable-name">file</span><span class="constant">,</span> <span class="keyword">new</span> <span class="function-name">InstanceContent</span><span class="constant">(</span><span class="constant">)</span><span class="constant">)</span><span class="constant">;</span>
    <span class="constant">}</span>

    <span class="comment">/** A private constructor that takes an InstanceContent and</span>
    <span class="comment">     * uses it as internals for the Node lookup and also allow us</span>
    <span class="comment">     * to modify the content, for example by adding a reference </span>
    <span class="comment">     * to the node itself or any other object we want to represent.</span>
    <span class="comment">     *</span>
    <span class="comment">     * @param ch children we wish to use</span>
    <span class="comment">     * @param file sample object we wish to have in lookup</span>
    <span class="comment">     * @param content the content created by the first constructor</span>
    <span class="comment">     *<b></b>/</span>
    <span class="keyword">private</span> <span class="function-name">MyNode</span><span class="constant">(</span><span class="variable-name">Children</span> <span class="variable-name">ch</span><span class="constant">,</span> <span class="variable-name">FileObject</span> <span class="variable-name">file</span><span class="constant">,</span> <span class="variable-name">InstanceContent</span> <span class="variable-name">content</span><span class="constant">)</span> <span class="constant">{</span>
        <span class="keyword">super</span><span class="constant">(</span><span class="variable-name">ch</span><span class="constant">,</span> <span class="keyword">new</span> <span class="function-name">AbstractLookup</span><span class="constant">(</span><span class="variable-name">content</span><span class="constant">)</span><span class="constant">)</span><span class="constant">;</span>
        <span class="comment">// adds the node to our own lookup</span>
        <span class="variable-name">content</span><span class="constant">.</span><span class="function-name">add</span> <span class="constant">(</span><span class="keyword">this</span><span class="constant">)</span><span class="constant">;</span>
        <span class="comment">// adds aditional items to the lookup</span>
        <span class="variable-name">content</span><span class="constant">.</span><span class="function-name">add</span> <span class="constant">(</span><span class="variable-name">file</span><span class="constant">)</span><span class="constant">;</span>
    <span class="constant">}</span>
    </PRE>
    *
    * @since 3.11
    * @param h the children to use for this node
    * @param lookup the lookup to provide content of {@link #getLookup}
    *   and also {@link #getCookie}
    * @exception IllegalStateException if the children object is already in use by
    *   a different node
    */
    protected Node(Children h, Lookup lookup) throws IllegalStateException {
        this.hierarchy = h;

        // allow subclasses (FilterNode) to update the lookup
        lookup = replaceProvidedLookup(lookup);

        if (lookup != null) {
            this.listeners = new LookupEventList(lookup);
        } else {
            this.listeners = new EventListenerList();
        }

        // attaches to this node
        h.attachTo(this);
    }

    /** Method for subclasses to modify provided lookup before its use.
     * This implementation does nothing.
     */
    Lookup replaceProvidedLookup(Lookup l) {
        return l;
    }

    /** Method that gives access to internal lookup.
     * @param init should it be really initialized (ready for queries) or need not be
     * @return lookup or null
     */
    final Lookup internalLookup(boolean init) {
        if (listeners instanceof LookupEventList) {
            return ((LookupEventList) listeners).init(init);
        } else {
            return null;
        }
    }

    /** Implements {@link Object#clone} to behave correctly if cloning is desired.
    * But {@link Cloneable} is <em>not</em> declared by default.
    * <P>
    * The default implementation checks whether the child list implements
    * <code>Cloneable</code>, and if so, it clones the children.
    * If the child list does not support cloning, {@link Children#LEAF} is used
    * instead for the children. The parent of this node is set to <code>null</code> and an empty set
    * of listeners is attached to the node.
    *
    * @return the cloned version of the node
    * @exception CloneNotSupportedException if the children cannot be cloned
    *    in spite of implementing <code>Cloneable</code>
    */
    protected Object clone() throws CloneNotSupportedException {
        Node n = (Node) super.clone();
        Children hier2;

        if (hierarchy instanceof Cloneable) {
            hier2 = (Children) hierarchy.cloneHierarchy();
        } else {
            hier2 = Children.LEAF;
        }

        // attach the hierarchy
        n.hierarchy = hier2;
        hier2.attachTo(n);

        // no parent
        n.parent = null;

        // empty set of listeners
        if (listeners instanceof LookupEventList) {
            n.listeners = new LookupEventList(internalLookup(false));
        } else {
            n.listeners = new EventListenerList();
        }

        return n;
    }

    /** Clone the node. The newly created node should reference the same
    * object is this node does, but it should not be inserted as a child
    * to any other node. Also it should have an empty set of listeners.
    * In all other respects the node should behave exactly as the
    * original one does.
    *
    * @return copy of this node
    */
    public abstract Node cloneNode();

    /** Finds the children we are attached to.
     * @return children
     */
    private Children getParentChildren() {
        return (this.parent instanceof ChildrenArray) ? ((ChildrenArray) this.parent).getChildren()
                                                      : (Children) this.parent;
    }

    /** Method that allows Children to change the parent children of
    * the node when the node is add to a children.
    *
    * @param parent the children that wants to contain this node
    * @param index index that will be assigned to this node
    * @exception IllegalStateException if this node already belongs to a children
    */
    final synchronized void assignTo(Children parent, int index) {
        Children ch = getParentChildren();

        if ((ch != null) && (ch != parent)) {
            throw new IllegalStateException(
                "Cannot initialize " + index + "th child of node " + parent.getNode() +
                "; it already belongs to node " + ch.getNode()
            ); // NOI18N
        }

        if (!(this.parent instanceof ChildrenArray)) {
            this.parent = parent;
        }
    }

    /** Code that reasignes the reference from to parent from its
     * Children to its ChildrenArray.
     */
    final synchronized void reassignTo(Children currentParent, ChildrenArray itsArray) {
        if ((this.parent != currentParent) && (this.parent != itsArray)) {
            throw new IllegalStateException(
                "Unauthorized call to change parent: " + this.parent + " and should be: " + currentParent
            );
        }

        this.parent = itsArray;
    }

    /** Deassignes the node from a children, when it is removed from
    * a children.
    */
    final synchronized void deassignFrom(Children parent) {
        Children p = getParentChildren();

        if (parent != p) {
            throw new IllegalArgumentException("Deassign from wrong parent. Old: " + p + " Caller: " + parent); //NOI18N
        }

        this.parent = null;
    }

    /** Set the system name. Fires a property change event.
    * @param s the new name
    * @exception IllegalArgumentException if the new name cannot represent
    *    a valid node name
    */
    public void setName(String s) {
        String name = super.getName();

        if ((name == null) || !name.equals(s)) {
            super.setName(s);
            fireNameChange(name, s);
        }
    }

    /** Set the display name. Fires a property change event.
    * @param s the new name
    */
    public void setDisplayName(String s) {
        String displayName = super.getDisplayName();

        if ((displayName == null) || !displayName.equals(s)) {
            super.setDisplayName(s);
            fireDisplayNameChange(displayName, s);
        }
    }

    /** Set the short description of the node. Fires a property change event.
    * <p>This description may be used for tool tips, etc.
    * @param s the new description
    */
    public void setShortDescription(String s) {
        String descr = super.getShortDescription();

        if ((descr == null) || !descr.equals(s)) {
            super.setShortDescription(s);
            fireShortDescriptionChange(descr, s);
        }
    }

    /** Find an icon for this node (in the closed state).
    * @param type constant from {@link java.beans.BeanInfo}
    * @return icon to use to represent the node
    */
    public abstract Image getIcon(int type);

    /** Find an icon for this node (in the open state).
    * This icon is used when the node may have children and is expanded.
    *
    * @param type constant from {@link java.beans.BeanInfo}
    * @return icon to use to represent the node when open
    */
    public abstract Image getOpenedIcon(int type);

    /** Get context help associated with this node.
    * @return the context help object (could be <code>null</code> or {@link HelpCtx#DEFAULT_HELP})
    */
    public abstract HelpCtx getHelpCtx();

    /** Get the list of children.
    * @return the children
    */
    public final Children getChildren() {
        updateChildren();

        return hierarchy;
    }

    /** Can be overriden in subclasses (probably in FilterNode) to check
     * whether children are of the right subclass
     */
    void updateChildren() {
    }

    /** Allows to change Children of the node. Call to this method aquires
     * write lock on the nodes hierarchy. Take care not to call this method
     * under read lock.<BR>
     *
     * @param ch New children to be set on the node.
     * @since 3.1
     */
    protected final void setChildren(Children ch) {
        try {
            Children.PR.enterWriteAccess();

            Node[] oldNodes = null;

            if (hierarchy.isInitialized()) {
                oldNodes = hierarchy.getNodes();
            }
            hierarchy.detachFrom();

            boolean wasLeaf = hierarchy == Children.LEAF;

            hierarchy = ch;
            hierarchy.attachTo(this);

            if (wasLeaf != (hierarchy == Children.LEAF)) {
                fireOwnPropertyChange(
                    PROP_LEAF, wasLeaf ? Boolean.TRUE : Boolean.FALSE,
                    (hierarchy == Children.LEAF) ? Boolean.TRUE : Boolean.FALSE
                );
            }

            if ((oldNodes != null) && !wasLeaf) {
                fireSubNodesChange(false, oldNodes, oldNodes);
                fireSubNodesChange(true, hierarchy.getNodes(), null);
            }
        } finally {
            Children.PR.exitWriteAccess();
        }
    }

    /** Test whether the node is a leaf, or may contain children.
    * @return <code>true</code> if the children list is actually {@link Children#LEAF}
    */
    public final boolean isLeaf() {
        updateChildren();

        return hierarchy == Children.LEAF;
    }

    /** Get the parent node.
    * @return the parent node, or <CODE>null</CODE> if this node is the root of a hierarchy
    */
    public final Node getParentNode() {
        // if contained in a list return its parent node
        Children ch = getParentChildren();

        return (ch == null) ? null : ch.getNode();
    }

    /** Test whether this node can be renamed.
    * If true, one can use {@link #getName} to obtain the current name and
    * {@link #setName} to change it.
    *
    * @return <code>true</code> if the node can be renamed
    */
    public abstract boolean canRename();

    /** Test whether this node can be deleted.
    * @return <CODE>true</CODE> if can
    */
    public abstract boolean canDestroy();

    // [PENDING] "valid" property?  --jglick // NOI18N

    /** Remove the node from its parent and deletes it.
    * The default
    * implementation obtains write access to
    * the {@link Children#MUTEX children's lock}, and removes
    * the node from its parent (if any). Also fires a property change.
    * <P>
    * This may be overridden by subclasses to do any additional
    * cleanup.
    *
    * @exception IOException if something fails
    */
    public void destroy() throws IOException {
        Children.MUTEX.postWriteRequest(
            new Runnable() {
                public void run() {
                    Children p = getParentChildren();

                    if (p != null) {
                        // remove itself from parent
                        p.remove(new Node[] { Node.this });
                    }

                    // sets the valid flag to false and fires prop. change
                    fireNodeDestroyed();
                }
            }
        );
    }

    /** Get the list of property sets for this node.
    * E.g. typically there may be one for normal Bean properties, one for expert
    * properties, and one for hidden properties.
    *
    * @return the property sets
    */
    public abstract PropertySet[] getPropertySets();

    /** Called when a node is to be copied to the clipboard.
    * @return the transferable object representing the
    *    content of the clipboard
    * @exception IOException when the
    *    copy cannot be performed
    */
    public abstract Transferable clipboardCopy() throws IOException;

    /** Called when a node is to be cut to the clipboard.
    * @return the transferable object representing the
    *    content of the clipboard
    * @exception IOException when the
    *    cut cannot be performed
    */
    public abstract Transferable clipboardCut() throws IOException;

    /** Called when a drag is started with this node.
    * The node can attach a transfer listener to ExTransferable and
    * will be then notified about progress of the drag (accept/reject).
    *
    * @return transferable to represent this node during a drag
    * @exception IOException if a drag cannot be started
    */
    public abstract Transferable drag() throws IOException;

    /** Test whether this node permits copying.
    * @return <code>true</code> if so
    */
    public abstract boolean canCopy();

    /** Test whether this node permits cutting.
    * @return <code>true</code> if so
    */
    public abstract boolean canCut();

    /** Determine which paste operations are allowed when a given transferable is in the clipboard.
    * For example, a node representing a Java package will permit classes to be pasted into it.
    * @param t the transferable in the clipboard
    * @return array of operations that are allowed
    */
    public abstract PasteType[] getPasteTypes(Transferable t);

    /** Determine if there is a paste operation that can be performed
    * on provided transferable. Used by drag'n'drop code to check
    * whether the drop is possible.
    *
    * @param t the transferable
    * @param action the drag'n'drop action to do DnDConstants.ACTION_MOVE, ACTION_COPY, ACTION_LINK
    * @param index index between children the drop occured at or -1 if not specified
    * @return null if the transferable cannot be accepted or the paste type
    *    to execute when the drop occures
    */
    public abstract PasteType getDropType(Transferable t, int action, int index);

    /** Get the new types that can be created in this node.
    * For example, a node representing a Java package will permit classes to be added.
    * @return array of new type operations that are allowed
    */
    public abstract NewType[] getNewTypes();

    /** Get the set of actions that are associated with this node.
     * This set is used to construct the popup menu for the node.
     *
     * <P>
     * By default this method delegates to the deprecated getActions or getContextActions
     * method depending on the value of suplied argument.
     * <P>
     * It is supposed to be overridden by subclasses accordingly.
     *
     * @param context whether to find actions for context meaning or for the
     *   node itself
     * @return a list of actions (you may include nulls for separators)
     * @since 3.29
     */
    public Action[] getActions(boolean context) {
        return context ? getContextActions() : getActions();
    }

    /** Get the set of actions associated with this node.
    * This may be used e.g. in constructing a {@link #getContextMenu context menu}.
    *
    * <P>
    * By default returns the actions in {@link NodeOp#getDefaultActions}.
    *
    * @return system actions appropriate to the node
    * @deprecated Use getActions (false) instead.
    */
    public SystemAction[] getActions() {
        return NodeOp.getDefaultActions();
    }

    /** Get a special set of actions
    * for situations when this node is displayed as a context.
    * <p>For example, right-clicking on a parent node in a hierarchical view (such as
    * the normal Explorer) should use <code>getActions</code>. However, if this node
    * is serving as the parent of a (say) a window tab full of icons (e.g., in
    * {@link org.openide.explorer.view.IconView}), and the users right-clicks on
    * the empty space in this pane, then this method should be used to get
    * the appropriate actions for a popup menu.
    * <p>Note that in the Windows UI system, e.g., these action sets are quite different.
    *
    * @return actions for a context. In the default implementation, same as {@link #getActions}.
    * @deprecated Use getActions (true) instead.
    */
    public SystemAction[] getContextActions() {
        return getActions();
    }

    /** Gets the default action for this node.
     * @return <code>null</code> indicating there should be none default action
     * @deprecated Use {@link #getPreferredAction} instead.
     */
    public SystemAction getDefaultAction() {
        return null;
    }

    /** Gets the preferred action for this node.
     * This action can but need not to be one from the action array returned
     * from {@link #getActions(boolean)}.
     * In case it is, the popup menu created from those actions
     * is encouraged to highlight the preferred action.
     * Override in subclasses accordingly.
     *
     * @return the preferred action, or <code>null</code> if there is none
     * @since 3.29
     */
    public Action getPreferredAction() {
        return getDefaultAction();
    }

    /** Make a context menu for this node.
    * The menu is constructed from the set of actions returned by {@link #getActions}.
    *
    * @return the popup menu
    */
    public final JPopupMenu getContextMenu() {
        return NodeOp.findContextMenuImpl(new Node[] { this }, null);
    }

    /** Test whether there is a customizer for this node. If true,
    * the customizer can be obtained via {@link #getCustomizer}.
    *
    * @return <CODE>true</CODE> if there is a customizer
    */
    public abstract boolean hasCustomizer();

    /** Get the customizer component.
    * @return the component, or <CODE>null</CODE> if there is no customizer
    */
    public abstract java.awt.Component getCustomizer();

    /** Get a cookie for this node.
    * <P>
    * The set of cookies can change. If a node changes its set of
    * cookies, it fires a property change event with {@link #PROP_COOKIE}.
    * <P>
    * If the Node was constructed with a <code>Lookup</code> in constructor
    * than this method delegates to the provided lookup object.
    *
    * @param type the representation class of the cookie
    * @return a cookie assignable to that class, or <code>null</code> if this node has no such cookie
    * @see Lookup
    */
    public Node.Cookie getCookie(Class type) {
        Lookup l = internalLookup(true);

        if (l != null) {
            Object o = l.lookup(type);

            if (o instanceof Node.Cookie) {
                return (Node.Cookie) o;
            }
        }

        return null;
    }

    /** Obtains a Lookup represeting additional content of this Node.
     * If the lookup was provided in a constructor, it is returned here,
     * if not, a lookup based on the content of <link>getCookie</link>
     * method is provided.
     *
     * @return lookup for this node
     * @since 3.11
     */
    public final Lookup getLookup() {
        synchronized (listeners) {
            Lookup l = internalLookup(true);

            if (l != null) {
                return l;
            }

            l = findDelegatingLookup();

            if (l != null) {
                return l;
            }

            // create new lookup and use it
            NodeLookup nl = new NodeLookup(this);
            registerDelegatingLookup(nl);

            return nl;
        }
    }

    /** Return a variant of the display name containing HTML markup
     * conforming to the limited subset of font-markup HTML supported by
     * the lightweight HTML renderer <code>org.openide.awt.HtmlRenderer</code>
     * (font color, bold, italic and strikethrough supported; font
     * colors can be UIManager color keys if they are prefixed with
     * a ! character, i.e. <samp>&lt;font color='!controlShadow'&gt;</samp>).
     * Enclosing <samp>&lt;html&gt;</samp> tags are not needed. If returning non-null, HTML
     * markup characters that should be literally renderered must be
     * escaped (<samp>&gt;</samp> becomes <samp>&amp;gt;</samp> and so forth).
     * <p><strong>This method should return either an HTML display name
     * or null; it should not return the non-HTML display name.</strong>
     * <p>
     * Note there is no property corresponding to the HTML display name -
     * if it should change, a change in the display name should be fired; this
     * should not be a mechanism for returning anything other than a marked
     * up version of the return value of <code>getDisplayName</code>.
     *
     * @see org.openide.awt.HtmlRenderer
     * @since 4.30
     * @return a String containing conformant HTML markup which
     *  represents the display name, or null.  The default implementation
     *  returns null.  */
    public String getHtmlDisplayName() {
        return null;
    }

    /** Register delegating lookup so it can always be found.
     */
    final void registerDelegatingLookup(NodeLookup l) {
        // to have just one thread accessing the static lookups variable
        synchronized (lookups) {
            lookups.put(listeners, new WeakReference(l));
        }
    }

    /** Finds delegating lookup that was previously registered
     * @return the lookup or null if nothing was registed or the
     *    lookup was GCed.
     */
    final Lookup findDelegatingLookup() {
        WeakReference ref = (WeakReference) lookups.get(listeners);

        return (ref == null) ? null : (Lookup) ref.get();
    }

    /** Obtain handle for this node (for serialization).
    * The handle can be serialized and {@link Handle#getNode} used after
    * deserialization to obtain the original node.
    *
    * @return the handle, or <code>null</code> if this node is not persistable
    */
    public abstract Node.Handle getHandle();

    /** Add a listener to changes in the node's intrinsic properties (name, cookies, etc.).
     * <P>
     * The listener is not notified about changes in subnodes until the
     * method <CODE>getChildren().getNodes()</CODE> is called.
     * @param l the listener to add
     */
    public final void addNodeListener(NodeListener l) {
        listeners.add(NodeListener.class, l);
        listenerAdded();
    }

    /** A method to notify FilterNode that a listenerAdded has been added */
    void listenerAdded() {
    }

    /** Remove a node listener.
     * @param l the listener
    */
    public final void removeNodeListener(NodeListener l) {
        listeners.remove(NodeListener.class, l);
    }

    /** Add a listener to the node's computed Bean properties.
     * @param l the listener
    */
    public final void addPropertyChangeListener(PropertyChangeListener l) {
        int count = -1;

        if (doLog) {
            count = getPropertyChangeListenersCount();
        }

        listeners.add(PropertyChangeListener.class, l);

        if (doLog) {
            err.log(
                ErrorManager.INFORMATIONAL,
                "ADD - " + getName() + " [" + count + "]->[" + getPropertyChangeListenersCount() + "] " + l
            );
        }

        notifyPropertyChangeListenerAdded(l);
    }

    /** Called to notify subclasses (FilterNode) about addition of
     * PropertyChangeListener.
     */
    void notifyPropertyChangeListenerAdded(PropertyChangeListener l) {
    }

    /** Returns the number of property change listeners attached to this node
     */
    int getPropertyChangeListenersCount() {
        return listeners.getListenerCount(PropertyChangeListener.class);
    }

    /** Allows to figure out, whether the node has any
     * PropertyChangeListeners attached.
     * @return True if node has one or more PropertyChangeListeners attached.
     * @since 1.36
     */
    protected final boolean hasPropertyChangeListener() {
        return getPropertyChangeListenersCount() > 0;
    }

    /** Remove a Bean property change listener.
     * @param l the listener
    */
    public final void removePropertyChangeListener(PropertyChangeListener l) {
        int count = -1;

        if (doLog) {
            count = getPropertyChangeListenersCount();
        }

        listeners.remove(PropertyChangeListener.class, l);

        if (doLog) {
            err.log(
                ErrorManager.INFORMATIONAL,
                "RMV - " + getName() + " [" + count + "]->[" + getPropertyChangeListenersCount() + "] " + l
            );
        }

        notifyPropertyChangeListenerRemoved(l);
    }

    /** Called to notify subclasses (FilterNode) about removal of
     * PropertyChangeListener.
     */
    void notifyPropertyChangeListenerRemoved(PropertyChangeListener l) {
    }

    /** Fire a property change event.
    *
    * @param name name of changed property (from {@link #getPropertySets}); may be null
    * @param o old value; may be null
    * @param n new value; may be null
    * @see PropertyChangeEvent
    */
    protected final void firePropertyChange(String name, Object o, Object n) {
        // First check if this property actually exists - if not warn! See #31413.
        if (doWarn && (name != null) && propertySetsAreKnown()) {
            Node.PropertySet[] pss = getPropertySets();
            boolean exists = false;

            for (int i = 0; i < pss.length; i++) {
                Node.Property[] ps = pss[i].getProperties();

                for (int j = 0; j < ps.length; j++) {
                    if (ps[j].getName().equals(name)) {
                        exists = true;

                        break;
                    }
                }
            }

            if (!exists) {
                synchronized (warnedBadProperties) {
                    String clazz = getClass().getName();

                    if (warnedBadProperties.add(clazz + "." + name)) {
                        ErrorManager.getDefault().notify(
                            ErrorManager.INFORMATIONAL,
                            new IllegalStateException(
                                "Warning - the node \"" + getDisplayName() + "\" [" + clazz +
                                "] is trying to fire the property " + name +
                                " which is not included in its property sets. This is illegal. See IZ #31413 for details."
                            )
                        ); // NOI18N
                    }
                }
            }
        }

        // do not fire if the values are the same
        if ((o != null) && (n != null) && ((o == n) || o.equals(n))) {
            return;
        }

        PropertyChangeEvent ev = null;

        Object[] listeners = this.listeners.getListenerList();

        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == PropertyChangeListener.class) {
                // Lazily create the event:
                if (ev == null) {
                    ev = new PropertyChangeEvent(this, name, o, n);
                }

                ((PropertyChangeListener) listeners[i + 1]).propertyChange(ev);
            }
        }
    }

    /**
     * If true, property sets have definitely been computed, and it is fine
     * to call {@link #getPropertySets} without fear of killing laziness.
     * Used from {@link #firePropertyChange} to only check for bad properties
     * if the set of properties has already been computed. Otherwise, don't
     * bother. Subclasses may override - {@link AbstractNode} does.
     */
    boolean propertySetsAreKnown() {
        return false;
    }

    /** Allow subclasses that override the getName method to fire
    * the changes of the name by itself. Please notice that default
    * implementation of setName will fire the change by itself.
    */
    protected final void fireNameChange(String o, String n) {
        fireOwnPropertyChange(PROP_NAME, o, n);
    }

    /** Allow subclasses that override the getDisplayName method to fire
    * the changes of the name by itself. Please notice that default
    * implementation of setDisplayName will fire the change by itself.
    */
    protected final void fireDisplayNameChange(String o, String n) {
        fireOwnPropertyChange(PROP_DISPLAY_NAME, o, n);
    }

    /** Allow subclasses that override the getShortDescription method to fire
    * the changes of the description by itself. Please notice that default
    * implementation of setShortDescription will fire the change by itself.
    */
    protected final void fireShortDescriptionChange(String o, String n) {
        fireOwnPropertyChange(PROP_SHORT_DESCRIPTION, o, n);
    }

    /** Fire a change event for {@link #PROP_ICON}.
    */
    protected final void fireIconChange() {
        fireOwnPropertyChange(PROP_ICON, null, null);
    }

    /** Fire a change event for {@link #PROP_OPENED_ICON}.
    */
    protected final void fireOpenedIconChange() {
        fireOwnPropertyChange(PROP_OPENED_ICON, null, null);
    }

    /** Fires info about some structural change in children. Providing
    * type of operation and set of children changed generates event describing
    * the change.
    *
    *
    * @param addAction <CODE>true</CODE> if the set of children has been added,
    *   false if it has been removed
    * @param delta the array with changed children
    * @param from the array of nodes to take indices from.
    *   Can be null if one should find indices from current set of nodes
    */
    final void fireSubNodesChange(boolean addAction, Node[] delta, Node[] from) {
        NodeMemberEvent ev = null;

        Object[] listeners = this.listeners.getListenerList();

        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == NodeListener.class) {
                // Lazily create the event:
                if (ev == null) {
                    ev = new NodeMemberEvent(this, addAction, delta, from);
                }

                if (addAction) {
                    ((NodeListener) listeners[i + 1]).childrenAdded(ev);
                } else {
                    ((NodeListener) listeners[i + 1]).childrenRemoved(ev);
                }
            }
        }
    }

    /** Fires info about reordering of some children.
    *
    * @param indices array of integers describing the permutation
    */
    final void fireReorderChange(int[] indices) {
        NodeReorderEvent ev = null;

        Object[] listeners = this.listeners.getListenerList();

        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == NodeListener.class) {
                // Lazily create the event:
                if (ev == null) {
                    ev = new NodeReorderEvent(this, indices);
                }

                ((NodeListener) listeners[i + 1]).childrenReordered(ev);
            }
        }
    }

    /** To all node listeners fire node destroyed notification.
    */
    protected final void fireNodeDestroyed() {
        NodeEvent ev = null;

        Object[] listeners = this.listeners.getListenerList();

        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == NodeListener.class) {
                // Lazily create the event:
                if (ev == null) {
                    ev = new NodeEvent(this);
                }

                ((NodeListener) listeners[i + 1]).nodeDestroyed(ev);
            }
        }
    }

    /** Fires info about change of parent node.
    * @param o old node
    * @param n new parent
    */
    final void fireParentNodeChange(Node o, Node n) {
        fireOwnPropertyChange(PROP_PARENT_NODE, o, n);
    }

    /** Fires a (Bean) property change event (for {@link #PROP_PROPERTY_SETS}).
    * @param o the old set
    * @param n the new set
    */
    protected final void firePropertySetsChange(PropertySet[] o, PropertySet[] n) {
        fireOwnPropertyChange(PROP_PROPERTY_SETS, o, n);
    }

    /** Fires a change event for {@link #PROP_COOKIE}.
    * The old and new values are set to null.
    */
    protected final void fireCookieChange() {
        Lookup l = findDelegatingLookup();

        if (l instanceof NodeLookup) {
            ((NodeLookup) l).updateLookupAsCookiesAreChanged(null);
        }

        fireOwnPropertyChange(PROP_COOKIE, null, null);
    }

    /** Fires info about change of own property.
    * @param name name of property
    * @param o old value
    * @param n new value
    */
    final void fireOwnPropertyChange(String name, Object o, Object n) {
        // do not fire if the values are the same
        if ((o != null) && (n != null) && ((o == n) || o.equals(n))) {
            return;
        }

        PropertyChangeEvent ev = null;

        Object[] listeners = this.listeners.getListenerList();

        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == NodeListener.class) {
                // Lazily create the event:
                if (ev == null) {
                    ev = new PropertyChangeEvent(this, name, o, n);
                }

                ((NodeListener) listeners[i + 1]).propertyChange(ev);
            }
        }
    }

    /** Compares for equaliness. Does special treatment of
     * FilterNodes. If argument is FilterNode then this node can be
     * equal with it if it is its original.
     *
     * @param obj object to compare
     * @return true if the obj is <code>==</code> or is filter node of this node
     */
    public boolean equals(Object obj) {
        if (obj instanceof FilterNode) {
            return ((FilterNode) obj).equals(this);
        }

        return this == obj;
    }

    /** Obtains a resource string from bundle.
    * @param resName resource name
    * @return the string
    */
    static String getString(final String resName) {
        return NbBundle.getBundle(Node.class).getString(resName);
    }

    public String toString() {
        return super.toString() + "[Name=" + getName() + ", displayName=" + getDisplayName() + "]"; // NOI18N
    }

    /** Marker interface for all cookies.
    * <P>
    * Most examples are present in {@link org.openide.cookies}.
    */
    public static interface Cookie {
    }

    /** Serializable node reference. The node should not
    * be serialized directly but via this handle. One can obtain a handle
    * by a call to {@link Node#getHandle}.
    * <P>
    * If that methods returns a non-<code>null</code> value, one can serialize it,
    * and after deserialization
    * use {@link #getNode} to obtain the original node.
    */
    public static interface Handle extends java.io.Serializable {
        /** @deprecated Only public by accident. */

        /* public static final */ long serialVersionUID = -4518262478987434353L;

        /** Reconstitute the node for this handle.
        *
        * @return the node for this handle
        * @exception IOException if the node cannot be created
        */
        public Node getNode() throws java.io.IOException;
    }

    /** Class that represents one set of properties. A usual bean has three
    * sets of properties: normal, expert, and events.
    * <p>You may associate context help with this object, if desired, by setting
    * a {@link FeatureDescriptor#setValue custom property} with the name <code>helpID</code>
    * and value of type <code>String</code> giving a help ID.
    * Normally this is unnecessary as help for the whole {@link Node} will be used by default.
    */
    public static abstract class PropertySet extends FeatureDescriptor {
        /** Default constructor. */
        public PropertySet() {
        }

        /** Create a property set.
         * @param name system name of the property set
        * @param displayName human presentable name
        * @param shortDescription description for the set
        */
        public PropertySet(String name, String displayName, String shortDescription) {
            super.setName(name);
            super.setDisplayName(displayName);
            super.setShortDescription(shortDescription);
        }

        /** Get the list of contained properties.
        * This list can contain both {@link Node.Property} and {@link Node.IndexedProperty} elements.
        *
        * @return the properties
        */
        public abstract Property[] getProperties();

        /* Compares just the names.
         * @param propertySet The object to compare to
         */
        public boolean equals(Object propertySet) {
            if (!(propertySet instanceof PropertySet)) {
                return false;
            }

            return ((PropertySet) propertySet).getName().equals(getName());
        }

        /* Returns a hash code value for the object.
         *
         * @return int hashcode
         */
        public int hashCode() {
            return getName().hashCode();
        }

        /** Return a variant of the display name containing HTML markup
         * conforming to the limited subset of font-markup HTML supported by
         * the lightweight HTML renderer org.openide.awt.HtmlRenderer
         * (font color, bold, italic and strikethrough supported; font
         * colors can be UIManager color keys if they are prefixed with
         * a ! character, i.e. &lt;font color=&amp;'controlShadow'&gt;).
         * Enclosing html tags are not needed.
         * <p><strong>This method should return either an HTML display name
         * or null; it should not return the non-html display name if no
         * markup is needed.
         *
         * @see org.openide.awt.HtmlRenderer
         * @since 4.30
         * @return a String containing conformant, legal HTML markup which
         *  represents the display name, or null.  The default implementation
         *  returns null.  */
        public String getHtmlDisplayName() {
            return null;
        }
    }

    /** Description of a Bean property on a node, and operations on it.
    * <p>You may associate context help with this object, if desired, by setting
    * a {@link FeatureDescriptor#setValue custom property} with the name <code>helpID</code>
    * and value of type <code>String</code> giving a help ID.
    * Normally this is unnecessary as help for the whole {@link Node} will be used by default.
     * <p><strong>Important:</strong> the {@link FeatureDescriptor#getName code name} you use for the
     * property is relevant not only for making properties of a node unique, but also for
     * {@link Node#firePropertyChange firing property changes}.
    */
    public static abstract class Property extends FeatureDescriptor {
        /**
         * Contains classNames of incorrectly implemented properties which have
         * been already logged by an ErrorManager.<br>
         * For more information see the
         * <a href="http://openide.netbeans.org/issues/show_bug.cgi?id=51907">
         * discussion in issuezilla</a>
         */
        private static final Set /*<String>*/ warnedNames = new HashSet();

        /** type that this property works with */
        private Class type;

        //Soft caching of property editor references to improve JTable
        //property sheet performance
        java.lang.ref.SoftReference edRef = null;

        /** Constructor.
        * @param valueType type of the property
        */
        public Property(Class valueType) {
            this.type = valueType;
            super.setName(""); // NOI18N
        }

        /** Get the value type. This is the representation class of the property.
        * Remember that e.g. {@link Boolean <code>Boolean.class</code>} means that values are <code>Boolean</code>
        * objects; to specify the primitive type, use e.g. {@link Boolean#TYPE}.
        * In the latter case, {@link #getValue} and {@link #setValue} will still operate on the wrapper object.
        * @return the type
        */
        public Class getValueType() {
            return type;
        }

        /** Test whether the property is readable.
        * @return <CODE>true</CODE> if it is
        */
        public abstract boolean canRead();

        /** Get the value.
        * @return the value of the property
        * @exception IllegalAccessException cannot access the called method
        * @exception InvocationTargetException an exception during invocation
        */
        public abstract Object getValue() throws IllegalAccessException, InvocationTargetException;

        /** Test whether the property is writable.
        * @return <CODE>true</CODE> if the read of the value is supported
        */
        public abstract boolean canWrite();

        /** Set the value.
        * @param val the new value of the property
        * @exception IllegalAccessException cannot access the called method
        * @exception IllegalArgumentException wrong argument
        * @exception InvocationTargetException an exception during invocation
        */
        public abstract void setValue(Object val)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

        /** Test whether the property had a default value.
        * @return <code>true</code> if it does (<code>false</code> by default)
        */
        public boolean supportsDefaultValue() {
            return false;
        }

        /** Restore this property to its default value, if supported.
        * In the default implementation, does nothing.
        * Typically you would just call e.g. <code>setValue(default)</code>.
        * Note that it is not permitted for this call to throw {@link IllegalArgumentException},
        * though the other two exceptions from {@link #setValue} may be passed through.
        * @exception IllegalAccessException cannot access the called method
        * @exception InvocationTargetException an exception during invocation
        */
        public void restoreDefaultValue() throws IllegalAccessException, InvocationTargetException {
        }

        /**
         * This method indicates whether the current value is the same as
         * the value that would otherwise be restored by calling
         * <code>restoreDefaultValue()</code> (if <code>supportsDefaultValue()</code>
         * returns true). The default implementation returns true and
         * it is recommended to also return true when <code>supportsDefaultValue()</code>
         * returns false (if we do not support default value any value can
         * be considered as the default). If <code>supportsDefaultValue()</code>
         * returns false this method will not be called by the default
         * implementation of property sheet.
         * @since 3.19
         */
        public boolean isDefaultValue() {
            String name = getClass().getName();

            // Issue 51907 backward compatibility
            if (supportsDefaultValue() && warnedNames.add(name)) {
                ErrorManager.getDefault().log(
                    ErrorManager.WARNING,
                    "Class " + name + " must override isDefaultValue() since it " +
                    "overrides supportsDefaultValue() to be true"
                );
            }

            return true;
        }

        /** Get a property editor for this property.
         * The default implementation tries to use {@link java.beans.PropertyEditorManager}.
         * @return the property editor, or <CODE>null</CODE> if there is no editor  */
        public PropertyEditor getPropertyEditor() {
            if (type == null) {
                return null;
            }

            PropertyEditor result = null;

            if (edRef != null) {
                result = (PropertyEditor) edRef.get();
            }

            if (result == null) {
                result = java.beans.PropertyEditorManager.findEditor(type);
                edRef = new java.lang.ref.SoftReference(result);
            }

            return result;
        }

        /* Standard equals implementation for all property
        * classes.
        * @param property The object to compare to
        */
        public boolean equals(Object property) {
            // fix #32845 - check for non-matching types and also for null values
            // coming in input parameter 'property'
            if (!(property instanceof Property)) {
                return false;
            }

            Class propValueType = ((Property) property).getValueType();
            Class valueType = getValueType();

            if (((propValueType == null) && (valueType != null)) || ((propValueType != null) && (valueType == null))) {
                return false;
            }

            return ((Property) property).getName().equals(getName()) &&
            (((propValueType == null) && (valueType == null)) || propValueType.equals(valueType));
        }

        /* Returns a hash code value for the object.
        *
        * @return int hashcode
        */
        public int hashCode() {
            Class valueType = getValueType();

            return getName().hashCode() * ((valueType == null) ? 1 : valueType.hashCode());
        }

        /** Return a variant of the display name containing HTML markup
         * conforming to the limited subset of font-markup HTML supported by
         * the lightweight HTML renderer org.openide.awt.HtmlRenderer
         * (font color, bold, italic and strikethrough supported; font
         * colors can be UIManager color keys if they are prefixed with
         * a ! character, i.e. &lt;font color=&amp;'controlShadow'&gt;).
         * Enclosing html tags are not needed.
         * <p><strong>This method should return either an HTML display name
         * or null; it should not return the non-html display name.
         *
         * @see org.openide.awt.HtmlRenderer
         * @since 4.30
         * @return a String containing conformant, legal HTML markup which
         *  represents the display name, or null.  The default implementation
         *  returns null.  */
        public String getHtmlDisplayName() {
            return null;
        }
    }

    /** Description of an indexed property and operations on it.
    */
    public static abstract class IndexedProperty extends Node.Property {
        /** type of element that this property works with */
        private Class elementType;

        /** Constructor.
        * @param valueType type of the property
        */
        public IndexedProperty(Class valueType, Class elementType) {
            super(valueType);
            this.elementType = elementType;
        }

        /** Test whether the property is readable by index.
        * @return <CODE>true</CODE> if so
        */
        public abstract boolean canIndexedRead();

        /** Get the element type of the property (not the type of the whole property).
        * @return the type
        */
        public Class getElementType() {
            return elementType;
        }

        /** Get the value of the property at an index.
        *
        * @param index the index
        * @return the value at that index
        * @exception IllegalAccessException cannot access the called method
        * @exception IllegalArgumentException wrong argument
        * @exception InvocationTargetException an exception during invocation
        */
        public abstract Object getIndexedValue(int index)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

        /** Test whether the property is writable by index.
        * @return <CODE>true</CODE> if so
        */
        public abstract boolean canIndexedWrite();

        /** Set the value of the property at an index.
        *
        * @param indx the index
        * @param val the value to set
        * @exception IllegalAccessException cannot access the called method
        * @exception IllegalArgumentException wrong argument
        * @exception InvocationTargetException an exception during invocation
        */
        public abstract void setIndexedValue(int indx, Object val)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

        /** Get a property editor for individual elements in this property.
        * @return the property editor for elements
        */
        public PropertyEditor getIndexedPropertyEditor() {
            return java.beans.PropertyEditorManager.findEditor(elementType);
        }

        /* Standard equals implementation for all property
        * classes.
        * @param property The object to compare to
        */
        public boolean equals(Object property) {
            try {
                if (!super.equals(property)) {
                    return false;
                }

                Class propElementType = ((IndexedProperty) property).getElementType();
                Class elementType = getElementType();

                if (
                    ((propElementType == null) && (elementType != null)) ||
                        ((propElementType != null) && (elementType == null))
                ) {
                    return false;
                }

                return (((propElementType == null) && (elementType == null)) || propElementType.equals(elementType));
            } catch (ClassCastException e) {
                return false;
            }
        }

        /* Returns a hash code value for the object.
        *
        * @return int hashcode
        */
        public int hashCode() {
            Class ementType = getElementType();

            return super.hashCode() * ((elementType == null) ? 1 : elementType.hashCode());
        }
    }

    /** Special subclass of EventListenerList that can also listen on changes in
     * a lookup.
     */
    private final class LookupEventList extends javax.swing.event.EventListenerList implements LookupListener {
        public final Lookup lookup;
        private Lookup.Result result;

        public LookupEventList(Lookup l) {
            this.lookup = l;
        }

        public Lookup init(boolean init) {
            boolean doInit = false;

            synchronized (INIT_LOCK) {
                if (init && (result == null)) {
                    result = lookup.lookup(TEMPL_COOKIE);
                    assert result != null : "Null lookup result from " + lookup + " in " + Node.this;
                    result.addLookupListener(this);
                    doInit = true;
                }
            }

            if (doInit) {
                result.allItems();
            }

            return lookup;
        }

        public void resultChanged(org.openide.util.LookupEvent ev) {
            if (Node.this instanceof FilterNode) {
                FilterNode f = (FilterNode) Node.this;

                // See #40734 and NodeLookupTest and CookieActionIsTooSlowTest. 
                if (f.getOriginal() == NodeLookup.NO_COOKIE_CHANGE.get()) {
                    // this is not real cookie change, do not fire it
                    // issue 40734
                    return;
                }
            }

            fireCookieChange();
        }
    }
}
