0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.openide.nodes;
0043:
0044: import java.awt.Image;
0045: import java.awt.datatransfer.Transferable;
0046: import java.beans.FeatureDescriptor;
0047: import java.beans.PropertyChangeEvent;
0048: import java.beans.PropertyChangeListener;
0049: import java.beans.PropertyEditor;
0050: import java.io.IOException;
0051: import java.io.PrintWriter;
0052: import java.io.StringWriter;
0053: import java.lang.ref.Reference;
0054: import java.lang.ref.SoftReference;
0055: import java.lang.ref.WeakReference;
0056: import java.lang.reflect.InvocationTargetException;
0057: import java.util.HashSet;
0058: import java.util.Map;
0059: import java.util.Set;
0060: import java.util.WeakHashMap;
0061: import java.util.logging.Level;
0062: import java.util.logging.Logger;
0063: import javax.swing.Action;
0064: import javax.swing.JPopupMenu;
0065: import javax.swing.event.EventListenerList;
0066: import org.openide.util.HelpCtx;
0067: import org.openide.util.Lookup;
0068: import org.openide.util.LookupEvent;
0069: import org.openide.util.LookupListener;
0070: import org.openide.util.NbBundle;
0071: import org.openide.util.actions.SystemAction;
0072: import org.openide.util.datatransfer.NewType;
0073: import org.openide.util.datatransfer.PasteType;
0074:
0075: /** A <em>node</em> represents one element in a hierarchy of objects (beans).
0076: * It provides all methods that are needed for communication between
0077: * an explorer view and the bean.
0078: * <P>
0079: * The node has three purposes:
0080: * <OL>
0081: * <LI>visually represent the object in the tree hierarchy (i.e. Explorer)
0082: * <LI>provide sets of properties for that object (i.e. Component Inspector, Property Sheet)
0083: * <LI>offer actions to perform on itself
0084: * </OL>
0085: * <P>
0086: * Frequently nodes are created to represent <code>DataObject</code>s.
0087: * But they may also represent anything to be displayed to the user or manipulated programmatically,
0088: * even if they have no data directly stored behind them; for example, a control panel or debugger
0089: * breakpoint.
0090: * <P>
0091: * There are two listeners in this class: {@link PropertyChangeListener}
0092: * and {@link NodeListener} (which extends <code>PropertyChangeListener</code>). The first
0093: * is designed to listen on properties that can be returned from
0094: * {@link #getPropertySets}, the later for listening on changes in the
0095: * node itself (including the name, children, parent, set of properties,
0096: * icons, etc.). Be sure to distinguish between these two.
0097: * <P>
0098: * The node is cloneable. When a node is cloned, it is initialized
0099: * with an empty set of listeners and no parent. The display name and short description
0100: * are copied to the new node. The set of properties is <em>shared</em>.
0101: * <P>
0102: * Implements {@link org.openide.util.Lookup.Provider} since 3.11.
0103: *
0104: * @author Jaroslav Tulach,
0105: */
0106: public abstract class Node extends FeatureDescriptor implements
0107: Lookup.Provider, HelpCtx.Provider {
0108: /** An empty leaf node. */
0109: public static final Node EMPTY = new AbstractNode(Children.LEAF);
0110:
0111: /* here is list of property names that can be changed in the Node object.
0112: * These properties can be notified to the <CODE>NodeListener</CODE>.
0113: */
0114:
0115: /** Property for node display name. */
0116: public static final String PROP_DISPLAY_NAME = "displayName"; // NOI18N
0117:
0118: /** Property for internal (not displayable) name of a node. This name is often used to
0119: * look up a node in the hierarchy, so it should be chosen to be simple.
0120: */
0121: public static final String PROP_NAME = "name"; // NOI18N
0122:
0123: /** Property for short description of a node. */
0124: public static final String PROP_SHORT_DESCRIPTION = "shortDescription"; // NOI18N
0125:
0126: /** Property for the normal (closed) icon of a node. */
0127: public static final String PROP_ICON = "icon"; // NOI18N
0128:
0129: /** Property for the opened icon of a node. */
0130: public static final String PROP_OPENED_ICON = "openedIcon"; // NOI18N
0131:
0132: /** Property for a node's parent. */
0133: public static final String PROP_PARENT_NODE = "parentNode"; // NOI18N
0134:
0135: /** Property for a node's list of property sets. */
0136: public static final String PROP_PROPERTY_SETS = "propertySets"; // NOI18N
0137:
0138: /** Property for a node's cookie set. */
0139: public static final String PROP_COOKIE = "cookie"; // NOI18N
0140:
0141: /** Property saying whether the Node is Leaf
0142: *@since 3.1
0143: */
0144: public static final String PROP_LEAF = "leaf"; // NOI18N
0145:
0146: /** Error manager used for logging and its precomputed err.isLoggable(Level.FINE)
0147: */
0148: private static final Logger err = Logger
0149: .getLogger("org.openide.nodes.Node"); //NOI18N;
0150:
0151: /** cache of all created lookups */
0152: private static Map<EventListenerList, Reference<Lookup>> lookups = new WeakHashMap<EventListenerList, Reference<Lookup>>(
0153: 37);
0154:
0155: /** class.property names we have warned about for #31413 */
0156: private static final Set<String> warnedBadProperties = new HashSet<String>(
0157: 100);
0158:
0159: /** template for changes in cookies */
0160: private static final Lookup.Template<Node.Cookie> TEMPL_COOKIE = new Lookup.Template<Node.Cookie>(
0161: Node.Cookie.class);
0162:
0163: /** Lock for initialization */
0164: private static final Object INIT_LOCK = new Object();
0165:
0166: /** children representing parent node (Children or ChildrenArray),
0167: * for synchronization reasons must be changed only
0168: * under the Children.MUTEX lock
0169: */
0170: private Object parent;
0171:
0172: /** children list, for synch. reasons change only under Children.MUTEX
0173: * lock
0174: */
0175: Children hierarchy;
0176:
0177: /** listeners for changes in hierarchy.
0178: */
0179: private transient EventListenerList listeners;
0180:
0181: /** Creates a new node with a given hierarchy of children.
0182: * @param h the children to use for this node
0183: * @exception IllegalStateException if the children object is already in use by
0184: * a different node
0185: */
0186: protected Node(Children h) throws IllegalStateException {
0187: this (h, null);
0188: }
0189:
0190: /** Creates a new node with a given hierarchy of children and a lookup
0191: * providing content for {@link #getCookie} and {@link #getLookup} methods.
0192: * <p>
0193: * As the lookup needs to be constructed before Node's constructor is called,
0194: * it might not be obvious how to add Node or other objects into it without
0195: * type casting. Here is the recommended suggestion that uses public/private
0196: * pair of constructors:
0197: * <PRE>
0198: <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>
0199: <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>
0200: <span class="constant">}</span>
0201:
0202: <span class="comment">/** A private constructor that takes an InstanceContent and</span>
0203: <span class="comment"> * uses it as internals for the Node lookup and also allow us</span>
0204: <span class="comment"> * to modify the content, for example by adding a reference </span>
0205: <span class="comment"> * to the node itself or any other object we want to represent.</span>
0206: <span class="comment"> *</span>
0207: <span class="comment"> * @param ch children we wish to use</span>
0208: <span class="comment"> * @param file sample object we wish to have in lookup</span>
0209: <span class="comment"> * @param content the content created by the first constructor</span>
0210: <span class="comment"> *<b></b>/</span>
0211: <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>
0212: <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>
0213: <span class="comment">// adds the node to our own lookup</span>
0214: <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>
0215: <span class="comment">// adds additional items to the lookup</span>
0216: <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>
0217: <span class="constant">}</span>
0218: </PRE>
0219: *
0220: * @since 3.11
0221: * @param h the children to use for this node
0222: * @param lookup the lookup to provide content of {@link #getLookup}
0223: * and also {@link #getCookie}
0224: * @exception IllegalStateException if the children object is already in use by
0225: * a different node
0226: */
0227: protected Node(Children h, Lookup lookup)
0228: throws IllegalStateException {
0229: this .hierarchy = h;
0230:
0231: // allow subclasses (FilterNode) to update the lookup
0232: lookup = replaceProvidedLookup(lookup);
0233:
0234: if (lookup != null) {
0235: this .listeners = new LookupEventList(lookup);
0236: } else {
0237: this .listeners = new EventListenerList();
0238: }
0239:
0240: // attaches to this node
0241: h.attachTo(this );
0242: }
0243:
0244: /** Method for subclasses to modify provided lookup before its use.
0245: * This implementation does nothing.
0246: */
0247: Lookup replaceProvidedLookup(Lookup l) {
0248: return l;
0249: }
0250:
0251: /** Method that gives access to internal lookup.
0252: * @param init should it be really initialized (ready for queries) or need not be
0253: * @return lookup or null
0254: */
0255: final Lookup internalLookup(boolean init) {
0256: if (listeners instanceof LookupEventList) {
0257: return ((LookupEventList) listeners).init(init);
0258: } else {
0259: return null;
0260: }
0261: }
0262:
0263: /** Implements {@link Object#clone} to behave correctly if cloning is desired.
0264: * But {@link Cloneable} is <em>not</em> declared by default.
0265: * <P>
0266: * The default implementation checks whether the child list implements
0267: * <code>Cloneable</code>, and if so, it clones the children.
0268: * If the child list does not support cloning, {@link Children#LEAF} is used
0269: * instead for the children. The parent of this node is set to <code>null</code> and an empty set
0270: * of listeners is attached to the node.
0271: *
0272: * @return the cloned version of the node
0273: * @exception CloneNotSupportedException if the children cannot be cloned
0274: * in spite of implementing <code>Cloneable</code>
0275: */
0276: protected Object clone() throws CloneNotSupportedException {
0277: Node n = (Node) super .clone();
0278: Children hier2;
0279:
0280: if (hierarchy instanceof Cloneable) {
0281: hier2 = (Children) hierarchy.cloneHierarchy();
0282: } else {
0283: hier2 = Children.LEAF;
0284: }
0285:
0286: // attach the hierarchy
0287: n.hierarchy = hier2;
0288: hier2.attachTo(n);
0289:
0290: // no parent
0291: n.parent = null;
0292:
0293: // empty set of listeners
0294: if (listeners instanceof LookupEventList) {
0295: n.listeners = new LookupEventList(internalLookup(false));
0296: } else {
0297: n.listeners = new EventListenerList();
0298: }
0299:
0300: return n;
0301: }
0302:
0303: /** Clone the node. The newly created node should reference the same
0304: * object is this node does, but it should not be inserted as a child
0305: * to any other node. Also it should have an empty set of listeners.
0306: * In all other respects the node should behave exactly as the
0307: * original one does.
0308: *
0309: * @return copy of this node
0310: */
0311: public abstract Node cloneNode();
0312:
0313: /** Finds the children we are attached to.
0314: * @return children
0315: */
0316: private Children getParentChildren() {
0317: Object p = this .parent;
0318: return (p instanceof ChildrenArray) ? ((ChildrenArray) p)
0319: .getChildren() : (Children) p;
0320: }
0321:
0322: /** Method that allows Children to change the parent children of
0323: * the node when the node is add to a children.
0324: *
0325: * @param parent the children that wants to contain this node
0326: * @param index index that will be assigned to this node
0327: * @exception IllegalStateException if this node already belongs to a children
0328: */
0329: final synchronized void assignTo(Children parent, int index) {
0330: Children ch = getParentChildren();
0331:
0332: if ((ch != null) && (ch != parent)) {
0333: throw new IllegalStateException("Cannot initialize "
0334: + index + "th child of node " + parent.getNode()
0335: + "; it already belongs to node " + ch.getNode()); // NOI18N
0336: }
0337:
0338: if (!(this .parent instanceof ChildrenArray)) {
0339: this .parent = parent;
0340: }
0341: }
0342:
0343: /** Code that reassigns the reference from to parent from its
0344: * Children to its ChildrenArray.
0345: */
0346: final synchronized void reassignTo(Children currentParent,
0347: ChildrenArray itsArray) {
0348: if ((this .parent != currentParent) && (this .parent != itsArray)) {
0349: throw new IllegalStateException(
0350: "Unauthorized call to change parent: "
0351: + this .parent + " and should be: "
0352: + currentParent);
0353: }
0354:
0355: this .parent = itsArray;
0356: }
0357:
0358: /** Deassigns the node from a children, when it is removed from
0359: * a children.
0360: */
0361: final synchronized void deassignFrom(Children parent) {
0362: Children p = getParentChildren();
0363:
0364: if (parent != p) {
0365: throw new IllegalArgumentException(
0366: "Deassign from wrong parent. Old: " + p
0367: + " Caller: " + parent); //NOI18N
0368: }
0369:
0370: this .parent = null;
0371: }
0372:
0373: /** Set the system name. Fires a property change event.
0374: * @param s the new name
0375: * @exception IllegalArgumentException if the new name cannot represent
0376: * a valid node name
0377: */
0378: public void setName(String s) {
0379: String name = super .getName();
0380:
0381: if ((name == null) || !name.equals(s)) {
0382: super .setName(s);
0383: fireNameChange(name, s);
0384: }
0385: }
0386:
0387: /** Set the display name. Fires a property change event.
0388: * @param s the new name
0389: */
0390: public void setDisplayName(String s) {
0391: String displayName = super .getDisplayName();
0392:
0393: if ((displayName == null) || !displayName.equals(s)) {
0394: super .setDisplayName(s);
0395: fireDisplayNameChange(displayName, s);
0396: }
0397: }
0398:
0399: /** Set the short description of the node. Fires a property change event.
0400: * <p>This description may be used for tool tips, etc.
0401: * @param s the new description
0402: */
0403: public void setShortDescription(String s) {
0404: String descr = super .getShortDescription();
0405:
0406: if ((descr == null) || !descr.equals(s)) {
0407: super .setShortDescription(s);
0408: fireShortDescriptionChange(descr, s);
0409: }
0410: }
0411:
0412: /** Find an icon for this node (in the closed state).
0413: * @param type constant from {@link java.beans.BeanInfo}
0414: * @return icon to use to represent the node
0415: */
0416: public abstract Image getIcon(int type);
0417:
0418: /** Find an icon for this node (in the open state).
0419: * This icon is used when the node may have children and is expanded.
0420: *
0421: * @param type constant from {@link java.beans.BeanInfo}
0422: * @return icon to use to represent the node when open
0423: */
0424: public abstract Image getOpenedIcon(int type);
0425:
0426: /** Get context help associated with this node.
0427: * @return the context help object (could be <code>null</code> or {@link HelpCtx#DEFAULT_HELP})
0428: */
0429: public abstract HelpCtx getHelpCtx();
0430:
0431: /** Get the list of children.
0432: * @return the children
0433: */
0434: public final Children getChildren() {
0435: updateChildren();
0436:
0437: return hierarchy;
0438: }
0439:
0440: /** Can be overridden in subclasses (probably in FilterNode) to check
0441: * whether children are of the right subclass
0442: */
0443: void updateChildren() {
0444: }
0445:
0446: /** Allows to change Children of the node. Call to this method aquires
0447: * write lock on the nodes hierarchy. Take care not to call this method
0448: * under read lock.<BR>
0449: *
0450: * @param ch New children to be set on the node.
0451: * @since 3.1
0452: */
0453: protected final void setChildren(final Children ch) {
0454: Children.MUTEX.postWriteRequest(new Runnable() {
0455: public void run() {
0456: Node[] oldNodes = null;
0457:
0458: if (hierarchy.isInitialized()) {
0459: oldNodes = hierarchy.getNodes();
0460: }
0461: hierarchy.detachFrom();
0462:
0463: boolean wasLeaf = hierarchy == Children.LEAF;
0464:
0465: hierarchy = ch;
0466: hierarchy.attachTo(Node.this );
0467:
0468: if (wasLeaf != (hierarchy == Children.LEAF)) {
0469: fireOwnPropertyChange(PROP_LEAF, wasLeaf,
0470: hierarchy == Children.LEAF);
0471: }
0472:
0473: if ((oldNodes != null) && !wasLeaf) {
0474: fireSubNodesChange(false, oldNodes, oldNodes);
0475: Node[] arr = hierarchy.getNodes();
0476: if (arr.length > 0) {
0477: fireSubNodesChange(true, arr, null);
0478: }
0479: }
0480: }
0481: });
0482: }
0483:
0484: /** Test whether the node is a leaf, or may contain children.
0485: * @return <code>true</code> if the children list is actually {@link Children#LEAF}
0486: */
0487: public final boolean isLeaf() {
0488: updateChildren();
0489:
0490: return hierarchy == Children.LEAF;
0491: }
0492:
0493: /** Get the parent node.
0494: * @return the parent node, or <CODE>null</CODE> if this node is the root of a hierarchy
0495: */
0496: public final Node getParentNode() {
0497: // if contained in a list return its parent node
0498: Children ch = getParentChildren();
0499:
0500: return (ch == null) ? null : ch.getNode();
0501: }
0502:
0503: /** Test whether this node can be renamed.
0504: * If true, one can use {@link #getName} to obtain the current name and
0505: * {@link #setName} to change it.
0506: *
0507: * @return <code>true</code> if the node can be renamed
0508: */
0509: public abstract boolean canRename();
0510:
0511: /** Test whether this node can be deleted.
0512: * @return <CODE>true</CODE> if can
0513: */
0514: public abstract boolean canDestroy();
0515:
0516: // [PENDING] "valid" property? --jglick // NOI18N
0517:
0518: /** Remove the node from its parent and deletes it.
0519: * The default
0520: * implementation obtains write access to
0521: * the {@link Children#MUTEX children's lock}, and removes
0522: * the node from its parent (if any). Also fires a property change.
0523: * <P>
0524: * This may be overridden by subclasses to do any additional
0525: * cleanup.
0526: *
0527: * @exception IOException if something fails
0528: */
0529: public void destroy() throws IOException {
0530: Children.MUTEX.postWriteRequest(new Runnable() {
0531: public void run() {
0532: Children p = getParentChildren();
0533:
0534: if (p != null) {
0535: // remove itself from parent
0536: p.remove(new Node[] { Node.this });
0537: }
0538:
0539: // sets the valid flag to false and fires prop. change
0540: fireNodeDestroyed();
0541: }
0542: });
0543: }
0544:
0545: /** Get the list of property sets for this node.
0546: * E.g. typically there may be one for normal Bean properties, one for expert
0547: * properties, and one for hidden properties.
0548: *
0549: * @return the property sets
0550: */
0551: public abstract PropertySet[] getPropertySets();
0552:
0553: /** Called when a node is to be copied to the clipboard.
0554: * @return the transferable object representing the
0555: * content of the clipboard
0556: * @exception IOException when the
0557: * copy cannot be performed
0558: */
0559: public abstract Transferable clipboardCopy() throws IOException;
0560:
0561: /** Called when a node is to be cut to the clipboard.
0562: * @return the transferable object representing the
0563: * content of the clipboard
0564: * @exception IOException when the
0565: * cut cannot be performed
0566: */
0567: public abstract Transferable clipboardCut() throws IOException;
0568:
0569: /** Called when a drag is started with this node.
0570: * The node can attach a transfer listener to ExTransferable and
0571: * will be then notified about progress of the drag (accept/reject).
0572: *
0573: * @return transferable to represent this node during a drag
0574: * @exception IOException if a drag cannot be started
0575: */
0576: public abstract Transferable drag() throws IOException;
0577:
0578: /** Test whether this node permits copying.
0579: * @return <code>true</code> if so
0580: */
0581: public abstract boolean canCopy();
0582:
0583: /** Test whether this node permits cutting.
0584: * @return <code>true</code> if so
0585: */
0586: public abstract boolean canCut();
0587:
0588: /** Determine which paste operations are allowed when a given transferable is in the clipboard.
0589: * For example, a node representing a Java package will permit classes to be pasted into it.
0590: * @param t the transferable in the clipboard
0591: * @return array of operations that are allowed
0592: */
0593: public abstract PasteType[] getPasteTypes(Transferable t);
0594:
0595: /** Determine if there is a paste operation that can be performed
0596: * on provided transferable. Used by drag'n'drop code to check
0597: * whether the drop is possible.
0598: *
0599: * @param t the transferable
0600: * @param action the drag'n'drop action to do DnDConstants.ACTION_MOVE, ACTION_COPY, ACTION_LINK
0601: * @param index index between children the drop occurred at or -1 if not specified
0602: * @return null if the transferable cannot be accepted or the paste type
0603: * to execute when the drop occurs
0604: */
0605: public abstract PasteType getDropType(Transferable t, int action,
0606: int index);
0607:
0608: /** Get the new types that can be created in this node.
0609: * For example, a node representing a Java package will permit classes to be added.
0610: * @return array of new type operations that are allowed
0611: */
0612: public abstract NewType[] getNewTypes();
0613:
0614: /** Get the set of actions that are associated with this node.
0615: * This set is used to construct the context menu for the node.
0616: *
0617: * <P>
0618: * By default this method delegates to the deprecated getActions or getContextActions
0619: * method depending on the value of supplied argument.
0620: * <P>
0621: * It is supposed to be overridden by subclasses accordingly.
0622: *
0623: * @param context whether to find actions for context meaning or for the
0624: * node itself
0625: * @return a list of actions (you may include nulls for separators)
0626: * @since 3.29
0627: */
0628: public Action[] getActions(boolean context) {
0629: return context ? getContextActions() : getActions();
0630: }
0631:
0632: /** Get the set of actions associated with this node.
0633: * This may be used e.g. in constructing a {@link #getContextMenu context menu}.
0634: *
0635: * <P>
0636: * By default returns the actions in {@link NodeOp#getDefaultActions}.
0637: *
0638: * @return system actions appropriate to the node
0639: * @deprecated Use getActions (false) instead.
0640: */
0641: @Deprecated
0642: public SystemAction[] getActions() {
0643: return NodeOp.getDefaultActions();
0644: }
0645:
0646: /** Get a special set of actions
0647: * for situations when this node is displayed as a context.
0648: * <p>For example, right-clicking on a parent node in a hierarchical view (such as
0649: * the normal Explorer) should use <code>getActions</code>. However, if this node
0650: * is serving as the parent of a (say) a window tab full of icons (e.g., in
0651: * <code>IconView</code>), and the users right-clicks on
0652: * the empty space in this pane, then this method should be used to get
0653: * the appropriate actions for a context menu.
0654: * <p>Note that in the Windows UI system, e.g., these action sets are quite different.
0655: *
0656: * @return actions for a context. In the default implementation, same as {@link #getActions}.
0657: * @deprecated Use getActions (true) instead.
0658: */
0659: @Deprecated
0660: public SystemAction[] getContextActions() {
0661: return getActions();
0662: }
0663:
0664: /** Gets the default action for this node.
0665: * @return <code>null</code> indicating there should be none default action
0666: * @deprecated Use {@link #getPreferredAction} instead.
0667: */
0668: @Deprecated
0669: public SystemAction getDefaultAction() {
0670: return null;
0671: }
0672:
0673: /** Gets the preferred action for this node.
0674: * This action can but need not to be one from the action array returned
0675: * from {@link #getActions(boolean)}.
0676: * In case it is, the context menu created from those actions
0677: * is encouraged to highlight the preferred action.
0678: * Override in subclasses accordingly.
0679: *
0680: * @return the preferred action, or <code>null</code> if there is none
0681: * @since 3.29
0682: */
0683: public Action getPreferredAction() {
0684: return getDefaultAction();
0685: }
0686:
0687: /** Make a context menu for this node.
0688: * The menu is constructed from the set of actions returned by {@link #getActions}.
0689: *
0690: * @return the context menu
0691: */
0692: public final JPopupMenu getContextMenu() {
0693: return NodeOp.findContextMenuImpl(new Node[] { this }, null);
0694: }
0695:
0696: /** Test whether there is a customizer for this node. If true,
0697: * the customizer can be obtained via {@link #getCustomizer}.
0698: *
0699: * @return <CODE>true</CODE> if there is a customizer
0700: */
0701: public abstract boolean hasCustomizer();
0702:
0703: /** Get the customizer component.
0704: * @return the component, or <CODE>null</CODE> if there is no customizer
0705: */
0706: public abstract java.awt.Component getCustomizer();
0707:
0708: /** Get a cookie for this node.
0709: * <P>
0710: * The set of cookies can change. If a node changes its set of
0711: * cookies, it fires a property change event with {@link #PROP_COOKIE}.
0712: * <P>
0713: * If the Node was constructed with a <code>Lookup</code> in constructor
0714: * than this method delegates to the provided lookup object.
0715: *
0716: * @param type the representation class of the cookie
0717: * @return a cookie assignable to that class, or <code>null</code> if this node has no such cookie
0718: * @see Lookup
0719: */
0720: public <T extends Node.Cookie> T getCookie(Class<T> type) {
0721: Lookup l = internalLookup(true);
0722:
0723: if (l != null) {
0724: Object obj = l.lookup(type);
0725: if (Node.Cookie.class.isInstance(obj)) {
0726: return type.cast(obj);
0727: }
0728: CookieSet.enhancedQueryMode(l, type);
0729: }
0730:
0731: return null;
0732: }
0733:
0734: /** Obtains a Lookup representing additional content of this Node.
0735: * If the lookup was provided in a constructor, it is returned here,
0736: * if not, a lookup based on the content of <link>getCookie</link>
0737: * method is provided.
0738: *
0739: * @return lookup for this node
0740: * @since 3.11
0741: */
0742: public final Lookup getLookup() {
0743: synchronized (listeners) {
0744: Lookup l = internalLookup(true);
0745:
0746: if (l != null) {
0747: return l;
0748: }
0749:
0750: l = findDelegatingLookup();
0751:
0752: if (l != null) {
0753: return l;
0754: }
0755:
0756: // create new lookup and use it
0757: NodeLookup nl = new NodeLookup(this );
0758: registerDelegatingLookup(nl);
0759:
0760: return nl;
0761: }
0762: }
0763:
0764: /** Return a variant of the display name containing HTML markup
0765: * conforming to the limited subset of font-markup HTML supported by
0766: * the lightweight HTML renderer <code>org.openide.awt.HtmlRenderer</code>
0767: * (font color, bold, italic and strike-through supported; font
0768: * colors can be UIManager color keys if they are prefixed with
0769: * a ! character, i.e. <samp><font color='!controlShadow'></samp>).
0770: * Enclosing <samp><html></samp> tags are not needed. If returning non-null, HTML
0771: * markup characters that should be literally rendered must be
0772: * escaped (<samp>></samp> becomes <samp>&gt;</samp> and so forth).
0773: * <p><strong>This method should return either an HTML display name
0774: * or null; it should not return the non-HTML display name.</strong>
0775: * <p>
0776: * Note there is no property corresponding to the HTML display name -
0777: * if it should change, a change in the display name should be fired; this
0778: * should not be a mechanism for returning anything other than a marked
0779: * up version of the return value of <code>getDisplayName</code>.
0780: *
0781: * @see org.openide.awt.HtmlRenderer
0782: * @since 4.30
0783: * @return a String containing conformant HTML markup which
0784: * represents the display name, or null. The default implementation
0785: * returns null. */
0786: public String getHtmlDisplayName() {
0787: return null;
0788: }
0789:
0790: /** Register delegating lookup so it can always be found.
0791: */
0792: final void registerDelegatingLookup(NodeLookup l) {
0793: // to have just one thread accessing the static lookups variable
0794: synchronized (lookups) {
0795: lookups.put(listeners, new WeakReference<Lookup>(l));
0796: }
0797: }
0798:
0799: /** Finds delegating lookup that was previously registered
0800: * @return the lookup or null if nothing was registered or the
0801: * lookup was GCed.
0802: */
0803: final Lookup findDelegatingLookup() {
0804: Reference<Lookup> ref = lookups.get(listeners);
0805:
0806: return (ref == null) ? null : ref.get();
0807: }
0808:
0809: /** Obtain handle for this node (for serialization).
0810: * The handle can be serialized and {@link Handle#getNode} used after
0811: * deserialization to obtain the original node.
0812: *
0813: * @return the handle, or <code>null</code> if this node is not persistable
0814: */
0815: public abstract Node.Handle getHandle();
0816:
0817: /** Add a listener to changes in the node's intrinsic properties (name, cookies, etc.).
0818: * <P>
0819: * The listener is not notified about changes in subnodes until the
0820: * method <CODE>getChildren().getNodes()</CODE> is called.
0821: * @param l the listener to add
0822: */
0823: public final void addNodeListener(NodeListener l) {
0824: listeners.add(NodeListener.class, l);
0825: listenerAdded();
0826: }
0827:
0828: /** A method to notify FilterNode that a listenerAdded has been added */
0829: void listenerAdded() {
0830: }
0831:
0832: /** Remove a node listener.
0833: * @param l the listener
0834: */
0835: public final void removeNodeListener(NodeListener l) {
0836: listeners.remove(NodeListener.class, l);
0837: }
0838:
0839: /** Add a listener to the node's computed Bean properties.
0840: * @param l the listener
0841: */
0842: public final void addPropertyChangeListener(PropertyChangeListener l) {
0843: int count = -1;
0844:
0845: if (err.isLoggable(Level.FINE)) {
0846: count = getPropertyChangeListenersCount();
0847: }
0848:
0849: listeners.add(PropertyChangeListener.class, l);
0850:
0851: if (err.isLoggable(Level.FINE)) {
0852: err.log(Level.FINE, "ADD - " + getName() + " [" + count
0853: + "]->[" + getPropertyChangeListenersCount() + "] "
0854: + l);
0855: }
0856:
0857: notifyPropertyChangeListenerAdded(l);
0858: }
0859:
0860: /** Called to notify subclasses (FilterNode) about addition of
0861: * PropertyChangeListener.
0862: */
0863: void notifyPropertyChangeListenerAdded(PropertyChangeListener l) {
0864: }
0865:
0866: /** Returns the number of property change listeners attached to this node
0867: */
0868: int getPropertyChangeListenersCount() {
0869: return listeners.getListenerCount(PropertyChangeListener.class);
0870: }
0871:
0872: /** Allows to figure out, whether the node has any
0873: * PropertyChangeListeners attached.
0874: * @return True if node has one or more PropertyChangeListeners attached.
0875: * @since 1.36
0876: */
0877: protected final boolean hasPropertyChangeListener() {
0878: return getPropertyChangeListenersCount() > 0;
0879: }
0880:
0881: /** Remove a Bean property change listener.
0882: * @param l the listener
0883: */
0884: public final void removePropertyChangeListener(
0885: PropertyChangeListener l) {
0886: int count = -1;
0887:
0888: if (err.isLoggable(Level.FINE)) {
0889: count = getPropertyChangeListenersCount();
0890: }
0891:
0892: listeners.remove(PropertyChangeListener.class, l);
0893:
0894: if (err.isLoggable(Level.FINE)) {
0895: err.log(Level.FINE, "RMV - " + getName() + " [" + count
0896: + "]->[" + getPropertyChangeListenersCount() + "] "
0897: + l);
0898: }
0899:
0900: notifyPropertyChangeListenerRemoved(l);
0901: }
0902:
0903: /** Called to notify subclasses (FilterNode) about removal of
0904: * PropertyChangeListener.
0905: */
0906: void notifyPropertyChangeListenerRemoved(PropertyChangeListener l) {
0907: }
0908:
0909: /** Fire a property change event.
0910: *
0911: * @param name name of changed property (from {@link #getPropertySets}); may be null
0912: * @param o old value; may be null
0913: * @param n new value; may be null
0914: * @see PropertyChangeEvent
0915: */
0916: protected final void firePropertyChange(String name, Object o,
0917: Object n) {
0918: // First check if this property actually exists - if not warn! See #31413.
0919: if (err.isLoggable(Level.WARNING) && (name != null)
0920: && propertySetsAreKnown()) {
0921: Node.PropertySet[] pss = getPropertySets();
0922: boolean exists = false;
0923:
0924: for (int i = 0; i < pss.length; i++) {
0925: Node.Property[] ps = pss[i].getProperties();
0926:
0927: for (int j = 0; j < ps.length; j++) {
0928: if (ps[j].getName().equals(name)) {
0929: exists = true;
0930:
0931: break;
0932: }
0933: }
0934: }
0935:
0936: if (!exists) {
0937: synchronized (warnedBadProperties) {
0938: String clazz = getClass().getName();
0939:
0940: if (warnedBadProperties.add(clazz + "." + name)) {
0941: StringWriter w = new StringWriter();
0942: IllegalStateException ise = new IllegalStateException(
0943: "Warning - the node \""
0944: + getDisplayName()
0945: + "\" ["
0946: + clazz
0947: + "] is trying to fire the property "
0948: + name
0949: + " which is not included in its property sets. This is illegal. See IZ #31413 for details."); // NOI18N
0950: ise.printStackTrace(new PrintWriter(w));
0951: Logger.getLogger(Node.class.getName()).warning(
0952: w.toString());
0953: }
0954: }
0955: }
0956: }
0957:
0958: // do not fire if the values are the same
0959: if ((o != null) && (n != null) && ((o == n) || o.equals(n))) {
0960: return;
0961: }
0962:
0963: PropertyChangeEvent ev = null;
0964:
0965: Object[] listeners = this .listeners.getListenerList();
0966:
0967: // Process the listeners last to first, notifying
0968: // those that are interested in this event
0969: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0970: if (listeners[i] == PropertyChangeListener.class) {
0971: // Lazily create the event:
0972: if (ev == null) {
0973: ev = new PropertyChangeEvent(this , name, o, n);
0974: }
0975:
0976: ((PropertyChangeListener) listeners[i + 1])
0977: .propertyChange(ev);
0978: }
0979: }
0980: }
0981:
0982: /**
0983: * If true, property sets have definitely been computed, and it is fine
0984: * to call {@link #getPropertySets} without fear of killing laziness.
0985: * Used from {@link #firePropertyChange} to only check for bad properties
0986: * if the set of properties has already been computed. Otherwise, don't
0987: * bother. Subclasses may override - {@link AbstractNode} does.
0988: */
0989: boolean propertySetsAreKnown() {
0990: return false;
0991: }
0992:
0993: /** Allow subclasses that override the getName method to fire
0994: * the changes of the name by itself. Please notice that default
0995: * implementation of setName will fire the change by itself.
0996: */
0997: protected final void fireNameChange(String o, String n) {
0998: fireOwnPropertyChange(PROP_NAME, o, n);
0999: }
1000:
1001: /** Allow subclasses that override the getDisplayName method to fire
1002: * the changes of the name by itself. Please notice that default
1003: * implementation of setDisplayName will fire the change by itself.
1004: */
1005: protected final void fireDisplayNameChange(String o, String n) {
1006: fireOwnPropertyChange(PROP_DISPLAY_NAME, o, n);
1007: }
1008:
1009: /** Allow subclasses that override the getShortDescription method to fire
1010: * the changes of the description by itself. Please notice that default
1011: * implementation of setShortDescription will fire the change by itself.
1012: */
1013: protected final void fireShortDescriptionChange(String o, String n) {
1014: fireOwnPropertyChange(PROP_SHORT_DESCRIPTION, o, n);
1015: }
1016:
1017: /** Fire a change event for {@link #PROP_ICON}.
1018: */
1019: protected final void fireIconChange() {
1020: fireOwnPropertyChange(PROP_ICON, null, null);
1021: }
1022:
1023: /** Fire a change event for {@link #PROP_OPENED_ICON}.
1024: */
1025: protected final void fireOpenedIconChange() {
1026: fireOwnPropertyChange(PROP_OPENED_ICON, null, null);
1027: }
1028:
1029: /** Fires info about some structural change in children. Providing
1030: * type of operation and set of children changed generates event describing
1031: * the change.
1032: *
1033: *
1034: * @param addAction <CODE>true</CODE> if the set of children has been added,
1035: * false if it has been removed
1036: * @param delta the array with changed children
1037: * @param from the array of nodes to take indices from.
1038: * Can be null if one should find indices from current set of nodes
1039: */
1040: final void fireSubNodesChange(boolean addAction, Node[] delta,
1041: Node[] from) {
1042: NodeMemberEvent ev = null;
1043:
1044: Object[] listeners = this .listeners.getListenerList();
1045:
1046: // Process the listeners last to first, notifying
1047: // those that are interested in this event
1048: for (int i = listeners.length - 2; i >= 0; i -= 2) {
1049: if (listeners[i] == NodeListener.class) {
1050: // Lazily create the event:
1051: if (ev == null) {
1052: ev = new NodeMemberEvent(this , addAction, delta,
1053: from);
1054: }
1055:
1056: if (addAction) {
1057: ((NodeListener) listeners[i + 1]).childrenAdded(ev);
1058: } else {
1059: ((NodeListener) listeners[i + 1])
1060: .childrenRemoved(ev);
1061: }
1062: }
1063: }
1064: }
1065:
1066: /** Fires info about reordering of some children.
1067: *
1068: * @param indices array of integers describing the permutation
1069: */
1070: final void fireReorderChange(int[] indices) {
1071: NodeReorderEvent ev = null;
1072:
1073: Object[] listeners = this .listeners.getListenerList();
1074:
1075: // Process the listeners last to first, notifying
1076: // those that are interested in this event
1077: for (int i = listeners.length - 2; i >= 0; i -= 2) {
1078: if (listeners[i] == NodeListener.class) {
1079: // Lazily create the event:
1080: if (ev == null) {
1081: ev = new NodeReorderEvent(this , indices);
1082: }
1083:
1084: ((NodeListener) listeners[i + 1]).childrenReordered(ev);
1085: }
1086: }
1087: }
1088:
1089: /** To all node listeners fire node destroyed notification.
1090: */
1091: protected final void fireNodeDestroyed() {
1092: NodeEvent ev = null;
1093:
1094: Object[] listeners = this .listeners.getListenerList();
1095:
1096: // Process the listeners last to first, notifying
1097: // those that are interested in this event
1098: for (int i = listeners.length - 2; i >= 0; i -= 2) {
1099: if (listeners[i] == NodeListener.class) {
1100: // Lazily create the event:
1101: if (ev == null) {
1102: ev = new NodeEvent(this );
1103: }
1104:
1105: ((NodeListener) listeners[i + 1]).nodeDestroyed(ev);
1106: }
1107: }
1108: }
1109:
1110: /** Fires info about change of parent node.
1111: * @param o old node
1112: * @param n new parent
1113: */
1114: final void fireParentNodeChange(Node o, Node n) {
1115: fireOwnPropertyChange(PROP_PARENT_NODE, o, n);
1116: }
1117:
1118: /** Fires a (Bean) property change event (for {@link #PROP_PROPERTY_SETS}).
1119: * @param o the old set
1120: * @param n the new set
1121: */
1122: protected final void firePropertySetsChange(PropertySet[] o,
1123: PropertySet[] n) {
1124: fireOwnPropertyChange(PROP_PROPERTY_SETS, o, n);
1125: }
1126:
1127: /** Fires a change event for {@link #PROP_COOKIE}.
1128: * The old and new values are set to null.
1129: */
1130: protected final void fireCookieChange() {
1131: Lookup l = findDelegatingLookup();
1132:
1133: if (l instanceof NodeLookup) {
1134: ((NodeLookup) l).updateLookupAsCookiesAreChanged(null);
1135: }
1136:
1137: fireOwnPropertyChange(PROP_COOKIE, null, null);
1138: }
1139:
1140: /** Fires info about change of own property.
1141: * @param name name of property
1142: * @param o old value
1143: * @param n new value
1144: */
1145: final void fireOwnPropertyChange(String name, Object o, Object n) {
1146: // do not fire if the values are the same
1147: if ((o != null) && (n != null) && ((o == n) || o.equals(n))) {
1148: return;
1149: }
1150:
1151: PropertyChangeEvent ev = null;
1152:
1153: Object[] listeners = this .listeners.getListenerList();
1154:
1155: // Process the listeners last to first, notifying
1156: // those that are interested in this event
1157: for (int i = listeners.length - 2; i >= 0; i -= 2) {
1158: if (listeners[i] == NodeListener.class) {
1159: // Lazily create the event:
1160: if (ev == null) {
1161: ev = new PropertyChangeEvent(this , name, o, n);
1162: }
1163:
1164: ((NodeListener) listeners[i + 1]).propertyChange(ev);
1165: }
1166: }
1167: }
1168:
1169: /** Compares for equality. Does special treatment of
1170: * FilterNodes. If argument is FilterNode then this node can be
1171: * equal with it if it is its original.
1172: *
1173: * @param obj object to compare
1174: * @return true if the obj is <code>==</code> or is filter node of this node
1175: */
1176: public boolean equals(Object obj) {
1177: if (obj instanceof FilterNode) {
1178: return ((FilterNode) obj).equals(this );
1179: }
1180:
1181: return this == obj;
1182: }
1183:
1184: /** Obtains a resource string from bundle.
1185: * @param resName resource name
1186: * @return the string
1187: */
1188: static String getString(final String resName) {
1189: return NbBundle.getBundle(Node.class).getString(resName);
1190: }
1191:
1192: public String toString() {
1193: return super .toString() + "[Name=" + getName()
1194: + ", displayName=" + getDisplayName() + "]"; // NOI18N
1195: }
1196:
1197: /** Marker interface for all cookies.
1198: * <P>
1199: * Most examples are present in {@link org.openide.cookies}.
1200: */
1201: public static interface Cookie {
1202: }
1203:
1204: /** Serializable node reference. The node should not
1205: * be serialized directly but via this handle. One can obtain a handle
1206: * by a call to {@link Node#getHandle}.
1207: * <P>
1208: * If that methods returns a non-<code>null</code> value, one can serialize it,
1209: * and after deserialization
1210: * use {@link #getNode} to obtain the original node.
1211: */
1212: public static interface Handle extends java.io.Serializable {
1213: /** @deprecated Only public by accident. */
1214: @Deprecated
1215: /* public static final */long serialVersionUID = -4518262478987434353L;
1216:
1217: /** Reconstitute the node for this handle.
1218: *
1219: * @return the node for this handle
1220: * @exception IOException if the node cannot be created
1221: */
1222: public Node getNode() throws java.io.IOException;
1223: }
1224:
1225: /** Class that represents one set of properties. A usual bean has three
1226: * sets of properties: normal, expert, and events.
1227: * <p>You may associate context help with this object, if desired, by setting
1228: * a {@link FeatureDescriptor#setValue custom property} with the name <code>helpID</code>
1229: * and value of type <code>String</code> giving a help ID.
1230: * Normally this is unnecessary as help for the whole {@link Node} will be used by default.
1231: */
1232: public static abstract class PropertySet extends FeatureDescriptor {
1233: /** Default constructor. */
1234: public PropertySet() {
1235: }
1236:
1237: /** Create a property set.
1238: * @param name system name of the property set
1239: * @param displayName human presentable name
1240: * @param shortDescription description for the set
1241: */
1242: public PropertySet(String name, String displayName,
1243: String shortDescription) {
1244: super .setName(name);
1245: super .setDisplayName(displayName);
1246: super .setShortDescription(shortDescription);
1247: }
1248:
1249: /** Get the list of contained properties.
1250: * This list can contain both {@link Node.Property} and {@link Node.IndexedProperty} elements.
1251: *
1252: * @return the properties
1253: */
1254: public abstract Property<?>[] getProperties();
1255:
1256: /* Compares just the names.
1257: * @param propertySet The object to compare to
1258: */
1259: public boolean equals(Object propertySet) {
1260: if (!(propertySet instanceof PropertySet)) {
1261: return false;
1262: }
1263:
1264: return ((PropertySet) propertySet).getName().equals(
1265: getName());
1266: }
1267:
1268: /* Returns a hash code value for the object.
1269: *
1270: * @return int hashcode
1271: */
1272: public int hashCode() {
1273: return getName().hashCode();
1274: }
1275:
1276: /** Return a variant of the display name containing HTML markup
1277: * conforming to the limited subset of font-markup HTML supported by
1278: * the lightweight HTML renderer org.openide.awt.HtmlRenderer
1279: * (font color, bold, italic and strikethrough supported; font
1280: * colors can be UIManager color keys if they are prefixed with
1281: * a ! character, i.e. <font color=&'controlShadow'>).
1282: * Enclosing HTML tags are not needed.
1283: * <p><strong>This method should return either an HTML display name
1284: * or null; it should not return the non-HTML display name if no
1285: * markup is needed.</strong>
1286: *
1287: * @see org.openide.awt.HtmlRenderer
1288: * @since 4.30
1289: * @return a String containing conformant, legal HTML markup which
1290: * represents the display name, or null. The default implementation
1291: * returns null. */
1292: public String getHtmlDisplayName() {
1293: return null;
1294: }
1295: }
1296:
1297: /** Description of a Bean property on a node, and operations on it.
1298: * <p>You may associate context help with this object, if desired, by setting
1299: * a {@link FeatureDescriptor#setValue custom property} with the name <code>helpID</code>
1300: * and value of type <code>String</code> giving a help ID.
1301: * Normally this is unnecessary as help for the whole {@link Node} will be used by default.
1302: * <p><strong>Important:</strong> the {@link FeatureDescriptor#getName code name} you use for the
1303: * property is relevant not only for making properties of a node unique, but also for
1304: * {@link Node#firePropertyChange firing property changes}.
1305: * @param T the type of bean
1306: */
1307: public static abstract class Property<T> extends FeatureDescriptor {
1308: /**
1309: * Contains classNames of incorrectly implemented properties which have
1310: * been already logged by an ErrorManager.<br>
1311: * For more information see the
1312: * <a href="http://openide.netbeans.org/issues/show_bug.cgi?id=51907">
1313: * discussion in issuezilla</a>
1314: */
1315: private static final Set<String> warnedNames = new HashSet<String>();
1316:
1317: /** type that this property works with */
1318: private Class<T> type;
1319:
1320: //Soft caching of property editor references to improve JTable
1321: //property sheet performance
1322: private Reference<PropertyEditor> edRef = null;
1323:
1324: /** Constructor.
1325: * @param valueType type of the property
1326: */
1327: public Property(Class<T> valueType) {
1328: this .type = valueType;
1329: super .setName(""); // NOI18N
1330: }
1331:
1332: /** Get the value type. This is the representation class of the property.
1333: * Remember that e.g. {@link Boolean <code>Boolean.class</code>} means that values are <code>Boolean</code>
1334: * objects; to specify the primitive type, use e.g. {@link Boolean#TYPE}.
1335: * In the latter case, {@link #getValue} and {@link #setValue} will still operate on the wrapper object.
1336: * @return the type
1337: */
1338: public Class<T> getValueType() {
1339: return type;
1340: }
1341:
1342: /** Test whether the property is readable.
1343: * @return <CODE>true</CODE> if it is
1344: */
1345: public abstract boolean canRead();
1346:
1347: /** Get the value.
1348: * @return the value of the property
1349: * @exception IllegalAccessException cannot access the called method
1350: * @exception InvocationTargetException an exception during invocation
1351: */
1352: public abstract T getValue() throws IllegalAccessException,
1353: InvocationTargetException;
1354:
1355: /** Test whether the property is writable.
1356: * @return <CODE>true</CODE> if the read of the value is supported
1357: */
1358: public abstract boolean canWrite();
1359:
1360: /** Set the value.
1361: * @param val the new value of the property
1362: * @exception IllegalAccessException cannot access the called method
1363: * @exception IllegalArgumentException wrong argument
1364: * @exception InvocationTargetException an exception during invocation
1365: */
1366: public abstract void setValue(T val)
1367: throws IllegalAccessException,
1368: IllegalArgumentException, InvocationTargetException;
1369:
1370: /** Test whether the property had a default value.
1371: * @return <code>true</code> if it does (<code>false</code> by default)
1372: */
1373: public boolean supportsDefaultValue() {
1374: return false;
1375: }
1376:
1377: /** Restore this property to its default value, if supported.
1378: * In the default implementation, does nothing.
1379: * Typically you would just call e.g. <code>setValue(default)</code>.
1380: * Note that it is not permitted for this call to throw {@link IllegalArgumentException},
1381: * though the other two exceptions from {@link #setValue} may be passed through.
1382: * @exception IllegalAccessException cannot access the called method
1383: * @exception InvocationTargetException an exception during invocation
1384: */
1385: public void restoreDefaultValue()
1386: throws IllegalAccessException,
1387: InvocationTargetException {
1388: }
1389:
1390: /**
1391: * This method indicates whether the current value is the same as
1392: * the value that would otherwise be restored by calling
1393: * <code>restoreDefaultValue()</code> (if <code>supportsDefaultValue()</code>
1394: * returns true). The default implementation returns true and
1395: * it is recommended to also return true when <code>supportsDefaultValue()</code>
1396: * returns false (if we do not support default value any value can
1397: * be considered as the default). If <code>supportsDefaultValue()</code>
1398: * returns false this method will not be called by the default
1399: * implementation of property sheet.
1400: * @since 3.19
1401: */
1402: public boolean isDefaultValue() {
1403: String name = getClass().getName();
1404:
1405: // Issue 51907 backward compatibility
1406: if (supportsDefaultValue() && warnedNames.add(name)) {
1407: Logger
1408: .getLogger(Node.Property.class.getName())
1409: .log(
1410: Level.WARNING,
1411: "Class "
1412: + name
1413: + " must override isDefaultValue() since it "
1414: + "overrides supportsDefaultValue() to be true");
1415: }
1416:
1417: return true;
1418: }
1419:
1420: /** Get a property editor for this property.
1421: * The default implementation tries to use {@link java.beans.PropertyEditorManager}.
1422: * @return the property editor, or <CODE>null</CODE> if there is no editor */
1423: public PropertyEditor getPropertyEditor() {
1424: if (type == null) {
1425: return null;
1426: }
1427:
1428: PropertyEditor result = null;
1429:
1430: if (edRef != null) {
1431: result = edRef.get();
1432: }
1433:
1434: if (result == null) {
1435: result = java.beans.PropertyEditorManager
1436: .findEditor(type);
1437: edRef = new SoftReference<PropertyEditor>(result);
1438: }
1439:
1440: return result;
1441: }
1442:
1443: /* Standard equals implementation for all property
1444: * classes.
1445: * @param property The object to compare to
1446: */
1447: @Override
1448: public boolean equals(Object property) {
1449: // fix #32845 - check for non-matching types and also for null values
1450: // coming in input parameter 'property'
1451: if (!(property instanceof Property)) {
1452: return false;
1453: }
1454:
1455: Class<?> propValueType = ((Property) property)
1456: .getValueType();
1457: Class<?> valueType = getValueType();
1458:
1459: if (((propValueType == null) && (valueType != null))
1460: || ((propValueType != null) && (valueType == null))) {
1461: return false;
1462: }
1463:
1464: return ((Property) property).getName().equals(getName())
1465: && (((propValueType == null) && (valueType == null)) || propValueType
1466: .equals(valueType));
1467: }
1468:
1469: /* Returns a hash code value for the object.
1470: *
1471: * @return int hashcode
1472: */
1473: @Override
1474: public int hashCode() {
1475: Class<?> valueType = getValueType();
1476:
1477: return getName().hashCode()
1478: * ((valueType == null) ? 1 : valueType.hashCode());
1479: }
1480:
1481: /** Return a variant of the display name containing HTML markup
1482: * conforming to the limited subset of font-markup HTML supported by
1483: * the lightweight HTML renderer {@link org.openide.awt.HtmlRenderer}
1484: * (font color, bold, italic and strike-through supported; font
1485: * colors can be UIManager color keys if they are prefixed with
1486: * a ! character, i.e. <font color=&'controlShadow'>).
1487: * Enclosing HTML tags are not needed.
1488: * <p><strong>This method should return either an HTML display name
1489: * or null; it should not return the non-HTML display name.
1490: *
1491: * @see org.openide.awt.HtmlRenderer
1492: * @since 4.30
1493: * @return a String containing conformant, legal HTML markup which
1494: * represents the display name, or null. The default implementation
1495: * returns null. */
1496: public String getHtmlDisplayName() {
1497: return null;
1498: }
1499: }
1500:
1501: /** Description of an indexed property and operations on it.
1502: * @param T type of the whole property
1503: * @param E type of one element
1504: */
1505: public static abstract class IndexedProperty<T, E> extends
1506: Node.Property<T> {
1507: /** type of element that this property works with */
1508: private Class<E> elementType;
1509:
1510: /** Constructor.
1511: * @param valueType type of the property
1512: */
1513: public IndexedProperty(Class<T> valueType, Class<E> elementType) {
1514: super (valueType);
1515: this .elementType = elementType;
1516: }
1517:
1518: /** Test whether the property is readable by index.
1519: * @return <CODE>true</CODE> if so
1520: */
1521: public abstract boolean canIndexedRead();
1522:
1523: /** Get the element type of the property (not the type of the whole property).
1524: * @return the type
1525: */
1526: public Class<E> getElementType() {
1527: return elementType;
1528: }
1529:
1530: /** Get the value of the property at an index.
1531: *
1532: * @param index the index
1533: * @return the value at that index
1534: * @exception IllegalAccessException cannot access the called method
1535: * @exception IllegalArgumentException wrong argument
1536: * @exception InvocationTargetException an exception during invocation
1537: */
1538: public abstract E getIndexedValue(int index)
1539: throws IllegalAccessException,
1540: IllegalArgumentException, InvocationTargetException;
1541:
1542: /** Test whether the property is writable by index.
1543: * @return <CODE>true</CODE> if so
1544: */
1545: public abstract boolean canIndexedWrite();
1546:
1547: /** Set the value of the property at an index.
1548: *
1549: * @param indx the index
1550: * @param val the value to set
1551: * @exception IllegalAccessException cannot access the called method
1552: * @exception IllegalArgumentException wrong argument
1553: * @exception InvocationTargetException an exception during invocation
1554: */
1555: public abstract void setIndexedValue(int indx, E val)
1556: throws IllegalAccessException,
1557: IllegalArgumentException, InvocationTargetException;
1558:
1559: /** Get a property editor for individual elements in this property.
1560: * @return the property editor for elements
1561: */
1562: public PropertyEditor getIndexedPropertyEditor() {
1563: return java.beans.PropertyEditorManager
1564: .findEditor(elementType);
1565: }
1566:
1567: /* Standard equals implementation for all property
1568: * classes.
1569: * @param property The object to compare to
1570: */
1571: @Override
1572: public boolean equals(Object property) {
1573: try {
1574: if (!super .equals(property)) {
1575: return false;
1576: }
1577:
1578: Class<?> propElementType = ((IndexedProperty) property)
1579: .getElementType();
1580: Class<?> elementType = getElementType();
1581:
1582: if (((propElementType == null) && (elementType != null))
1583: || ((propElementType != null) && (elementType == null))) {
1584: return false;
1585: }
1586:
1587: return (((propElementType == null) && (elementType == null)) || propElementType
1588: .equals(elementType));
1589: } catch (ClassCastException e) {
1590: return false;
1591: }
1592: }
1593:
1594: /* Returns a hash code value for the object.
1595: *
1596: * @return int hashcode
1597: */
1598: @Override
1599: public int hashCode() {
1600: Class<?> ementType = getElementType();
1601:
1602: return super .hashCode()
1603: * ((elementType == null) ? 1 : elementType
1604: .hashCode());
1605: }
1606: }
1607:
1608: /** Special subclass of EventListenerList that can also listen on changes in
1609: * a lookup.
1610: */
1611: private final class LookupEventList extends EventListenerList
1612: implements LookupListener {
1613: public final Lookup lookup;
1614: private Lookup.Result<Node.Cookie> result;
1615:
1616: public LookupEventList(Lookup l) {
1617: this .lookup = l;
1618: }
1619:
1620: public Lookup init(boolean init) {
1621: boolean doInit = false;
1622:
1623: synchronized (INIT_LOCK) {
1624: if (init && (result == null)) {
1625: result = lookup.lookup(TEMPL_COOKIE);
1626: assert result != null : "Null lookup result from "
1627: + lookup + " in " + Node.this ;
1628: result.addLookupListener(this );
1629: doInit = true;
1630: }
1631: }
1632:
1633: if (doInit) {
1634: result.allItems();
1635: }
1636:
1637: return lookup;
1638: }
1639:
1640: public void resultChanged(LookupEvent ev) {
1641: if (Node.this instanceof FilterNode) {
1642: FilterNode f = (FilterNode) Node.this ;
1643:
1644: // See #40734 and NodeLookupTest and CookieActionIsTooSlowTest.
1645: if (f.getOriginal() == NodeLookup.NO_COOKIE_CHANGE
1646: .get()) {
1647: // this is not real cookie change, do not fire it
1648: // issue 40734
1649: return;
1650: }
1651: }
1652:
1653: fireCookieChange();
1654: }
1655: }
1656: }
|