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-2007 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.netbeans.modules.xml.schema.ui.nodes;
0043:
0044: import java.awt.Dialog;
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.beans.PropertyEditorSupport;
0051: import java.io.IOException;
0052: import java.lang.ref.SoftReference;
0053: import java.lang.reflect.InvocationTargetException;
0054: import java.util.ArrayList;
0055: import java.util.Collections;
0056: import java.util.LinkedList;
0057: import java.util.List;
0058: import java.util.Set;
0059: import javax.swing.Action;
0060: import org.netbeans.modules.refactoring.api.ui.RefactoringActionsFactory;
0061: import org.netbeans.modules.xml.refactoring.spi.SharedUtils;
0062:
0063: //import org.netbeans.modules.xml.refactoring.actions.RefactorAction;
0064: import org.netbeans.modules.xml.refactoring.ui.ReferenceableProvider;
0065: import org.netbeans.modules.xml.schema.model.Annotation;
0066: import org.netbeans.modules.xml.schema.model.Documentation;
0067: import org.netbeans.modules.xml.schema.model.ReferenceableSchemaComponent;
0068: import org.netbeans.modules.xml.schema.model.Schema;
0069: import org.netbeans.modules.xml.schema.model.SchemaComponent;
0070: import org.netbeans.modules.xml.schema.model.SchemaComponentReference;
0071: import org.netbeans.modules.xml.schema.model.SchemaModel;
0072: import org.netbeans.modules.xml.schema.ui.basic.DesignGotoType;
0073: import org.netbeans.modules.xml.schema.ui.basic.SchemaGotoType;
0074: import org.netbeans.modules.xml.schema.ui.basic.SchemaSettings;
0075: import org.netbeans.modules.xml.schema.ui.basic.UIUtilities;
0076: import org.netbeans.modules.xml.xam.dom.Utils;
0077: import org.netbeans.modules.xml.xam.ui.ComponentPasteType;
0078: import org.netbeans.modules.xml.xam.ui.actions.GotoType;
0079: import org.netbeans.modules.xml.xam.ui.actions.SourceGotoType;
0080: import org.netbeans.modules.xml.xam.ui.actions.SuperGotoType;
0081: import org.netbeans.modules.xml.xam.ui.cookies.GetComponentCookie;
0082: import org.netbeans.modules.xml.xam.ui.cookies.GetSuperCookie;
0083: import org.netbeans.modules.xml.xam.Component;
0084: import org.netbeans.modules.xml.xam.ComponentEvent;
0085: import org.netbeans.modules.xml.xam.ComponentListener;
0086: import org.netbeans.modules.xml.xam.Nameable;
0087: import org.netbeans.modules.xml.xam.Named;
0088: import org.netbeans.modules.xml.xam.NamedReferenceable;
0089: import org.netbeans.modules.xml.schema.ui.basic.ShowSchemaAction;
0090: import org.netbeans.modules.xml.xam.ui.cookies.GotoCookie;
0091: import org.netbeans.modules.xml.xam.ui.customizer.Customizer;
0092: import org.netbeans.modules.xml.xam.ui.customizer.CustomizerProvider;
0093: import org.netbeans.modules.xml.xam.ui.highlight.Highlight;
0094: import org.netbeans.modules.xml.xam.ui.highlight.HighlightManager;
0095: import org.netbeans.modules.xml.xam.ui.highlight.Highlighted;
0096: import org.netbeans.modules.xml.schema.ui.basic.editors.StringEditor;
0097: import org.netbeans.modules.xml.schema.ui.nodes.schema.properties.BaseSchemaProperty;
0098: import org.netbeans.modules.xml.xam.ui.XAMUtils;
0099: import org.netbeans.modules.xml.xam.ui.actions.GoToAction;
0100: import org.netbeans.modules.xml.xam.ui.cookies.CountChildrenCookie;
0101: import org.openide.DialogDescriptor;
0102: import org.openide.DialogDisplayer;
0103: import org.openide.ErrorManager;
0104: import org.openide.actions.CopyAction;
0105: import org.openide.actions.CutAction;
0106: import org.openide.actions.DeleteAction;
0107: import org.openide.actions.NewAction;
0108: import org.openide.actions.PasteAction;
0109: import org.openide.actions.PropertiesAction;
0110: import org.openide.actions.ReorderAction;
0111: import org.openide.explorer.propertysheet.ExPropertyEditor;
0112: import org.openide.explorer.propertysheet.PropertyEnv;
0113: import org.openide.filesystems.FileObject;
0114: import org.openide.loaders.DataObject;
0115: import org.openide.loaders.DataObjectNotFoundException;
0116: import org.openide.nodes.AbstractNode;
0117: import org.openide.nodes.Children;
0118: import org.openide.nodes.Index;
0119: import org.openide.nodes.Node;
0120: import org.openide.nodes.Node.Property;
0121: import org.openide.nodes.PropertySupport;
0122: import org.openide.nodes.Sheet;
0123: import org.openide.util.HelpCtx;
0124: import org.openide.util.Lookup;
0125: import org.openide.util.NbBundle;
0126: import org.openide.util.WeakListeners;
0127: import org.openide.util.actions.SystemAction;
0128: import org.openide.util.datatransfer.NewType;
0129: import org.openide.util.datatransfer.PasteType;
0130: import org.openide.util.lookup.AbstractLookup;
0131: import org.openide.util.lookup.InstanceContent;
0132: import org.openide.util.lookup.Lookups;
0133: import org.openide.util.lookup.ProxyLookup;
0134:
0135: /**
0136: *
0137: * @author Todd Fast, todd.fast@sun.com
0138: * @author Nathan Fiedler
0139: */
0140: public abstract class SchemaComponentNode<T extends SchemaComponent>
0141: extends AbstractNode implements Node.Cookie, ComponentListener,
0142: PropertyChangeListener, Highlighted, ReferenceableProvider,
0143: CountChildrenCookie, GetComponentCookie, GetSuperCookie,
0144: GotoCookie {
0145:
0146: /**
0147: *
0148: *
0149: */
0150: public SchemaComponentNode(SchemaUIContext context,
0151: SchemaComponentReference<T> reference, Children children) {
0152: this (context, reference, children, new InstanceContent());
0153: }
0154:
0155: /**
0156: * Constructor HACK to allow creating of our own lookup
0157: *
0158: */
0159: private SchemaComponentNode(SchemaUIContext context,
0160: SchemaComponentReference<T> reference, Children children,
0161: InstanceContent contents) {
0162: super (children, createLookup(context, contents));
0163:
0164: this .context = context;
0165: this .reference = reference;
0166: this .lookupContents = contents;
0167:
0168: // Add various objects to the lookup.
0169: contents.add(this );
0170: // Include the data object in order for the Navigator to
0171: // show the structure of the current document.
0172: DataObject dobj = getDataObject();
0173: if (dobj != null) {
0174: contents.add(dobj);
0175: }
0176: contents.add(context);
0177: contents.add(reference);
0178: contents.add(new DefaultExpandedCookie(false));
0179: // // add customizer provider if provided
0180: // CustomizerProvider provider = getCustomizerProvider();
0181: // if (provider != null) {
0182: // contents.add(provider);
0183: // }
0184:
0185: // reorder must be enabled only if its editable node
0186: if (children instanceof Index && isEditable() &&
0187: // dont show for schema bug 80138
0188: !(reference.get() instanceof Schema)) {
0189: contents.add(children);
0190: }
0191: T comp = reference.get();
0192: contents.add(comp);
0193:
0194: // Listen to changes in the model using a WeakListener. I hold onto
0195: // the WeakListener instance so I can explicitly remove it in the
0196: // destroy method.
0197: SchemaModel model = reference.get().getModel();
0198: if (model != null) {
0199: weakModelListener = WeakListeners.propertyChange(this ,
0200: model);
0201: model.addPropertyChangeListener(weakModelListener);
0202: weakComponentListener = (ComponentListener) WeakListeners
0203: .create(ComponentListener.class, this , model);
0204: model.addComponentListener(weakComponentListener);
0205: }
0206: // Determine default names for the node
0207: if (comp instanceof Named) {
0208: // Just set the name, and let the method call below handle
0209: // the display name
0210: _setName(((Named) comp).getName());
0211: } else {
0212: _setName(comp.getPeer().getLocalName());
0213: }
0214: // Need a model for the following to work properly.
0215: if (model != null) {
0216: // Let the node try to update its display name
0217: updateDisplayName();
0218: // Let the node try to update its short desc
0219: updateShortDescription();
0220: }
0221:
0222: setIconBaseWithExtension("org/netbeans/modules/xml/schema/ui/nodes/resources/"
0223: + "generic.png");
0224:
0225: referenceSet = Collections
0226: .singleton((Component) ((SchemaComponentReference) reference)
0227: .get());
0228: highlights = new LinkedList<Highlight>();
0229: HighlightManager.getDefault().addHighlighted(this );
0230: }
0231:
0232: /**
0233: * Create a lookup for this node, based on the given contents.
0234: *
0235: * @param context from which a Lookup is retrieved.
0236: * @param contents the basis of our new lookup.
0237: */
0238: private static Lookup createLookup(SchemaUIContext context,
0239: InstanceContent contents) {
0240: // We want our lookup to be based on the lookup from the context,
0241: // which provides a few necessary objects, such as a SaveCookie.
0242: // However, we do not want the Nodes or DataObjects, since we
0243: // provide our own.
0244: return new ProxyLookup(new Lookup[] {
0245: // Keep our lookup contents first, so that whatever we add to
0246: // the lookup is at the top of the lookup, such as this node,
0247: // which provides certain cookies, rather than that of the
0248: // currently selected node.
0249: new AbstractLookup(contents),
0250: Lookups.exclude(context.getLookup(), new Class[] {
0251: Node.class, DataObject.class, }), });
0252: }
0253:
0254: /**
0255: * Attempt to retrieve the DataObject associated with the model that
0256: * contains the component this node represents.
0257: *
0258: * @return schema data object, if available, or null if not.
0259: */
0260: private DataObject getDataObject() {
0261: try {
0262: // Include the data object in order for the Navigator to
0263: // show the structure of the current document.
0264: SchemaModel model = reference.get().getModel();
0265: if (model != null) {
0266: FileObject fobj = (FileObject) model.getModelSource()
0267: .getLookup().lookup(FileObject.class);
0268: if (fobj != null) {
0269: return DataObject.find(fobj);
0270: }
0271: }
0272: } catch (DataObjectNotFoundException donfe) {
0273: // fall through to return null
0274: }
0275: return null;
0276: }
0277:
0278: /**
0279: * This api returns the customizer provider.
0280: * Subclasses must override this api to return appropriate
0281: * customizer provider
0282: */
0283: protected CustomizerProvider getCustomizerProvider() {
0284: return null;
0285: }
0286:
0287: /**
0288: * Overriden to provide custom customizer.
0289: * Gets the customizer component from customizer provider
0290: * and displays it as modal dialog.
0291: * Subclasses who provide customizer should override
0292: * hasCustomizer and return true.
0293: */
0294: @Override
0295: public java.awt.Component getCustomizer() {
0296: if (!hasCustomizer() || !isEditable())
0297: return null;
0298: // get it from soft ref
0299: if (custRef == null || custRef.get() == null) {
0300: CustomizerProvider cp = getCustomizerProvider();
0301: if (cp == null)
0302: return null;
0303: Customizer cust = cp.getCustomizer();
0304: if (cust == null || cust.getComponent() == null)
0305: return null;
0306: custRef = new SoftReference<Customizer>(cust);
0307: } else {
0308: // might not be in sync so sync
0309: custRef.get().reset();
0310: }
0311: Customizer customizer = custRef.get();
0312: DialogDescriptor descriptor = UIUtilities.getCustomizerDialog(
0313: customizer, getTypeDisplayName(), isEditable());
0314: Dialog dlg = DialogDisplayer.getDefault().createDialog(
0315: descriptor);
0316: dlg.getAccessibleContext().setAccessibleDescription(
0317: dlg.getTitle());
0318: return dlg;
0319: }
0320:
0321: /**
0322: *
0323: *
0324: */
0325: public boolean equals(Object o) {
0326: // Without this, the tree view collapses when nodes are changed.
0327: if (o instanceof SchemaComponentNode) {
0328: SchemaComponentNode scn = (SchemaComponentNode) o;
0329: SchemaComponentReference scr = scn.getReference();
0330: return scr.equals(reference);
0331: }
0332: return false;
0333: }
0334:
0335: /**
0336: *
0337: *
0338: */
0339: public int hashCode() {
0340: // Without this, the tree view collapses when nodes are changed.
0341: return reference.hashCode();
0342: }
0343:
0344: /**
0345: *
0346: *
0347: */
0348: public SchemaUIContext getContext() {
0349: return context;
0350: }
0351:
0352: /**
0353: *
0354: *
0355: */
0356: public SchemaComponentReference<T> getReference() {
0357: return reference;
0358: }
0359:
0360: /**
0361: * Returns the contents of the lookup. All cookies and other objects that
0362: * should be findable via the lookup should be added to this.
0363: *
0364: */
0365: protected InstanceContent getLookupContents() {
0366: return lookupContents;
0367: }
0368:
0369: /**
0370: * Determines if this node represents a component that is contained
0371: * in a valid (non-null) model.
0372: *
0373: * @return true if model is valid, false otherwise.
0374: */
0375: protected boolean isValid() {
0376: return getReference().get().getModel() != null;
0377: }
0378:
0379: /**
0380: * Determines if this node represents a component that is contained
0381: * is editable
0382: *
0383: * @return true if component is editable, false otherwise.
0384: */
0385: protected boolean isEditable() {
0386: SchemaModel model = getReference().get().getModel();
0387: return model != null && model == getContext().getModel()
0388: && XAMUtils.isWritable(model);
0389: }
0390:
0391: /**
0392: * Used by subclasses to update the display name as needed. The default
0393: * implementation updates the display name for named schema components.
0394: * Note, this method may be called from the constructor, so be sure to
0395: * avoid using member variables!
0396: *
0397: */
0398: protected void updateDisplayName() {
0399: if (!isValid()) {
0400: // If there is no model, exceptions will occur.
0401: return;
0402: }
0403: T component = getReference().get();
0404: if (component instanceof Named) {
0405: String name = ((Named) component).getName();
0406: // Automatically keep the name in sync for named schema components.
0407: _setName(name);
0408: if (name == null || name.equals(""))
0409: name = component.getPeer().getLocalName();
0410: setDisplayName(name);
0411: }
0412: }
0413:
0414: /**
0415: * updates the short descrption associated with node.
0416: * checks for if there is any annotation with documentation element.
0417: */
0418: private void updateShortDescription() {
0419: if (!isValid()) {
0420: // If there is no model, exceptions will occur.
0421: return;
0422: }
0423: T component = getReference().get();
0424: Documentation d = null;
0425: Annotation a = null;
0426: String language = SchemaSettings.getDefault().getLanguage();
0427: if (component instanceof Documentation) {
0428: d = (Documentation) component;
0429: } else if (component instanceof Annotation) {
0430: a = (Annotation) component;
0431: } else {
0432: a = component.getAnnotation();
0433: }
0434: if (a != null && !a.getDocumentationElements().isEmpty()) {
0435: if (language == null) {
0436: d = a.getDocumentationElements().iterator().next();
0437: } else {
0438: for (Documentation doc : a.getDocumentationElements()) {
0439: if (language.equals(doc.getLanguage())) {
0440: d = doc;
0441: break;
0442: }
0443: }
0444: if (d == null) {
0445: d = a.getDocumentationElements().iterator().next();
0446: }
0447: }
0448: }
0449: if (d != null) {
0450: setShortDescription(d.getContentFragment());
0451: } else {
0452: setShortDescription(null);
0453: }
0454: }
0455:
0456: /**
0457: *
0458: *
0459: */
0460: public abstract String getTypeDisplayName();
0461:
0462: /**
0463: *
0464: *
0465: */
0466: protected String getHtmlTypeDisplayName() {
0467: return "<font color='#aaaaaa'>(" + getTypeDisplayName()
0468: + ")</font>";
0469: }
0470:
0471: /**
0472: *
0473: *
0474: */
0475: public boolean isDefaultExpanded() {
0476: DefaultExpandedCookie cookie = (DefaultExpandedCookie) getCookie(DefaultExpandedCookie.class);
0477: if (cookie != null)
0478: return cookie.isDefaultExpanded();
0479: else
0480: return false;
0481: }
0482:
0483: /**
0484: *
0485: *
0486: */
0487: public void setDefaultExpanded(boolean value) {
0488: DefaultExpandedCookie cookie = (DefaultExpandedCookie) getCookie(DefaultExpandedCookie.class);
0489: if (cookie != null)
0490: cookie.setDefaultExpanded(value);
0491: }
0492:
0493: /**
0494: * Finds the super definition of the schema component.
0495: * Returns null as default implementation
0496: * Subclasses which fave global type or reference definitions,
0497: * must override to return the global reference.
0498: */
0499: protected ReferenceableSchemaComponent getSuperDefinition() {
0500: return null;
0501: }
0502:
0503: public int getChildCount() {
0504: return getReference().get().getChildren().size();
0505: }
0506:
0507: public Component getComponent() {
0508: return getReference().get();
0509: }
0510:
0511: public Class<? extends Component> getComponentType() {
0512: return getReference().get().getComponentType();
0513: }
0514:
0515: // implementation of get super cookie
0516: public SchemaComponent getSuper() {
0517: return getSuperDefinition();
0518: }
0519:
0520: ////////////////////////////////////////////////////////////////////////////
0521: // Node methods
0522: ////////////////////////////////////////////////////////////////////////////
0523:
0524: /**
0525: *
0526: *
0527: */
0528: @Override
0529: public HelpCtx getHelpCtx() {
0530: if (this instanceof SchemaComponentNode)
0531: return new HelpCtx(SchemaComponentNode.class);
0532: return new HelpCtx(getClass());
0533: }
0534:
0535: /**
0536: *
0537: *
0538: */
0539: @Override
0540: public boolean canCut() {
0541: return isEditable();
0542: }
0543:
0544: /**
0545: *
0546: *
0547: */
0548: @Override
0549: public boolean canCopy() {
0550: return true;
0551: }
0552:
0553: @Override
0554: @SuppressWarnings("unchecked")
0555: protected void createPasteTypes(Transferable transferable, List list) {
0556: if (isValid() && isEditable()) {
0557: PasteType type = ComponentPasteType.getPasteType(reference
0558: .get(), transferable, null);
0559: if (type != null) {
0560: list.add(type);
0561: }
0562: }
0563: }
0564:
0565: @Override
0566: public PasteType getDropType(Transferable transferable, int action,
0567: int index) {
0568: if (isValid() && isEditable()) {
0569: PasteType type = ComponentPasteType.getDropType(reference
0570: .get(), transferable, null, action, index);
0571: if (type != null) {
0572: return type;
0573: }
0574: }
0575: return null;
0576: }
0577:
0578: @Override
0579: public boolean canDestroy() {
0580: SchemaComponent component = getReference().get();
0581: if (component instanceof Schema || !isEditable()) {
0582: return false;
0583: }
0584: return true;
0585: }
0586:
0587: /**
0588: *
0589: *
0590: */
0591: @Override
0592: public boolean canRename() {
0593: return supportsRename();
0594: }
0595:
0596: /**
0597: * Indicates if the component is nameable.
0598: *
0599: * @return true if nameable, false otherwise.
0600: */
0601: private boolean isNameable() {
0602: // Need to check the component type instead of the component, to
0603: // avoid allowing rename of an element reference, in which the
0604: // implementation extends Nameable.
0605: return Nameable.class.isAssignableFrom(getReference().get()
0606: .getComponentType());
0607: }
0608:
0609: /**
0610: * Indicates if the component can be renamed.
0611: *
0612: * @return true if nameable, false otherwise.
0613: */
0614: private boolean supportsRename() {
0615: // check if its nameable and editable
0616: return isNameable() && isEditable();
0617: }
0618:
0619: /**
0620: * Set the name property directly without adjusting the associated model
0621: *
0622: */
0623: private void _setName(String value) {
0624: // prevent NPE from explorermanager
0625: if (value == null)
0626: value = "";
0627: super .setName(value);
0628: }
0629:
0630: /**
0631: *
0632: *
0633: */
0634: @Override
0635: public void setName(String value) {
0636: NamedReferenceable ref = getReferenceable();
0637: if (ref == null) {
0638: _setName(value);
0639: if (supportsRename()) {
0640: try {
0641: getReference().get().getModel().startTransaction();
0642: Nameable n = (Nameable) getReference().get();
0643: n.setName(value);
0644: } finally {
0645: getReference().get().getModel().endTransaction();
0646: }
0647: }
0648: } else {
0649: SharedUtils.locallyRenameRefactor((Nameable) ref, value);
0650: }
0651: }
0652:
0653: /**
0654: * Checks for references to this component, and if none are found,
0655: * remove it from the model.
0656: */
0657: public void destroy() throws IOException {
0658: SchemaModel model = getReference().get().getModel();
0659: if (model == null) {
0660: // fix bug 6421899
0661: // this node might have been deleted from model as a result of
0662: // deletion of its parent. get model from context and remove
0663: // listeners. no need to remove it again from model.
0664: model = getContext().getModel();
0665: model.removeComponentListener(weakComponentListener);
0666: model.removePropertyChangeListener(weakModelListener);
0667: } else {
0668: model.removeComponentListener(weakComponentListener);
0669: model.removePropertyChangeListener(weakModelListener);
0670:
0671: // Remove the component from the model.
0672: SchemaComponent component = getReference().get();
0673: try {
0674: model.startTransaction();
0675: model.removeChildComponent(component);
0676: //need to provide a hook
0677: cleanup();
0678: } finally {
0679: model.endTransaction();
0680: }
0681: }
0682: super .destroy();
0683: }
0684:
0685: /**
0686: * This is a hook for the subclasses if they want to do something special in the same transaction.
0687: * For example, when an import gets deleted, we should also remove the namespace declaration.
0688: */
0689: protected void cleanup() {
0690: //default implementation needs to be empty
0691: //subclasses should override, if they want do extra stuff inside the same transaction.
0692: //See AdvancedImportNode for details.
0693: }
0694:
0695: /**
0696: *
0697: *
0698: */
0699: @Override
0700: protected Sheet createSheet() {
0701: super .createSheet();
0702: Sheet sheet = Sheet.createDefault();
0703: Sheet.Set set = sheet.get(Sheet.PROPERTIES);
0704: set.put(new PropertySupport("kind", String.class, NbBundle
0705: .getMessage(SchemaComponentNode.class,
0706: "PROP_SchemaComponentNode_Kind"), "", true,
0707: false) {
0708: public Object getValue() {
0709: return getTypeDisplayName();
0710: }
0711:
0712: public void setValue(Object value) {
0713: // Not modifiable
0714: }
0715: });
0716:
0717: try {
0718: // id property
0719: Property idProperty = new BaseSchemaProperty(
0720: (SchemaComponent) getReference().get(),
0721: String.class, SchemaComponent.ID_PROPERTY, NbBundle
0722: .getMessage(SchemaComponentNode.class,
0723: "PROP_SchemaComponentNode_ID"),
0724: NbBundle.getMessage(SchemaComponentNode.class,
0725: "PROP_SchemaComponentNode_IDDesc"),
0726: StringEditor.class) {
0727: public void setValue(Object o)
0728: throws IllegalAccessException,
0729: InvocationTargetException {
0730: if (o instanceof String) {
0731: if ("".equals(o)) {
0732: super .setValue(null);
0733: } else if (Utils.isValidNCName(o.toString())) {
0734: super .setValue(o);
0735: } else {
0736: String msg = NbBundle.getMessage(
0737: BaseSchemaProperty.class,
0738: "MSG_Neg_Int_Value", o); //NOI18N
0739: IllegalArgumentException iae = new IllegalArgumentException(
0740: msg);
0741: ErrorManager.getDefault().annotate(iae,
0742: ErrorManager.USER, msg, msg, null,
0743: new java.util.Date());
0744: throw iae;
0745: }
0746: } else {
0747: super .setValue(o);
0748: }
0749: }
0750: };
0751: set.put(new SchemaModelFlushWrapper(getReference().get(),
0752: idProperty));
0753: } catch (NoSuchMethodException nsme) {
0754: assert false : "properties must be defined";
0755: }
0756:
0757: // If we are a named node, display that in the property sheet
0758: if (isNameable())
0759: set.put(new PropertySupport.Name(this ));
0760:
0761: if (hasCustomizer() && isEditable()) {
0762: Property structureProp = new PropertySupport.ReadWrite(
0763: "structure", //NOI18N
0764: String.class,
0765: NbBundle.getMessage(SchemaComponentNode.class,
0766: "PROP_SchemaComponentNode_Customize"),
0767: NbBundle
0768: .getMessage(SchemaComponentNode.class,
0769: "PROP_SchemaComponentNode_Customize_ShortDesc")) {
0770: public Object getValue() throws IllegalAccessException,
0771: InvocationTargetException {
0772: return NbBundle.getMessage(
0773: SchemaComponentNode.class,
0774: "PROP_SchemaComponentNode_Customize_Label");
0775: }
0776:
0777: public void setValue(Object val)
0778: throws IllegalAccessException,
0779: IllegalArgumentException,
0780: InvocationTargetException {
0781: }
0782:
0783: public PropertyEditor getPropertyEditor() {
0784: return new StructurePropertyEditor();
0785: }
0786: };
0787: set.put(structureProp);
0788: }
0789:
0790: return sheet;
0791: }
0792:
0793: /**
0794: * Indicates if this node should allow reordering of its children.
0795: * The default implementation allows reordering only if there is
0796: * more than one child component.
0797: *
0798: * @return true if reordering of this node's children is permitted.
0799: */
0800: protected boolean allowReordering() {
0801: // Check if we have more than one physical child in the model.
0802: // Using the node children count results in too many index out
0803: // of bounds exceptions.
0804: return getReference().get().getChildren().size() > 1;
0805: }
0806:
0807: @Override
0808: public Action[] getActions(boolean context) {
0809: ReadOnlyCookie roc = (ReadOnlyCookie) getContext().getLookup()
0810: .lookup(ReadOnlyCookie.class);
0811: List<Action> actions = new ArrayList<Action>();
0812: if (roc != null && roc.isReadOnly()) {
0813: // Set of actions for read-only components.
0814: actions.add(SystemAction.get(GoToAction.class));
0815: } else {
0816: // Set of actions for modifiable components.
0817: actions.add(SystemAction.get(CutAction.class));
0818: actions.add(SystemAction.get(CopyAction.class));
0819: actions.add(SystemAction.get(PasteAction.class));
0820: actions.add(null);
0821: actions.add(SystemAction.get(NewAction.class));
0822: actions.add(SystemAction.get(DeleteAction.class));
0823: if (allowReordering()) {
0824: actions.add(SystemAction.get(ReorderAction.class));
0825: }
0826: actions.add(null);
0827: actions.add(SystemAction.get(GoToAction.class));
0828: //actions.add(SystemAction.get(FindUsagesAction.class));
0829: //new action based on new refactoring API
0830: actions.add(RefactoringActionsFactory.whereUsedAction());
0831: actions.add(null);
0832: //actions.add(SystemAction.get(RefactorAction.class));
0833: //new action based on new refactoring API
0834: actions
0835: .add(RefactoringActionsFactory
0836: .editorSubmenuAction());
0837: actions.add(null);
0838: actions.add(SystemAction.get(PropertiesAction.class));
0839: }
0840: return actions.toArray(new Action[actions.size()]);
0841: }
0842:
0843: @Override
0844: public Action getPreferredAction() {
0845: // This exists for use in the Navigator.
0846: ReadOnlyCookie roc = (ReadOnlyCookie) getCookie(ReadOnlyCookie.class);
0847: if (roc != null && roc.isReadOnly()) {
0848: return SystemAction.get(ShowSchemaAction.class);
0849: }
0850: return super .getPreferredAction();
0851: }
0852:
0853: /**
0854: * This api returns the factory which gives back new types for this node.
0855: * Default FactoryImpl provides addition of annotation.
0856: * Subclasses can override this api to allow addition of their allowed
0857: * child types.
0858: */
0859: protected NewTypesFactory getNewTypesFactory() {
0860: return new NewTypesFactory();
0861: }
0862:
0863: public final NewType[] getNewTypes() {
0864: if (isEditable()) {
0865: return getNewTypesFactory().getNewTypes(getReference(),
0866: null);
0867: }
0868: return new NewType[] {};
0869: }
0870:
0871: public GotoType[] getGotoTypes() {
0872: return GOTO_TYPES;
0873: }
0874:
0875: ////////////////////////////////////////////////////////////////////////////
0876: // Listener methods
0877: ////////////////////////////////////////////////////////////////////////////
0878:
0879: public void childrenAdded(ComponentEvent evt) {
0880: if (isValid()) {
0881: if (evt.getSource() == getReference().get()) {
0882: ((RefreshableChildren) getChildren()).refreshChildren();
0883: }
0884: if (evt.getSource() == getReference().get()
0885: || evt.getSource() == getReference().get()
0886: .getAnnotation()) {
0887: updateShortDescription();
0888: }
0889: }
0890: }
0891:
0892: public void childrenDeleted(ComponentEvent evt) {
0893: if (isValid()) {
0894: if (evt.getSource() == getReference().get()) {
0895: ((RefreshableChildren) getChildren()).refreshChildren();
0896: }
0897: if (evt.getSource() == getReference().get()
0898: || evt.getSource() == getReference().get()
0899: .getAnnotation()) {
0900: updateShortDescription();
0901: }
0902: }
0903: }
0904:
0905: public void valueChanged(ComponentEvent evt) {
0906: if (isValid()) {
0907: T component = getReference().get();
0908: if (evt.getSource() == component) {
0909: updateDisplayName();
0910: }
0911: Documentation d = null;
0912: if (component instanceof Documentation)
0913: d = (Documentation) component;
0914: else if (component instanceof Annotation) {
0915: Annotation a = (Annotation) component;
0916: if (!a.getDocumentationElements().isEmpty())
0917: d = a.getDocumentationElements().iterator().next();
0918: } else {
0919: Annotation a = component.getAnnotation();
0920: if (a != null
0921: && !a.getDocumentationElements().isEmpty())
0922: d = a.getDocumentationElements().iterator().next();
0923: }
0924: if (evt.getSource() == d) {
0925: updateShortDescription();
0926: }
0927: }
0928: }
0929:
0930: /**
0931: * Reacts to granular property change events from model.
0932: * Updates displayname if needed.
0933: * Fires properties changed events if needed.
0934: * Subclasses override if needed.
0935: */
0936: public void propertyChange(PropertyChangeEvent event) {
0937: if (isValid() && event.getSource() == getReference().get()) {
0938: try {
0939: updateDisplayName();
0940: String propName = event.getPropertyName();
0941: Sheet.Set propertySet = getSheet()
0942: .get(Sheet.PROPERTIES);
0943: if (propertySet != null) {
0944: if (propertySet.get(propName) != null) {
0945: firePropertyChange(propName, event
0946: .getOldValue(), event.getNewValue());
0947: } else {
0948: ErrorManager
0949: .getDefault()
0950: .log(
0951: ErrorManager.INFORMATIONAL,
0952: propName
0953: + " property is not defined in "
0954: + getTypeDisplayName());
0955: }
0956: }
0957: } catch (IllegalStateException ise) {
0958: // Component is not in the model.
0959: ErrorManager.getDefault().notify(
0960: ErrorManager.INFORMATIONAL, ise);
0961: } catch (NullPointerException npe) {
0962: // Does not reproduce reliably, but catch and log regardless.
0963: ErrorManager.getDefault().notify(
0964: ErrorManager.INFORMATIONAL, npe);
0965: }
0966: }
0967: }
0968:
0969: public Set<Component> getComponents() {
0970: return referenceSet;
0971: }
0972:
0973: public void highlightAdded(Highlight hl) {
0974: highlights.add(hl);
0975: fireDisplayNameChange("TempName", getDisplayName());
0976: }
0977:
0978: public void highlightRemoved(Highlight hl) {
0979: highlights.remove(hl);
0980: fireDisplayNameChange("TempName", getDisplayName());
0981: }
0982:
0983: /**
0984: * Given a display name, add the appropriate HTML tags to highlight
0985: * the display name as dictated by any Highlights associated with
0986: * this node.
0987: *
0988: * @param name current display name.
0989: * @return marked up display name.
0990: */
0991: protected String applyHighlights(String name) {
0992: int count = highlights.size();
0993: if (count > 0) {
0994: // Apply the last highlight that was added to our list.
0995: Highlight hl = highlights.get(count - 1);
0996: String type = hl.getType();
0997: String code = null;
0998:
0999: if (type.equals(Highlight.SEARCH_RESULT)) {
1000: code = "e68b2c";
1001: } else if (type.equals(Highlight.SEARCH_RESULT_PARENT)) {
1002: code = "ffc73c";
1003: } else if (type.equals(Highlight.FIND_USAGES_RESULT_PARENT)) {
1004: code = "B5E682"; // was c7ff3c chartreuse
1005: } else if (type.equals(Highlight.FIND_USAGES_RESULT)) {
1006: code = "8be62c"; // darker green
1007: }
1008: name = "<strong><font color=\"#" + code + "\">" + name
1009: + "</font></strong>";
1010: }
1011: return name;
1012: }
1013:
1014: public String getDisplayName() {
1015: String instanceName = getDefaultDisplayName();
1016: return instanceName.length() == 0 ? instanceName : instanceName
1017: + " " + "[" + getTypeDisplayName() + "]"; // NOI18N
1018: }
1019:
1020: public String getDefaultDisplayName() {
1021: String instanceName = super .getDisplayName();
1022: return instanceName == null || instanceName.length() == 0 ? ""
1023: : instanceName;
1024: }
1025:
1026: public String getHtmlDisplayName() {
1027: String name = getDefaultDisplayName();
1028: // Need to escape any HTML meta-characters in the name.
1029: if (name != null)
1030: name = name.replace("<", "<").replace(">", ">");
1031: return applyHighlights(name);
1032: }
1033:
1034: ////////////////////////////////////////////////////////////////////////////
1035: // Inner class
1036: ////////////////////////////////////////////////////////////////////////////
1037:
1038: private class StructurePropertyEditor extends PropertyEditorSupport
1039: implements ExPropertyEditor {
1040: public boolean supportsCustomEditor() {
1041: return true;
1042: }
1043:
1044: public java.awt.Component getCustomEditor() {
1045: return getCustomizer();
1046: }
1047:
1048: public void attachEnv(PropertyEnv env) {
1049: FeatureDescriptor desc = env.getFeatureDescriptor();
1050: desc.setValue("canEditAsText", Boolean.FALSE); // NOI18N
1051: }
1052: }
1053:
1054: private SchemaUIContext context;
1055: private SchemaComponentReference<T> reference;
1056: private Set<Component> referenceSet;
1057: /** Ordered list of highlights applied to this node. */
1058: private List<Highlight> highlights;
1059: private InstanceContent lookupContents;
1060: private PropertyChangeListener modelListener;
1061: private PropertyChangeListener weakModelListener;
1062: private ComponentListener weakComponentListener;
1063: private SoftReference<Customizer> custRef;
1064: private static final GotoType[] GOTO_TYPES = new GotoType[] {
1065: new SourceGotoType(), new SchemaGotoType(),
1066: new DesignGotoType(), new SuperGotoType(), };
1067:
1068: /**
1069: * Implement ReferenceableProvider
1070: *
1071: *
1072: * @returns NamedReferenceable used by Refactoring Find Usage, Safe Delete,
1073: * and Rename
1074: */
1075: public NamedReferenceable getReferenceable() {
1076: SchemaComponent comp = reference.get();
1077: if (comp instanceof NamedReferenceable
1078: && isValid()
1079: && comp.getModel().getModelSource().getLookup().lookup(
1080: FileObject.class) != null) {
1081: return NamedReferenceable.class.cast(comp);
1082: }
1083: return null;
1084: }
1085:
1086: /**
1087: * This api is used to set the back pointer to the ReadOnlySchemaComponentNode,
1088: * which represents this node on UI in case of refrenced components.
1089: */
1090: public void setReferencingNode(final Node referencingNode) {
1091: getLookupContents().add(new ReferencingNodeProvider() {
1092: public Node getNode() {
1093: return referencingNode;
1094: }
1095: });
1096: }
1097: }
|