001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.dbschema.nodes;
043:
044: import java.awt.datatransfer.Transferable;
045: import java.awt.datatransfer.DataFlavor;
046: import java.beans.*;
047: import java.io.IOException;
048: import java.lang.reflect.InvocationTargetException;
049:
050: import org.openide.nodes.*;
051: import org.openide.util.HelpCtx;
052: import org.openide.util.NbBundle;
053: import org.openide.util.WeakListeners;
054: import org.openide.util.actions.SystemAction;
055: import org.openide.util.datatransfer.ExTransferable;
056:
057: import org.netbeans.modules.dbschema.*;
058:
059: /** Superclass of nodes representing elements in the database metadata
060: * hierarchy.
061: * <p>Element nodes generally:
062: * <ul>
063: * <li>Have an associated icon, according to {@link #resolveIconBase}.
064: * <li>Have a display name based on the element's properties.
065: * <li>Have some node properties (displayable on the property sheet), according
066: * to the element's properties, and with suitable editors.
067: * <li>Permit renames and deletes, if a member element and writeable.
068: * <li>As permitted by the element, and a writable flag in the node,
069: * permit cut/copy/paste operations, as well as creation of new members.
070: * </ul>
071: */
072: public abstract class DBElementNode extends AbstractNode implements
073: IconStrings, DBElementProperties, Node.Cookie {
074:
075: /** Default return value of getIconAffectingProperties method. */
076: private static final String[] ICON_AFFECTING_PROPERTIES = new String[] {};
077:
078: /** Associated element. */
079: protected DBElement element;
080:
081: /** Is this node read-only or are modifications permitted? */
082: protected boolean writeable;
083:
084: /** Listener to forbid its garbage collection */
085: private transient PropertyChangeListener listener;
086:
087: /** Create a new element node.
088: *
089: * @param element element to represent
090: * @param children child nodes
091: * @param writeable <code>true</code> if this node should allow
092: * modifications. These include writable properties, clipboard operations,
093: * deletions, etc.
094: */
095: public DBElementNode(DBElement element, Children children,
096: boolean writeable) {
097: super (children);
098: this .element = element;
099: this .writeable = writeable;
100: setIconBase(resolveIconBase());
101: setName(element.getName().getName());
102: setDisplayName(element.getName().getName());
103: listener = new ElementListener();
104: element.addPropertyChangeListener(WeakListeners.propertyChange(
105: listener, element));
106: }
107:
108: /** Get the currently appropriate icon base.
109: * Subclasses should make this sensitive to the state of the element--for
110: * example, a pk column may have a different icon than a regular one.
111: * The icon will be automatically changed whenever a {@link
112: * #getIconAffectingProperties relevant} change is made to the element.
113: * @return icon base
114: * @see AbstractNode#setIconBase
115: */
116: abstract protected String resolveIconBase();
117:
118: /** Get the names of all element properties which might affect the choice of
119: * icon. The default implementation returns an empty array.
120: * @return the property names, from {@link DBElementProperties}
121: */
122: protected String[] getIconAffectingProperties() {
123: return ICON_AFFECTING_PROPERTIES;
124: }
125:
126: public HelpCtx getHelpCtx() {
127: return new HelpCtx("dbschema_ctxhelp_wizard"); //NOI18N
128: }
129:
130: /** Test whether this node can be renamed.
131: * The default implementation assumes it can if this node is {@link
132: * #writeable}.
133: * @return <code>true</code> if this node can be renamed
134: */
135: public boolean canRename() {
136: return writeable;
137: }
138:
139: /** Test whether this node can be deleted.
140: * The default implementation assumes it can if this node is {@link
141: * #writeable}.
142: * @return <code>true</code> if this node can be renamed
143: */
144: public boolean canDestroy() {
145: return writeable;
146: }
147:
148: /* Copy this node to the clipboard.
149: *
150: * @return {@link ExTransferable.Single} with one flavor, {@link
151: * NodeTransfer#nodeCopyFlavor}
152: * @throws IOException if it could not copy
153: */
154: public Transferable clipboardCopy() throws IOException {
155: ExTransferable ex = ExTransferable
156: .create(super .clipboardCopy());
157:
158: ex.put(new ElementStringTransferable());
159:
160: return ex;
161: }
162:
163: /* Cut this node to the clipboard.
164: *
165: * @return {@link ExTransferable.Single} with one flavor, {@link
166: * NodeTransfer#nodeCopyFlavor}
167: * @throws IOException if it could not cut
168: */
169: public Transferable clipboardCut() throws IOException {
170: if (!writeable)
171: throw new IOException();
172:
173: ExTransferable ex = ExTransferable.create(super .clipboardCut());
174: ex.put(new ElementStringTransferable());
175:
176: return ex;
177: }
178:
179: /** Transferable for elements as String. */
180: class ElementStringTransferable extends ExTransferable.Single {
181: /** Construct new Transferable for this node. */
182: ElementStringTransferable() {
183: super (DataFlavor.stringFlavor);
184: }
185:
186: /** @return the data as String */
187: protected Object getData() {
188: return element.toString();
189: }
190: }
191:
192: /** Test whether this node can be copied.
193: * The default implementation returns <code>true</code>.
194: * @return <code>true</code> if it can
195: */
196: public boolean canCopy() {
197: return true;
198: }
199:
200: /** Test whether this node can be cut.
201: * The default implementation assumes it can if this node is {@link
202: * #writeable}.
203: * @return <code>true</code> if it can
204: */
205: public boolean canCut() {
206: return writeable;
207: }
208:
209: /** Calls super.fireCookieChange. The reason why is redefined
210: * is only to allow the access from this package.
211: */
212: void super FireCookieChange() {
213: fireCookieChange();
214: }
215:
216: /** Get a cookie from this node.
217: * First tries the node itself, then {@link DBElement#getCookie}.
218: * Since {@link DBElement} implements <code>Node.Cookie</code>, it is
219: * possible to find the element from a node using code such as:
220: * <p><code><pre>
221: * Node someNode = ...;
222: * ColumnElement element =
223: * (ColumnElement)someNode.getCookie(ColumnElement.class);
224: * if (element != null) { ... }
225: * </pre></code>
226: * @param type the cookie class
227: * @return the cookie or <code>null</code>
228: */
229: public Node.Cookie getCookie(Class type) {
230: Node.Cookie c = super .getCookie(type);
231:
232: if (c == null
233: && (type.isAssignableFrom(DBElementProvider.class) || type
234: .isAssignableFrom(DBElement.class))) {
235: c = new DBElementProvider(element);
236: }
237:
238: return c;
239: }
240:
241: /** Test for equality.
242: * @return <code>true</code> if the represented {@link DBElement}s are equal
243: */
244: public boolean equals(Object o) {
245: return ((o instanceof DBElementNode) && (element
246: .equals(((DBElementNode) o).element)));
247: }
248:
249: /** Get a hash code.
250: * @return the hash code from the represented {@link DBElement}
251: */
252: public int hashCode() {
253: return element.hashCode();
254: }
255:
256: /** Create a node property representing the element's name.
257: * @param canW if <code>false</code>, property will be read-only
258: * @return the property.
259: */
260: protected Node.Property createNameProperty(boolean canW) {
261: return new ElementProp(Node.PROP_NAME, String.class, canW) {
262: /** Gets the value */
263: public Object getValue() {
264: return ((DBElement) element).getName().getFullName();
265: }
266: };
267: }
268:
269: void super SetName(String name) {
270: super .setName(name);
271: }
272:
273: // ================== Element listener =================================
274:
275: /** Listener for changes of the element's property changes.
276: * It listens and changes updates the iconBase and displayName
277: * if the changed property could affect them.
278: */
279: private class ElementListener implements PropertyChangeListener {
280: public ElementListener() {
281: }
282:
283: /** Called when any element's property changed.
284: */
285: public void propertyChange(PropertyChangeEvent evt) {
286: String propName = evt.getPropertyName();
287:
288: if (propName == null) {
289: setIconBase(resolveIconBase());
290: } else {
291: // icon
292: String[] iconProps = getIconAffectingProperties();
293: for (int i = 0; i < iconProps.length; i++)
294: if (iconProps[i].equals(propName)) {
295: setIconBase(resolveIconBase());
296: break;
297: }
298:
299: if (propName.equals(Node.PROP_NAME)) {
300: // set inherited name - this code should rather in
301: // DBMemberElementNode,
302: // but we safe one instance of listener for each node
303: // if it will be here. [Petr]
304: try {
305: super SetName(((DBMemberElement) DBElementNode.this .element)
306: .getName().toString());
307: } catch (ClassCastException e) {
308: // it is strange - PROP_NAME has only member element.
309: }
310: } else {
311: if (propName.equals(Node.PROP_COOKIE)) {
312: // Fires the changes of the cookies of the element.
313: super FireCookieChange();
314: return;
315: }
316: }
317: }
318: DBElementNode.this .firePropertyChange(
319: evt.getPropertyName(), evt.getOldValue(), evt
320: .getNewValue());
321: }
322: }
323:
324: // ================== Property support for element nodes =================
325:
326: /** Property support for element nodes properties.
327: */
328: static abstract class ElementProp extends PropertySupport {
329: /** Constructs a new ElementProp - support for properties of
330: * element hierarchy nodes.
331: *
332: * @param name The name of the property
333: * @param type The class type of the property
334: * @param canW The canWrite flag of the property
335: */
336: public ElementProp(String name, Class type, boolean canW) {
337: super (name, type, NbBundle.getMessage(DBElementNode.class,
338: "PROP_" + name), NbBundle.getMessage(
339: DBElementNode.class, "HINT_" + name), true, canW); //NOI18N
340: }
341:
342: /** Setter for the value. This implementation only tests
343: * if the setting is possible.
344: *
345: * @param val the value of the property
346: * @exception IllegalAccessException when this ElementProp was
347: * constructed like read-only.
348: */
349: public void setValue(Object val)
350: throws IllegalArgumentException,
351: IllegalAccessException, InvocationTargetException {
352: if (!canWrite())
353: throw new IllegalAccessException(NbBundle.getMessage(
354: DBElementNode.class, "MSG_Cannot_Write")); //NOI18N
355: }
356:
357: }
358: }
|