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: package org.openide.nodes;
0042:
0043: import java.lang.ref.Reference;
0044: import java.lang.reflect.Method;
0045: import org.openide.util.HelpCtx;
0046: import org.openide.util.Lookup;
0047: import org.openide.util.LookupEvent;
0048: import org.openide.util.LookupListener;
0049: import org.openide.util.WeakSet;
0050: import org.openide.util.actions.SystemAction;
0051: import org.openide.util.datatransfer.NewType;
0052: import org.openide.util.datatransfer.PasteType;
0053:
0054: import java.awt.Image;
0055: import java.awt.datatransfer.Transferable;
0056:
0057: import java.beans.PropertyChangeEvent;
0058: import java.beans.PropertyChangeListener;
0059:
0060: import java.io.IOException;
0061:
0062: import java.lang.ref.WeakReference;
0063:
0064: import java.util.*;
0065: import java.util.logging.Level;
0066: import java.util.logging.Logger;
0067: import org.openide.nodes.Node.PropertySet;
0068: import org.openide.util.Exceptions;
0069: import org.openide.util.Lookup.Item;
0070:
0071: /** A proxy for another node.
0072: * Unless otherwise mentioned, all methods of the original node are delegated to.
0073: * If desired, you can disable delegation of certain methods which are concrete in <code>Node</code>
0074: * by calling {@link #disableDelegation}.
0075: *
0076: * <p><strong>Note:</strong> it is fine to subclass this class and use
0077: * it to filter things. But please do not ever try to cast a node to
0078: * <code>FilterNode</code>: it probably means you are doing something
0079: * wrong. Instead, ask whatever <code>Node</code> you have for a proper
0080: * kind of cookie (e.g. <code>DataObject</code>).
0081: *
0082: * @author Jaroslav Tulach
0083: */
0084: public class FilterNode extends Node {
0085: /** Whether to delegate <code>setName</code>. */
0086: protected static final int DELEGATE_SET_NAME = 1 << 0;
0087:
0088: /** Whether to delegate <code>getName</code>. */
0089: protected static final int DELEGATE_GET_NAME = 1 << 1;
0090:
0091: /** Whether to delegate <code>setDisplayName</code>. */
0092: protected static final int DELEGATE_SET_DISPLAY_NAME = 1 << 2;
0093:
0094: /** Whether to delegate <code>getDisplayName</code>. */
0095: protected static final int DELEGATE_GET_DISPLAY_NAME = 1 << 3;
0096:
0097: /** Whether to delegate <code>setShortDescription</code>. */
0098: protected static final int DELEGATE_SET_SHORT_DESCRIPTION = 1 << 4;
0099:
0100: /** Whether to delegate <code>getShortDescription</code>. */
0101: protected static final int DELEGATE_GET_SHORT_DESCRIPTION = 1 << 5;
0102:
0103: /** Whether to delegate <code>destroy</code>. */
0104: protected static final int DELEGATE_DESTROY = 1 << 6;
0105:
0106: /** Whether to delegate <code>getActions</code>. */
0107: protected static final int DELEGATE_GET_ACTIONS = 1 << 7;
0108:
0109: /** Whether to delegate <code>getContextActions</code>. */
0110: protected static final int DELEGATE_GET_CONTEXT_ACTIONS = 1 << 8;
0111:
0112: /** Whether to delegate <code>setValue</code>.
0113: * @since 4.25
0114: */
0115: protected static final int DELEGATE_SET_VALUE = 1 << 9;
0116:
0117: /** Whether to delegate <code>getValue</code>.
0118: * @since 4.25
0119: */
0120: protected static final int DELEGATE_GET_VALUE = 1 << 10;
0121:
0122: /** Mask indicating delegation of all possible methods. */
0123: private static final int DELEGATE_ALL = DELEGATE_SET_NAME
0124: | DELEGATE_GET_NAME | DELEGATE_SET_DISPLAY_NAME
0125: | DELEGATE_GET_DISPLAY_NAME
0126: | DELEGATE_SET_SHORT_DESCRIPTION
0127: | DELEGATE_GET_SHORT_DESCRIPTION | DELEGATE_DESTROY
0128: | DELEGATE_GET_ACTIONS | DELEGATE_GET_CONTEXT_ACTIONS
0129: | DELEGATE_SET_VALUE | DELEGATE_GET_VALUE;
0130: private static final Map<Class<?>, Boolean> overridesGetDisplayNameCache = new WeakHashMap<Class<?>, Boolean>(
0131: 27);
0132: private static final Map<Class<?>, Boolean> replaceProvidedLookupCache = new WeakHashMap<Class<?>, Boolean>(
0133: 27);
0134:
0135: /** Depth of stack trace.
0136: */
0137: private static volatile int hashCodeDepth;
0138:
0139: /** node to delegate to */
0140: private Node original;
0141:
0142: /** listener to property changes,
0143: * accessible thru getPropertyChangeListener
0144: */
0145: private PropertyChangeListener propL;
0146:
0147: /** listener to node changes
0148: * Accessible thru get node listener
0149: */
0150: private NodeListener nodeL;
0151:
0152: // Note: int (not long) to avoid need to ever synchronize when accessing it
0153: // (Java VM spec does not guarantee that long's will be stored atomically)
0154:
0155: /** @see #delegating */
0156: private int delegateMask;
0157:
0158: /** Is PropertyChangeListener attached to original node */
0159: private boolean pchlAttached = false;
0160:
0161: /** children provided or created the default ones? */
0162: private boolean childrenProvided;
0163:
0164: /** Create proxy.
0165: * @param original the node to delegate to
0166: */
0167: public FilterNode(Node original) {
0168: this (original, null);
0169: }
0170:
0171: /** Create proxy with a different set of children.
0172: *
0173: * @param original the node to delegate to
0174: * @param children a set of children for this node
0175: */
0176: public FilterNode(Node original, org.openide.nodes.Children children) {
0177: this (original, children, new FilterLookup());
0178: }
0179:
0180: /** Constructs new filter node with a provided children and lookup.
0181: * The lookup is used to implement {@link FilterNode#getCookie} calls that just call
0182: * <code>lookup.lookup(clazz)</code>. If this constructor is used,
0183: * the code shall not override {@link FilterNode#getCookie} method, but do all
0184: * its state manipulation in the lookup. Look at {@link Node#Node}
0185: * constructor for best practices usage of this constructor.
0186: *
0187: * @param original the node we delegate to
0188: * @param children the children to use for the filter node or <code>null</code> if
0189: * default children should be provided
0190: * @param lookup lookup to use. Do not pass <CODE>orginal.getLookup()</CODE> into this parameter.
0191: * In such case use the {@link #FilterNode(Node, Children)} constructor.
0192: *
0193: * @since 4.4
0194: */
0195: public FilterNode(Node original,
0196: org.openide.nodes.Children children, Lookup lookup) {
0197: super (
0198: (children == null) ? (original.isLeaf() ? org.openide.nodes.Children.LEAF
0199: : new Children(original))
0200: : children, lookup);
0201:
0202: this .childrenProvided = children != null;
0203: this .original = original;
0204: init();
0205:
0206: Lookup lkp = internalLookup(false);
0207:
0208: if (lkp instanceof FilterLookup) {
0209: ((FilterLookup) lkp).ownNode(this );
0210: } else {
0211: if (lkp == null) {
0212: // rely on default NodeLookup around getCookie.
0213: getNodeListener();
0214: }
0215: }
0216: }
0217:
0218: /** Overrides package private method of a node that allows us to say
0219: * that the lookup provided in the constructor should be replaced by
0220: * something else
0221: *
0222: * @param lookup
0223: * @return lookup or null
0224: */
0225: final Lookup replaceProvidedLookup(Lookup lookup) {
0226: synchronized (replaceProvidedLookupCache) {
0227: Boolean b = replaceProvidedLookupCache.get(getClass());
0228:
0229: if (b == null) {
0230: b = !overridesAMethod("getCookie", Class.class); // NOI18N
0231: replaceProvidedLookupCache.put(getClass(), b);
0232: }
0233:
0234: return b ? lookup : null;
0235: }
0236: }
0237:
0238: /** Checks whether subclass overrides a method
0239: */
0240: private boolean overridesAMethod(String name, Class... arguments) {
0241: if (getClass() == FilterNode.class) {
0242: return false;
0243: }
0244:
0245: // we are subclass of FilterNode
0246: try {
0247: Method m = getClass().getMethod(name, arguments);
0248:
0249: if (m.getDeclaringClass() != FilterNode.class) {
0250: // ok somebody overriden getCookie method
0251: return true;
0252: }
0253: } catch (NoSuchMethodException ex) {
0254: Exceptions.printStackTrace(ex);
0255: }
0256:
0257: return false;
0258: }
0259:
0260: /** Initializes the node.
0261: */
0262: private void init() {
0263: delegateMask = DELEGATE_ALL;
0264: }
0265:
0266: void notifyPropertyChangeListenerAdded(PropertyChangeListener l) {
0267: if (!pchlAttached) {
0268: original
0269: .addPropertyChangeListener(getPropertyChangeListener());
0270: pchlAttached = true;
0271: }
0272: }
0273:
0274: void notifyPropertyChangeListenerRemoved(PropertyChangeListener l) {
0275: if (getPropertyChangeListenersCount() == 0) {
0276: original
0277: .removePropertyChangeListener(getPropertyChangeListener());
0278: pchlAttached = false;
0279: }
0280: }
0281:
0282: /** Removes all listeners (property and node) on
0283: * the original node. Called from {@link NodeListener#nodeDestroyed},
0284: * but can be called by any subclass to stop reflecting changes
0285: * in the original node.
0286: */
0287: @Override
0288: protected void finalize() {
0289: original
0290: .removePropertyChangeListener(getPropertyChangeListener());
0291: original.removeNodeListener(getNodeListener());
0292: }
0293:
0294: /** Enable delegation of a set of methods.
0295: * These will be delegated to the original node.
0296: * Since all available methods are delegated by default, normally you will not need to call this.
0297: * @param mask bitwise disjunction of <code>DELEGATE_XXX</code> constants
0298: * @throws IllegalArgumentException if the mask is invalid
0299: */
0300: protected final void enableDelegation(int mask) {
0301: if ((mask & ~DELEGATE_ALL) != 0) {
0302: throw new IllegalArgumentException("Bad delegation mask: "
0303: + mask); // NOI18N
0304: }
0305:
0306: delegateMask |= mask;
0307: }
0308:
0309: /** Disable delegation of a set of methods.
0310: * The methods will retain their behavior from {@link Node}.
0311: * <p>For example, if you wish to subclass <code>FilterNode</code>, giving your
0312: * node a distinctive display name and tooltip, and performing some special
0313: * action upon deletion, you may do so without risk of affecting the original
0314: * node as follows:
0315: * <br><code><pre>
0316: * public MyNode extends FilterNode {
0317: * public MyNode (Node orig) {
0318: * super (orig, new MyChildren (orig));
0319: * disableDelegation (DELEGATE_GET_DISPLAY_NAME | DELEGATE_SET_DISPLAY_NAME |
0320: * DELEGATE_GET_SHORT_DESCRIPTION | DELEGATE_SET_SHORT_DESCRIPTION |
0321: * DELEGATE_DESTROY);
0322: * // these will affect only the filter node:
0323: * setDisplayName ("Linking -> " + orig.getDisplayName ());
0324: * setShortDescription ("Something different.");
0325: * }
0326: * public boolean canRename () { return false; }
0327: * public void destroy () throws IOException {
0328: * doMyCleanup ();
0329: * super.destroy (); // calls Node.destroy(), not orig.destroy()
0330: * }
0331: * }
0332: * </pre></code>
0333: * <br>You may still manually delegate where desired using {@link #getOriginal}.
0334: * Other methods abstract in <code>Node</code> may simply be overridden without
0335: * any special handling.
0336: * @param mask bitwise disjunction of <code>DELEGATE_XXX</code> constants
0337: * @throws IllegalArgumentException if the mask is invalid
0338: */
0339: protected final void disableDelegation(int mask) {
0340: if ((mask & ~DELEGATE_ALL) != 0) {
0341: throw new IllegalArgumentException("Bad delegation mask: "
0342: + mask); // NOI18N
0343: }
0344:
0345: delegateMask &= ~mask;
0346: }
0347:
0348: /** Test whether we are currently delegating to some method. */
0349: private final boolean delegating(int what) {
0350: return (delegateMask & what) != 0;
0351: }
0352:
0353: /** Create new filter node for the original.
0354: * Subclasses do not have to override this, but if they do not,
0355: * the default implementation will filter the subclass filter, which is not
0356: * very efficient.
0357: * @return copy of this node
0358: */
0359: public Node cloneNode() {
0360: if (isDefault()) {
0361: // this is realy filter node without changed behaviour
0362: // with the normal children => use normal constructor for the
0363: // original node
0364: return new FilterNode(original);
0365: } else {
0366: // create filter node for this node to reflect changed
0367: // behaviour
0368: return new FilterNode(this );
0369: }
0370: }
0371:
0372: /** Tries to prevent issue 46993 by checking whether a node
0373: * to be set as original is actually pointing to this node.
0374: * @exception IllegalArgumentException if the check fails
0375: * @return always true
0376: */
0377: private boolean checkIfIamAccessibleFromOriginal(Node original) {
0378: if (this == original) {
0379: throw new IllegalArgumentException(
0380: "Node cannot be its own original (even thru indirect chain)"); // NOI18N
0381: }
0382:
0383: if (original instanceof FilterNode) {
0384: FilterNode f = (FilterNode) original;
0385: checkIfIamAccessibleFromOriginal(f.original);
0386: }
0387:
0388: return true;
0389: }
0390:
0391: /** Changes the original node for this node.
0392: *@param original The new original node.
0393: *@param changeChildren If set to <CODE>true</CODE> changes children
0394: * of this node according to the new original node. If you pass
0395: * children which are not instance of class
0396: * {@link FilterNode.Children} into the constructor set this
0397: * parameter to <CODE>false</CODE>. Be aware
0398: * that this method aquires
0399: * write lock on the nodes hierarchy ({@link Children#MUTEX}). Take care not to call this method
0400: * under read lock.
0401: *
0402: *@throws java.lang.IllegalStateException if children which are not
0403: * instance of <CODE>FilterNode.Children</CODE> were passed
0404: * into the constructor and the method was called with the parameter
0405: * <CODE>changeChildren</CODE> set to <CODE>true</CODE>.
0406: *@since 1.39
0407: */
0408: protected final void changeOriginal(Node original,
0409: boolean changeChildren) {
0410: if (changeChildren
0411: && !(getChildren() instanceof FilterNode.Children)
0412: && !(getChildren() == Children.LEAF /* && original.isLeaf () */)) {
0413: throw new IllegalStateException(
0414: "Can't change implicitly defined Children on FilterNode"); // NOI18N
0415: }
0416:
0417: assert checkIfIamAccessibleFromOriginal(original) : ""; // NOI18N
0418:
0419: try {
0420: Children.PR.enterWriteAccess();
0421:
0422: // First remove the listeners from current original node
0423: this .original.removeNodeListener(getNodeListener());
0424:
0425: if (pchlAttached) {
0426: this .original
0427: .removePropertyChangeListener(getPropertyChangeListener());
0428: }
0429:
0430: // Set the new original node
0431: this .original = original;
0432:
0433: // attach listeners to new original node
0434: this .original.addNodeListener(getNodeListener());
0435:
0436: if (pchlAttached) {
0437: this .original
0438: .addPropertyChangeListener(getPropertyChangeListener());
0439: }
0440:
0441: // Reset children's original node.
0442: if (changeChildren /* && !original.isLeaf () */) {
0443: if (original.isLeaf()
0444: && (getChildren() != Children.LEAF)) {
0445: setChildren(Children.LEAF);
0446: } else if (!original.isLeaf()
0447: && (getChildren() == Children.LEAF)) {
0448: setChildren(new Children(original));
0449: } else if (!original.isLeaf()
0450: && (getChildren() != Children.LEAF)) {
0451: ((FilterNode.Children) getChildren())
0452: .changeOriginal(original);
0453: }
0454: }
0455: } finally {
0456: Children.PR.exitWriteAccess();
0457: }
0458:
0459: // Fire all sorts of events (everything gets changed after we
0460: // reset the original node.)
0461: Lookup lkp = internalLookup(false);
0462:
0463: if (lkp instanceof FilterLookup) {
0464: ((FilterLookup) lkp).checkNode();
0465: }
0466:
0467: fireCookieChange();
0468: fireNameChange(null, null);
0469: fireDisplayNameChange(null, null);
0470: fireShortDescriptionChange(null, null);
0471: fireIconChange();
0472: fireOpenedIconChange();
0473: firePropertySetsChange(null, null);
0474: }
0475:
0476: // ------------- START OF DELEGATED METHODS ------------
0477: @Override
0478: public void setValue(String attributeName, Object value) {
0479: if (delegating(DELEGATE_SET_VALUE)) {
0480: original.setValue(attributeName, value);
0481: } else {
0482: super .setValue(attributeName, value);
0483: }
0484: }
0485:
0486: @Override
0487: public Object getValue(String attributeName) {
0488: if (delegating(DELEGATE_GET_VALUE)) {
0489: return original.getValue(attributeName);
0490: } else {
0491: return super .getValue(attributeName);
0492: }
0493: }
0494:
0495: /* Setter for system name. Fires info about property change.
0496: * @param s the string
0497: */
0498: @Override
0499: public void setName(String s) {
0500: if (delegating(DELEGATE_SET_NAME)) {
0501: original.setName(s);
0502: } else {
0503: super .setName(s);
0504: }
0505: }
0506:
0507: /* @return the name of the original node
0508: */
0509: @Override
0510: public String getName() {
0511: if (delegating(DELEGATE_GET_NAME)) {
0512: return original.getName();
0513: } else {
0514: return super .getName();
0515: }
0516: }
0517:
0518: /* Setter for display name. Fires info about property change.
0519: * @param s the string
0520: */
0521: @Override
0522: public void setDisplayName(String s) {
0523: if (delegating(DELEGATE_SET_DISPLAY_NAME)) {
0524: original.setDisplayName(s);
0525: } else {
0526: super .setDisplayName(s);
0527: }
0528: }
0529:
0530: /* @return the display name of the original node
0531: */
0532: @Override
0533: public String getDisplayName() {
0534: if (delegating(DELEGATE_GET_DISPLAY_NAME)) {
0535: return original.getDisplayName();
0536: } else {
0537: return super .getDisplayName();
0538: }
0539: }
0540:
0541: /* Setter for short description. Fires info about property change.
0542: * @param s the string
0543: */
0544: @Override
0545: public void setShortDescription(String s) {
0546: if (delegating(DELEGATE_SET_SHORT_DESCRIPTION)) {
0547: original.setShortDescription(s);
0548: } else {
0549: super .setShortDescription(s);
0550: }
0551: }
0552:
0553: /* @return the description of the original node
0554: */
0555: @Override
0556: public String getShortDescription() {
0557: if (delegating(DELEGATE_GET_SHORT_DESCRIPTION)) {
0558: return original.getShortDescription();
0559: } else {
0560: return super .getShortDescription();
0561: }
0562: }
0563:
0564: /* Finds an icon for this node. Delegates to the original.
0565: *
0566: * @see java.bean.BeanInfo
0567: * @param type constants from <CODE>java.bean.BeanInfo</CODE>
0568: * @return icon to use to represent the bean
0569: */
0570: public Image getIcon(int type) {
0571: return original.getIcon(type);
0572: }
0573:
0574: /* Finds an icon for this node. This icon should represent the node
0575: * when it is opened (if it can have children). Delegates to original.
0576: *
0577: * @see java.bean.BeanInfo
0578: * @param type constants from <CODE>java.bean.BeanInfo</CODE>
0579: * @return icon to use to represent the bean when opened
0580: */
0581: public Image getOpenedIcon(int type) {
0582: return original.getOpenedIcon(type);
0583: }
0584:
0585: public HelpCtx getHelpCtx() {
0586: return original.getHelpCtx();
0587: }
0588:
0589: /* Can the original node be renamed?
0590: *
0591: * @return true if the node can be renamed
0592: */
0593: public boolean canRename() {
0594: return original.canRename();
0595: }
0596:
0597: /* Can the original node be deleted?
0598: * @return <CODE>true</CODE> if can, <CODE>false</CODE> otherwise
0599: */
0600: public boolean canDestroy() {
0601: return original.canDestroy();
0602: }
0603:
0604: /* Degelates the delete operation to original.
0605: */
0606: @Override
0607: public void destroy() throws java.io.IOException {
0608: if (delegating(DELEGATE_DESTROY)) {
0609: original.destroy();
0610: } else {
0611: super .destroy();
0612: }
0613: }
0614:
0615: /** Used to access the destroy method when original nodes
0616: * has been deleted
0617: */
0618: private final void originalDestroyed() {
0619: try {
0620: super .destroy();
0621: } catch (IOException ex) {
0622: Logger.getLogger(FilterNode.class.getName()).log(
0623: Level.WARNING, null, ex);
0624: }
0625: }
0626:
0627: /* Getter for the list of property sets. Delegates to original.
0628: *
0629: * @return the array of property sets.
0630: */
0631: public PropertySet[] getPropertySets() {
0632: return original.getPropertySets();
0633: }
0634:
0635: /* Called when an object is to be copied to clipboard.
0636: * @return the transferable object dedicated to represent the
0637: * content of clipboard
0638: * @exception IOException is thrown when the
0639: * operation cannot be performed
0640: */
0641: public Transferable clipboardCopy() throws IOException {
0642: return original.clipboardCopy();
0643: }
0644:
0645: /* Called when an object is to be cut to clipboard.
0646: * @return the transferable object dedicated to represent the
0647: * content of clipboard
0648: * @exception IOException is thrown when the
0649: * operation cannot be performed
0650: */
0651: public Transferable clipboardCut() throws IOException {
0652: return original.clipboardCut();
0653: }
0654:
0655: /* Returns true if this object allows copying.
0656: * @returns true if this object allows copying.
0657: */
0658: public boolean canCopy() {
0659: return original.canCopy();
0660: }
0661:
0662: /* Returns true if this object allows cutting.
0663: * @returns true if this object allows cutting.
0664: */
0665: public boolean canCut() {
0666: return original.canCut();
0667: }
0668:
0669: public Transferable drag() throws IOException {
0670: return original.drag();
0671: }
0672:
0673: /* Default implementation that tries to delegate the implementation
0674: * to the createPasteTypes method. Simply calls the method and
0675: * tries to take the first provided argument. Ignores the action
0676: * argument and index.
0677: *
0678: * @param t the transferable
0679: * @param action the drag'n'drop action to do DnDConstants.ACTION_MOVE, ACTION_COPY, ACTION_LINK
0680: * @param index index between children the drop occured at or -1 if not specified
0681: * @return null if the transferable cannot be accepted or the paste type
0682: * to execute when the drop occures
0683: */
0684: public PasteType getDropType(Transferable t, int action, int index) {
0685: return original.getDropType(t, action, index);
0686: }
0687:
0688: /* Which paste operations are allowed when transferable t is in clipboard?
0689: * @param t the transferable in clipboard
0690: * @return array of operations that are allowed
0691: */
0692: public PasteType[] getPasteTypes(Transferable t) {
0693: return original.getPasteTypes(t);
0694: }
0695:
0696: /* Support for new types that can be created in this node.
0697: * @return array of new type operations that are allowed
0698: */
0699: public NewType[] getNewTypes() {
0700: return original.getNewTypes();
0701: }
0702:
0703: /* Delegates to original.
0704: *
0705: * @return array of system actions that should be in popup menu
0706: */
0707: @Override
0708: @Deprecated
0709: public SystemAction[] getActions() {
0710: if (delegating(DELEGATE_GET_ACTIONS)) {
0711: return original.getActions();
0712: } else {
0713: return super .getActions();
0714: }
0715: }
0716:
0717: /* Delegates to original
0718: */
0719: @Override
0720: @Deprecated
0721: public SystemAction[] getContextActions() {
0722: if (delegating(DELEGATE_GET_CONTEXT_ACTIONS)) {
0723: return original.getContextActions();
0724: } else {
0725: return super .getContextActions();
0726: }
0727: }
0728:
0729: /*
0730: * @return default action of the original node or null
0731: */
0732: @Override
0733: @Deprecated
0734: public SystemAction getDefaultAction() {
0735: return original.getDefaultAction();
0736: }
0737:
0738: @Override
0739: public javax.swing.Action[] getActions(boolean context) {
0740: if (context) {
0741: if (!delegating(DELEGATE_GET_ACTIONS)
0742: || overridesAMethod("getContextActions")) { // NOI18N
0743:
0744: return super .getActions(context);
0745: }
0746: } else {
0747: if (!delegating(DELEGATE_GET_CONTEXT_ACTIONS)
0748: || overridesAMethod("getActions")) { // NOI18N
0749:
0750: return super .getActions(context);
0751: }
0752: }
0753:
0754: javax.swing.Action[] retValue;
0755: retValue = original.getActions(context);
0756:
0757: return retValue;
0758: }
0759:
0760: @Override
0761: public javax.swing.Action getPreferredAction() {
0762: javax.swing.Action retValue;
0763:
0764: if (overridesAMethod("getDefaultAction")) { // NOI18N
0765: retValue = super .getPreferredAction();
0766: } else {
0767: retValue = original.getPreferredAction();
0768: }
0769:
0770: return retValue;
0771: }
0772:
0773: /** Get a display name containing HTML markup. <strong><b>Note:</b> If you subclass
0774: * FilterNode and override <code>getDisplayName()</code>, this method will
0775: * always return null unless you override it as well (assuming that if you're
0776: * changing the display name, you don't want an HTML display name constructed
0777: * from the original node's display name to be what shows up in views of
0778: * this node).</strong> If <code>getDisplayName()</code> is not overridden,
0779: * this method will return whatever the original node returns from this
0780: * method.
0781: * <p>
0782: * Note that if you do override <code>getDisplayName</code>, you should also override
0783: * this method to return null.
0784: *
0785: *
0786: *
0787: * @see org.openide.nodes.Node#getHtmlDisplayName
0788: * @return An HTML display name, if available, or null if no display name
0789: * is available */
0790: @Override
0791: public String getHtmlDisplayName() {
0792: if (overridesGetDisplayName()) {
0793: return null;
0794: } else {
0795: return delegating(DELEGATE_GET_DISPLAY_NAME) ? original
0796: .getHtmlDisplayName() : super .getHtmlDisplayName();
0797: }
0798: }
0799:
0800: private boolean overridesGetDisplayName() {
0801: synchronized (overridesGetDisplayNameCache) {
0802: Boolean b = overridesGetDisplayNameCache.get(getClass());
0803:
0804: if (b == null) {
0805: b = overridesAMethod("getDisplayName"); // NOI18N
0806: overridesGetDisplayNameCache.put(getClass(), b);
0807: }
0808:
0809: return b;
0810: }
0811: }
0812:
0813: /*
0814: * @return <CODE>true</CODE> if the original has a customizer.
0815: */
0816: public boolean hasCustomizer() {
0817: return original.hasCustomizer();
0818: }
0819:
0820: /* Returns the customizer component.
0821: * @return the component or <CODE>null</CODE> if there is no customizer
0822: */
0823: public java.awt.Component getCustomizer() {
0824: return original.getCustomizer();
0825: }
0826:
0827: /** Delegates to original, if no special lookup provided in constructor,
0828: * Otherwise it delegates to the lookup. Never override this method
0829: * if the lookup is provided in constructor.
0830: *
0831: * @param type the class to look for
0832: * @return instance of that class or null if this class of cookie
0833: * is not supported
0834: * @see Node#getCookie
0835: */
0836: @Override
0837: public <T extends Node.Cookie> T getCookie(Class<T> type) {
0838: Lookup l = internalLookup(true);
0839:
0840: if (l != null) {
0841: Object res = l.lookup(type);
0842: return type.isInstance(res) && res instanceof Node.Cookie ? type
0843: .cast(res)
0844: : null;
0845: }
0846:
0847: return original.getCookie(type);
0848: }
0849:
0850: /** If this is FilterNode without any changes (subclassed, changed children)
0851: * and the original provides handle, stores them and
0852: * returns a new handle for the proxy.
0853: * <p>Subclasses <strong>must</strong> override this if they wish for their nodes to be
0854: * properly serializable.
0855: *
0856: * @return the handle, or <code>null</code> if this node is subclassed or
0857: * uses changed children
0858: */
0859: public Node.Handle getHandle() {
0860: if (!isDefault()) {
0861: // subclasses has to implement the method by its own
0862: return null;
0863: }
0864:
0865: Node.Handle original = this .original.getHandle();
0866:
0867: if (original == null) {
0868: // no original handle => no handle here
0869: return null;
0870: }
0871:
0872: return new FilterHandle(original);
0873: }
0874:
0875: /** Test equality of original nodes.
0876: * Note that for subclasses of <code>FilterNode</code>, or filter nodes with non-default children,
0877: * the test reverts to object identity.
0878: * <strong>Note:</strong> if you wish that the {@link Index} cookie works correctly on
0879: * filtered nodes and their subnodes, and you are subclassing <code>FilterNode</code> or
0880: * using non-default children, you will probably want to override this method to test
0881: * equality of the specified node with this filter node's original node; otherwise Move Up
0882: * and Move Down actions may be disabled.
0883: * <p>Note though that it is often better to provide your own index cookie from a filter
0884: * node. Only then it is possible to change the number of children relative to the original.
0885: * And in many cases this is easier anyway, as for example with
0886: * <code>DataFolder.Index</code> for data folders.
0887: * @param o something to compare to, presumably a node or <code>FilterNode</code> of one
0888: * @return true if this node's original node is the same as the parameter (or original node of parameter)
0889: */
0890: @Override
0891: public boolean equals(Object o) {
0892: // VERY DANGEROUS! Completely messes up visualizers and often original node is displayed rather than filter.
0893: // Jst: I know that it is dangerous, but some code probably depends on it
0894: if (!(o instanceof Node)) {
0895: return false; // something else or null
0896: }
0897:
0898: if (this == o) {
0899: return true; // shortcut
0900: }
0901:
0902: // get the "most original" ones....
0903: Node left = getRepresentation(this );
0904: Node right = getRepresentation((Node) o);
0905:
0906: // cover nondefault FilterNodes (possibly) deep in the stack
0907: if ((left instanceof FilterNode)
0908: || (right instanceof FilterNode)) {
0909: return left == right;
0910: }
0911:
0912: return left.equals(right);
0913: }
0914:
0915: private static Node getRepresentation(Node n) {
0916: while (n instanceof FilterNode) {
0917: FilterNode fn = (FilterNode) n;
0918:
0919: if (!fn.isDefault()) {
0920: return n;
0921: }
0922:
0923: n = fn.original;
0924: }
0925:
0926: return n; // either node or nondefault FilterNode
0927: }
0928:
0929: /** Hash by original nodes.
0930: * Note that for subclasses of <code>FilterNode</code>, or filter nodes with non-default children,
0931: * the hash reverts to the identity hash code.
0932: * @return the delegated hash code
0933: */
0934: public int hashCode() {
0935: try {
0936: assert hashCodeLogging(true) : ""; // NOI18N
0937:
0938: int result = isDefault() ? original.hashCode() : System
0939: .identityHashCode(this );
0940: assert hashCodeLogging(false) : ""; // NOI18N
0941:
0942: return result;
0943: } catch (StackError err) {
0944: err.add(this );
0945: throw err;
0946: }
0947: }
0948:
0949: /** Method for tracing the issue 46993. Counts the depth of execution
0950: * and if larger than 1000 throws debugging exception.
0951: */
0952: private static boolean hashCodeLogging(boolean enter) {
0953: if (hashCodeDepth > 1000) {
0954: hashCodeDepth = 0;
0955: throw new StackError();
0956: }
0957:
0958: if (enter) {
0959: hashCodeDepth++;
0960: } else {
0961: hashCodeDepth--;
0962: }
0963:
0964: return true;
0965: }
0966:
0967: // public String toString () {
0968: // return super.toString () + " original has children: " + original.getChildren ().getNodesCount (); // NOI18N
0969: // }
0970: // ----------- END OF DELEGATED METHODS ------------
0971:
0972: /** Get the original node.
0973: * <p><strong>Yes</strong> this is supposed to be protected! If you
0974: * are not subclassing <code>FilterNode</code> yourself, you should
0975: * not be calling it (nor casting to <code>FilterNode</code>). Use
0976: * cookies instead.
0977: * @return the node proxied to
0978: */
0979: protected Node getOriginal() {
0980: return original;
0981: }
0982:
0983: /** Create a property change listener that allows listening on the
0984: * original node properties (contained in property sets) and propagating
0985: * them to the proxy.
0986: * <P>
0987: * This method is called during initialization and allows subclasses
0988: * to modify the default behaviour.
0989: *
0990: * @return a {@link PropertyChangeAdapter} in the default implementation
0991: */
0992: protected PropertyChangeListener createPropertyChangeListener() {
0993: return new PropertyChangeAdapter(this );
0994: }
0995:
0996: /** Creates a node listener that allows listening on the
0997: * original node and propagating events to the proxy.
0998: * <p>Intended for overriding by subclasses, as with {@link #createPropertyChangeListener}.
0999: *
1000: * @return a {@link FilterNode.NodeAdapter} in the default implementation
1001: */
1002: protected NodeListener createNodeListener() {
1003: return new NodeAdapter(this );
1004: }
1005:
1006: /** Getter for property change listener.
1007: */
1008: synchronized PropertyChangeListener getPropertyChangeListener() {
1009: if (propL == null) {
1010: propL = createPropertyChangeListener();
1011: }
1012:
1013: return propL;
1014: }
1015:
1016: /** Getter for node listener.
1017: */
1018: synchronized NodeListener getNodeListener() {
1019: if (nodeL == null) {
1020: nodeL = createNodeListener();
1021: getOriginal().addNodeListener(nodeL);
1022: }
1023:
1024: return nodeL;
1025: }
1026:
1027: /** Notified from Node that a listener has been added.
1028: * Thus we force initialization of listeners.
1029: */
1030: final void listenerAdded() {
1031: getNodeListener();
1032: }
1033:
1034: /** Check method whether the node has default behaviour or
1035: * if it is either subclass of uses different children.
1036: * @return true if it is default
1037: */
1038: private boolean isDefault() {
1039: //System.err.print ("FilterNode.isDefault: ");
1040: if (getClass() != FilterNode.class) {
1041: //System.err.println("false\n\tsubclass of FilterNode");
1042: return false;
1043: }
1044:
1045: return !childrenProvided;
1046: }
1047:
1048: /**
1049: * This method is used to change the Children from Children.LEAF to Children
1050: * typically used to when there is a setChildren() on the original node
1051: * setChildren will fire the appropriate events
1052: */
1053: final void updateChildren() {
1054: if (isDefault()) {
1055: org.openide.nodes.Children newChildren = null;
1056:
1057: try {
1058: Children.PR.enterReadAccess();
1059:
1060: if ((original.hierarchy == Children.LEAF)
1061: && (hierarchy != Children.LEAF)) {
1062: newChildren = Children.LEAF;
1063: } else if ((original.hierarchy != Children.LEAF)
1064: && (hierarchy == Children.LEAF)) {
1065: newChildren = new Children(original);
1066: }
1067: } finally {
1068: Children.PR.exitReadAccess();
1069: }
1070:
1071: if (newChildren != null) {
1072: final org.openide.nodes.Children set = newChildren;
1073: Children.MUTEX.postWriteRequest(new Runnable() {
1074: public void run() {
1075: setChildren(set);
1076: }
1077: });
1078: }
1079: }
1080: }
1081:
1082: /** An exception to be thrown from hashCode() to debug issue 46993.
1083: */
1084: private static class StackError extends StackOverflowError {
1085: private IdentityHashMap<FilterNode, FilterNode> nodes;
1086:
1087: public void add(FilterNode n) {
1088: if (nodes == null) {
1089: nodes = new IdentityHashMap<FilterNode, FilterNode>();
1090: }
1091:
1092: if (!nodes.containsKey(n)) {
1093: nodes.put(n, n);
1094: }
1095: }
1096:
1097: @Override
1098: public String getMessage() {
1099: StringBuffer sb = new StringBuffer();
1100: sb.append("StackOver in FilterNodes:\n"); // NOI18N
1101:
1102: for (FilterNode f : nodes.keySet()) {
1103: sb.append(" class: "); // NOI18N
1104: sb.append(f.getClass().getName());
1105: sb.append(" id: "); // NOI18N
1106: sb.append(Integer.toString(System.identityHashCode(f),
1107: 16));
1108: sb.append("\n"); // NOI18N
1109: }
1110:
1111: return sb.toString();
1112: }
1113: }
1114:
1115: /** Adapter that listens on changes in an original node
1116: * and refires them in a proxy.
1117: * This adapter is created during
1118: * initialization in {@link FilterNode#createPropertyChangeListener}. The method
1119: * can be overridden and this class used as the super class for the
1120: * new implementation.
1121: * <P>
1122: * A reference to the proxy is stored by weak reference, so it does not
1123: * prevent the node from being finalized.
1124: */
1125: protected static class PropertyChangeAdapter extends Object
1126: implements PropertyChangeListener {
1127: private Reference<FilterNode> fn;
1128:
1129: /** Create a new adapter.
1130: * @param fn the proxy
1131: */
1132: public PropertyChangeAdapter(FilterNode fn) {
1133: this .fn = new WeakReference<FilterNode>(fn);
1134: }
1135:
1136: /* Find the node we are attached to. If it is not null call property
1137: * change method with two arguments.
1138: */
1139: public final void propertyChange(PropertyChangeEvent ev) {
1140: FilterNode fn = this .fn.get();
1141:
1142: if (fn == null) {
1143: return;
1144: }
1145:
1146: propertyChange(fn, ev);
1147: }
1148:
1149: /** Actually propagate the event.
1150: * Intended for overriding.
1151: * @param fn the proxy
1152: * @param ev the event
1153: */
1154: protected void propertyChange(FilterNode fn,
1155: PropertyChangeEvent ev) {
1156: fn.firePropertyChange(ev.getPropertyName(), ev
1157: .getOldValue(), ev.getNewValue());
1158: }
1159: }
1160:
1161: /** Adapter that listens on changes in an original node and refires them
1162: * in a proxy. Created in {@link FilterNode#createNodeListener}.
1163: * @see FilterNode.PropertyChangeAdapter
1164: */
1165: protected static class NodeAdapter extends Object implements
1166: NodeListener {
1167: private Reference<FilterNode> fn;
1168:
1169: /** Create an adapter.
1170: * @param fn the proxy
1171: */
1172: public NodeAdapter(FilterNode fn) {
1173: this .fn = new WeakReference<FilterNode>(fn);
1174: }
1175:
1176: /* Tests if the reference to the node provided in costructor is
1177: * still valid (it has not been finalized) and if so, calls propertyChange (Node, ev).
1178: */
1179: public final void propertyChange(PropertyChangeEvent ev) {
1180: FilterNode fn = this .fn.get();
1181:
1182: if (fn == null) {
1183: return;
1184: }
1185:
1186: propertyChange(fn, ev);
1187: }
1188:
1189: /** Actually refire the change event in a subclass.
1190: * The default implementation ignores changes of the <code>parentNode</code> property but refires
1191: * everything else.
1192: *
1193: * @param fn the filter node
1194: * @param ev the event to fire
1195: */
1196: protected void propertyChange(FilterNode fn,
1197: PropertyChangeEvent ev) {
1198: String n = ev.getPropertyName();
1199:
1200: if (n.equals(Node.PROP_PARENT_NODE)) {
1201: // does nothing
1202: return;
1203: }
1204:
1205: if (n.equals(Node.PROP_DISPLAY_NAME)) {
1206: fn.fireOwnPropertyChange(PROP_DISPLAY_NAME, (String) ev
1207: .getOldValue(), (String) ev.getNewValue());
1208:
1209: return;
1210: }
1211:
1212: if (n.equals(Node.PROP_NAME)) {
1213: fn.fireOwnPropertyChange(PROP_NAME, (String) ev
1214: .getOldValue(), (String) ev.getNewValue());
1215:
1216: return;
1217: }
1218:
1219: if (n.equals(Node.PROP_SHORT_DESCRIPTION)) {
1220: fn.fireOwnPropertyChange(PROP_SHORT_DESCRIPTION,
1221: (String) ev.getOldValue(), (String) ev
1222: .getNewValue());
1223:
1224: return;
1225: }
1226:
1227: if (n.equals(Node.PROP_ICON)) {
1228: fn.fireIconChange();
1229:
1230: return;
1231: }
1232:
1233: if (n.equals(Node.PROP_OPENED_ICON)) {
1234: fn.fireOpenedIconChange();
1235:
1236: return;
1237: }
1238:
1239: if (n.equals(Node.PROP_PROPERTY_SETS)) {
1240: fn.firePropertySetsChange((PropertySet[]) ev
1241: .getOldValue(), (PropertySet[]) ev
1242: .getNewValue());
1243:
1244: return;
1245: }
1246:
1247: if (n.equals(Node.PROP_COOKIE)) {
1248: fn.fireCookieChange();
1249:
1250: return;
1251: }
1252:
1253: if (n.equals(Node.PROP_LEAF)) {
1254: fn.updateChildren();
1255:
1256: /*
1257: fn.fireOwnPropertyChange(
1258: Node.PROP_LEAF, ev.getOldValue(), ev.getNewValue()
1259: );
1260: */
1261: }
1262: }
1263:
1264: /** Does nothing.
1265: * @param ev event describing the action
1266: */
1267: public void childrenAdded(NodeMemberEvent ev) {
1268: }
1269:
1270: /** Does nothing.
1271: * @param ev event describing the action
1272: */
1273: public void childrenRemoved(NodeMemberEvent ev) {
1274: }
1275:
1276: /** Does nothing.
1277: * @param ev event describing the action
1278: */
1279: public void childrenReordered(NodeReorderEvent ev) {
1280: }
1281:
1282: /* Does nothing.
1283: * @param ev event describing the node
1284: */
1285: public final void nodeDestroyed(NodeEvent ev) {
1286: FilterNode fn = this .fn.get();
1287:
1288: if (fn == null) {
1289: return;
1290: }
1291:
1292: fn.originalDestroyed();
1293: }
1294: }
1295:
1296: /** Children for a filter node. Listens on changes in subnodes of
1297: * the original node and asks this filter node to creates representatives for
1298: * these subnodes.
1299: * <P>
1300: * This class is used as the default for subnodes of filter node, but
1301: * subclasses may modify it or provide a totally different implementation.
1302: * <p><code>FilterNode.Children</code> is not well suited to cases where you need to insert
1303: * additional nodes at the beginning or end of the list, or where you may need
1304: * to merge together multiple original children lists, or reorder them, etc.
1305: * That is because the keys are of type <code>Node</code>, one for each original
1306: * child, and the keys are reset during {@link #addNotify}, {@link #filterChildrenAdded},
1307: * {@link #filterChildrenRemoved}, and {@link #filterChildrenReordered}, so it is
1308: * not trivial to use different keys: you would need to override <code>addNotify</code>
1309: * (calling super first!) and the other three update methods. For such complex cases
1310: * you will do better by creating your own <code>Children.Keys</code> subclass, setting
1311: * keys that are useful to you, and keeping a <code>NodeListener</code> on the original
1312: * node to handle changes.
1313: */
1314: public static class Children extends
1315: org.openide.nodes.Children.Keys<Node> implements Cloneable {
1316: /** Original node. Should not be modified. */
1317: protected Node original;
1318:
1319: /** node listener on original */
1320: private ChildrenAdapter nodeL;
1321:
1322: /** Create children.
1323: * @param or original node to take children from */
1324: public Children(Node or) {
1325: original = or;
1326: }
1327:
1328: /** Sets the original children for this children.
1329: * Be aware that this method aquires
1330: * write lock on the nodes hierarchy ({@link Children#MUTEX}).
1331: * Take care not to call this method under read lock.
1332: * @param original The new original node.
1333: * @since 1.39
1334: */
1335: protected final void changeOriginal(Node original) {
1336: try {
1337: PR.enterWriteAccess();
1338:
1339: boolean wasAttached = nodeL != null;
1340:
1341: // uregister from the original node
1342: if (wasAttached) {
1343: this .original.removeNodeListener(nodeL);
1344: nodeL = null;
1345: }
1346:
1347: // reset the original node
1348: this .original = original;
1349:
1350: if (wasAttached) {
1351: addNotifyImpl();
1352: }
1353: } finally {
1354: PR.exitWriteAccess();
1355: }
1356: }
1357:
1358: /** Closes the listener, if any, on the original node.
1359: */
1360: @Override
1361: protected void finalize() {
1362: if (nodeL != null) {
1363: original.removeNodeListener(nodeL);
1364: }
1365:
1366: nodeL = null;
1367: }
1368:
1369: /* Clones the children object.
1370: */
1371: @Override
1372: public Object clone() {
1373: return new Children(original);
1374: }
1375:
1376: /** Initializes listening to changes in original node.
1377: */
1378: @Override
1379: protected void addNotify() {
1380: addNotifyImpl();
1381: }
1382:
1383: private void addNotifyImpl() {
1384: // add itself to reflect to changes children of original node
1385: nodeL = new ChildrenAdapter(this );
1386: original.addNodeListener(nodeL);
1387:
1388: updateKeys();
1389: }
1390:
1391: /** Clears current keys, because all mirrored nodes disappeared.
1392: */
1393: @Override
1394: protected void removeNotify() {
1395: setKeys(Collections.<Node> emptySet());
1396:
1397: if (nodeL != null) {
1398: original.removeNodeListener(nodeL);
1399: nodeL = null;
1400: }
1401: }
1402:
1403: /** Allows subclasses to override
1404: * creation of node representants for nodes in the mirrored children
1405: * list. The default implementation simply uses {@link Node#cloneNode}.
1406: * <p>Note that this method is only suitable for a 1-to-1 mirroring.
1407: *
1408: * @param node node to create copy of
1409: * @return copy of the original node
1410: */
1411: protected Node copyNode(Node node) {
1412: return node.cloneNode();
1413: }
1414:
1415: /* Implements find of child by finding the original child and then [PENDING]
1416: * @param name of node to find
1417: * @return the node or null
1418: */
1419: @Override
1420: public Node findChild(String name) {
1421: original.getChildren().findChild(name);
1422:
1423: return super .findChild(name);
1424: }
1425:
1426: /** Create nodes representing copies of the original node's children.
1427: * The default implementation returns exactly one representative for each original node,
1428: * as returned by {@link #copyNode}.
1429: * Subclasses may override this to avoid displaying a copy of an original child at all,
1430: * or even to display multiple nodes representing the original.
1431: * @param key the original child node
1432: * @return zero or more nodes representing the original child node
1433: */
1434: protected Node[] createNodes(Node key) {
1435: // is run under read access lock so nobody can change children
1436: return new Node[] { copyNode(key) };
1437: }
1438:
1439: /* Delegates to children of the original node.
1440: *
1441: * @param arr nodes to add
1442: * @return true/false
1443: */
1444: @Override
1445: @Deprecated
1446: public boolean add(Node[] arr) {
1447: return original.getChildren().add(arr);
1448: }
1449:
1450: /* Delegates to filter node.
1451: * @param arr nodes to remove
1452: * @return true/false
1453: */
1454: @Override
1455: @Deprecated
1456: public boolean remove(Node[] arr) {
1457: return original.getChildren().remove(arr);
1458: }
1459:
1460: /** Called when the filter node adds a new child.
1461: * The default implementation makes a corresponding change.
1462: * @param ev info about the change
1463: */
1464: protected void filterChildrenAdded(NodeMemberEvent ev) {
1465: updateKeys();
1466: }
1467:
1468: /** Called when the filter node removes a child.
1469: * The default implementation makes a corresponding change.
1470: * @param ev info about the change
1471: */
1472: protected void filterChildrenRemoved(NodeMemberEvent ev) {
1473: updateKeys();
1474: }
1475:
1476: /** Called when the filter node reorders its children.
1477: * The default implementation makes a corresponding change.
1478: * @param ev info about the change
1479: */
1480: protected void filterChildrenReordered(NodeReorderEvent ev) {
1481: updateKeys();
1482: }
1483:
1484: /** variable to notify that there is a cyclic update.
1485: * Used only in updateKeys method
1486: */
1487:
1488: // private transient boolean cyclic;
1489: /** Update keys from original nodes */
1490: private void updateKeys() {
1491: ChildrenAdapter runnable = nodeL;
1492:
1493: if (runnable != null) {
1494: runnable.run();
1495: }
1496: }
1497:
1498: /**
1499: * Implementation that ensures the original node is fully initialized
1500: * if optimal result is requested.
1501: *
1502: * @param optimalResult if <code>true</code>, the method will block
1503: * until the original node is fully initialized.
1504: * @since 3.9
1505: */
1506: @Override
1507: public Node[] getNodes(boolean optimalResult) {
1508: if (optimalResult) {
1509: setKeys(original.getChildren().getNodes(true));
1510: }
1511:
1512: return getNodes();
1513: }
1514: }
1515:
1516: /** Adapter that listens on changes in the original node and fires them
1517: * in this node.
1518: * Used as the default listener in {@link FilterNode.Children},
1519: * and is intended for refinement by its subclasses.
1520: */
1521: private static class ChildrenAdapter extends Object implements
1522: NodeListener, Runnable {
1523: /** children object to notify about addition of children.
1524: * Can be null. Set from Children's initNodes method.
1525: */
1526: private Reference<Children> children;
1527:
1528: /** Create a new adapter.
1529: * @param ch the children list
1530: */
1531: public ChildrenAdapter(Children ch) {
1532: this .children = new WeakReference<Children>(ch);
1533: }
1534:
1535: /** Called to update the content of children.
1536: */
1537: public void run() {
1538: Children ch = children.get();
1539:
1540: if (ch != null) {
1541: Node[] arr = ch.original.getChildren().getNodes();
1542: ch.setKeys(arr);
1543: }
1544: }
1545:
1546: /** Does nothing.
1547: * @param ev the event
1548: */
1549: public void propertyChange(PropertyChangeEvent ev) {
1550: }
1551:
1552: /* Informs that a set of new children has been added.
1553: * @param ev event describing the action
1554: */
1555: public void childrenAdded(NodeMemberEvent ev) {
1556: Children children = this .children.get();
1557:
1558: if (children == null) {
1559: return;
1560: }
1561:
1562: children.filterChildrenAdded(ev);
1563: }
1564:
1565: /* Informs that a set of children has been removed.
1566: * @param ev event describing the action
1567: */
1568: public void childrenRemoved(NodeMemberEvent ev) {
1569: Children children = this .children.get();
1570:
1571: if (children == null) {
1572: return;
1573: }
1574:
1575: children.filterChildrenRemoved(ev);
1576: }
1577:
1578: /* Informs that a set of children has been reordered.
1579: * @param ev event describing the action
1580: */
1581: public void childrenReordered(NodeReorderEvent ev) {
1582: Children children = this .children.get();
1583:
1584: if (children == null) {
1585: return;
1586: }
1587:
1588: children.filterChildrenReordered(ev);
1589: }
1590:
1591: /** Does nothing.
1592: * @param ev the event
1593: */
1594: public void nodeDestroyed(NodeEvent ev) {
1595: }
1596: }
1597:
1598: /** Filter node handle.
1599: */
1600: private static final class FilterHandle implements Node.Handle {
1601: static final long serialVersionUID = 7928908039428333839L;
1602: private Node.Handle original;
1603:
1604: public FilterHandle(Node.Handle original) {
1605: this .original = original;
1606: }
1607:
1608: public Node getNode() throws IOException {
1609: return new FilterNode(original.getNode());
1610: }
1611:
1612: @Override
1613: public String toString() {
1614: return "FilterHandle[" + original + "]"; // NOI18N
1615: }
1616: }
1617:
1618: /** Special ProxyLookup
1619: */
1620: private static final class FilterLookup extends Lookup {
1621: /** node we belong to */
1622: private FilterNode node;
1623:
1624: /** lookup we delegate too */
1625: private Lookup delegate;
1626:
1627: /** set of all results associated to this lookup */
1628: private Set<ProxyResult> results;
1629:
1630: FilterLookup() {
1631: }
1632:
1633: /** Registers own node.
1634: */
1635: public void ownNode(FilterNode n) {
1636: this .node = n;
1637: }
1638:
1639: /** A method that replaces instance of original node
1640: * with a new one
1641: */
1642: private <T> T replaceNodes(T orig, Class<T> clazz) {
1643: if (isNodeQuery(clazz) && (orig == node.getOriginal())
1644: && clazz.isInstance(node)) {
1645: return clazz.cast(node);
1646: } else {
1647: return orig;
1648: }
1649: }
1650:
1651: /** Changes the node we delegate to if necessary.
1652: * @param n the node to delegate to
1653: */
1654: public Lookup checkNode() {
1655: Lookup l = node.getOriginal().getLookup();
1656:
1657: if (delegate == l) {
1658: return l;
1659: }
1660:
1661: Iterator<ProxyResult> toCheck = null;
1662:
1663: synchronized (this ) {
1664: if (l != delegate) {
1665: this .delegate = l;
1666:
1667: if (results != null) {
1668: toCheck = new ArrayList<ProxyResult>(results)
1669: .iterator();
1670: }
1671: }
1672: }
1673:
1674: if (toCheck != null) {
1675: // update
1676:
1677: while (toCheck.hasNext()) {
1678: ProxyResult p = toCheck.next();
1679:
1680: if (p.updateLookup(l)) {
1681: p.resultChanged(null);
1682: }
1683: }
1684: }
1685:
1686: return delegate;
1687: }
1688:
1689: public <T> Result<T> lookup(Template<T> template) {
1690: ProxyResult<T> p = new ProxyResult<T>(template);
1691:
1692: synchronized (this ) {
1693: if (results == null) {
1694: results = new WeakSet<ProxyResult>();
1695: }
1696:
1697: results.add(p);
1698: }
1699:
1700: return p;
1701: }
1702:
1703: public <T> T lookup(Class<T> clazz) {
1704: T result = checkNode().lookup(clazz);
1705:
1706: if (result == null && clazz.isInstance(node)) {
1707: result = clazz.cast(node);
1708: }
1709:
1710: return replaceNodes(result, clazz);
1711: }
1712:
1713: /** Finds out whether a query for a class can be influenced
1714: * by a state of the "nodes" lookup and whether we should
1715: * initialize listening
1716: */
1717: private static boolean isNodeQuery(Class<?> c) {
1718: return Node.class.isAssignableFrom(c)
1719: || c.isAssignableFrom(Node.class);
1720: }
1721:
1722: @Override
1723: public <T> Item<T> lookupItem(Template<T> template) {
1724: boolean nodeQ = isNodeQuery(template.getType());
1725: Item<T> i = checkNode().lookupItem(template);
1726:
1727: if (nodeQ
1728: && i == null
1729: && template.getType().isInstance(node)
1730: && (template.getInstance() == null || template
1731: .getInstance() == node)) {
1732: i = checkNode().lookupItem(
1733: wackohacko(template.getId(), template
1734: .getInstance()));
1735: }
1736:
1737: return nodeQ && i != null ? new FilterItem<T>(i, template
1738: .getType()) : i;
1739: }
1740:
1741: @SuppressWarnings("unchecked")
1742: // cannot type-check this but ought to be safe
1743: private static <T> Lookup.Template<T> wackohacko(String id,
1744: T instance) {
1745: return new Lookup.Template(Node.class, id, instance);
1746: }
1747:
1748: /**
1749: * Result used in SimpleLookup. It holds a reference to the collection
1750: * passed in constructor. As the contents of this lookup result never
1751: * changes the addLookupListener and removeLookupListener are empty.
1752: */
1753: private final class ProxyResult<T> extends Result<T> implements
1754: LookupListener {
1755: /** Template used for this result. It is never null.*/
1756: private Template<T> template;
1757:
1758: /** result to delegate to */
1759: private Lookup.Result<T> delegate;
1760:
1761: /** listeners set */
1762: private javax.swing.event.EventListenerList listeners;
1763:
1764: /** Just remembers the supplied argument in variable template.*/
1765: ProxyResult(Template<T> template) {
1766: this .template = template;
1767: }
1768:
1769: /** Checks state of the result
1770: */
1771: private Result<T> checkResult() {
1772: updateLookup(checkNode());
1773:
1774: return this .delegate;
1775: }
1776:
1777: /** Updates the state of the lookup.
1778: * @return true if the lookup really changed
1779: */
1780: public boolean updateLookup(Lookup l) {
1781: Collection<? extends Item<T>> oldPairs = (delegate != null) ? delegate
1782: .allItems()
1783: : null;
1784:
1785: synchronized (this ) {
1786: if (delegate != null) {
1787: delegate.removeLookupListener(this );
1788: }
1789:
1790: delegate = l.lookup(template);
1791:
1792: if (template.getType().isAssignableFrom(
1793: node.getClass())
1794: && delegate.allItems().isEmpty()) {
1795: delegate = l.lookup(wackohacko(
1796: template.getId(), template
1797: .getInstance()));
1798: }
1799:
1800: delegate.addLookupListener(this );
1801: }
1802:
1803: if (oldPairs == null) {
1804: // nobody knows about a change
1805: return false;
1806: }
1807:
1808: Collection<? extends Item<T>> newPairs = delegate
1809: .allItems();
1810:
1811: return !oldPairs.equals(newPairs);
1812: }
1813:
1814: public synchronized void addLookupListener(LookupListener l) {
1815: if (listeners == null) {
1816: listeners = new javax.swing.event.EventListenerList();
1817: }
1818:
1819: listeners.add(LookupListener.class, l);
1820: }
1821:
1822: public synchronized void removeLookupListener(
1823: LookupListener l) {
1824: if (listeners != null) {
1825: listeners.remove(LookupListener.class, l);
1826: }
1827: }
1828:
1829: public Collection<? extends T> allInstances() {
1830: Collection<? extends T> c = checkResult()
1831: .allInstances();
1832:
1833: if (isNodeQuery(template.getType())) {
1834: List<T> ll = new ArrayList<T>(c.size());
1835: for (T o : c) {
1836: ll.add(replaceNodes(o, template.getType()));
1837: }
1838: if (ll.isEmpty()
1839: && template.getType().isInstance(node)) {
1840: if (template.getInstance() == null
1841: || template.getInstance() == node) {
1842: ll.add(template.getType().cast(node));
1843: }
1844: }
1845:
1846: return ll;
1847: } else {
1848: return c;
1849: }
1850: }
1851:
1852: @Override
1853: public Set<Class<? extends T>> allClasses() {
1854: return checkResult().allClasses();
1855: }
1856:
1857: @Override
1858: public Collection<? extends Item<T>> allItems() {
1859: return checkResult().allItems();
1860: }
1861:
1862: /** A change in lookup occured.
1863: * @param ev event describing the change
1864: *
1865: */
1866: public void resultChanged(LookupEvent anEvent) {
1867: javax.swing.event.EventListenerList l = this .listeners;
1868:
1869: if (l == null) {
1870: return;
1871: }
1872:
1873: Object[] listeners = l.getListenerList();
1874:
1875: if (listeners.length == 0) {
1876: return;
1877: }
1878:
1879: LookupEvent ev = new LookupEvent(this );
1880:
1881: for (int i = listeners.length - 1; i >= 0; i -= 2) {
1882: LookupListener ll = (LookupListener) listeners[i];
1883: ll.resultChanged(ev);
1884: }
1885: }
1886: }
1887:
1888: // end of ProxyResult
1889:
1890: /** Item that exchanges the original node for the FilterNode */
1891: private final class FilterItem<T> extends Lookup.Item<T> {
1892: private Item<T> delegate;
1893: private Class<T> clazz;
1894:
1895: FilterItem(Item<T> d, Class<T> clazz) {
1896: this .delegate = d;
1897: this .clazz = clazz;
1898: }
1899:
1900: public String getDisplayName() {
1901: return delegate.getDisplayName();
1902: }
1903:
1904: public String getId() {
1905: return delegate.getId();
1906: }
1907:
1908: public T getInstance() {
1909: return replaceNodes(delegate.getInstance(), clazz);
1910: }
1911:
1912: public Class<? extends T> getType() {
1913: return delegate.getType();
1914: }
1915: }
1916: }
1917: // end of FilterLookup
1918: }
|