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: package org.netbeans.tax;
042:
043: import java.io.PrintStream;
044: import java.beans.PropertyChangeListener;
045:
046: import org.netbeans.tax.event.TreeEventManager;
047: import org.netbeans.tax.event.TreeEventModel;
048: import org.netbeans.tax.event.TreeEvent;
049: import org.netbeans.tax.event.TreeEventChangeSupport;
050:
051: /**
052: * Tree objects base class with support for firing <b>events</b> and <b>merging</b>.
053: * <p>
054: * It also prescribes that each subclass MUST have <b>copy constuctor</b>
055: * calling its superclass copy constructor. The copy constructor MUST be then called
056: * during <b>cloning</b>.
057: * <p>
058: * All TreeObject subclasses should not have public contructors and therefore
059: * should be created just by factory methods.
060: * <p>
061: * Pending: validation on request, invalidation
062: *
063: * @author Libor Kramolis
064: * @author Petr Kuzel
065: * @version 0.1
066: */
067: public abstract class TreeObject implements TreeEventModel {
068:
069: /** */
070: public static final String PROP_READ_ONLY = "readOnly"; // NOI18N
071:
072: /** */
073: private boolean readOnly;
074:
075: /** */
076: transient private TreeEventChangeSupport eventChangeSupport;
077:
078: //
079: // init
080: //
081:
082: /** Creates new TreeObject. */
083: protected TreeObject() {
084: this .readOnly = false;
085: this .eventChangeSupport = null;
086: }
087:
088: /**
089: * Creates new TreeObject - copy constructor.
090: * (it does not copy eventChangeSupport)
091: */
092: protected TreeObject(TreeObject object) {
093: this .readOnly = object.readOnly;
094: this .eventChangeSupport = null;
095: }
096:
097: //
098: // clone
099: //
100:
101: /**
102: * Cloning must use copy constructors!
103: */
104: public abstract Object clone();
105:
106: //
107: // util
108: //
109:
110: /**
111: */
112: protected final boolean isInstance(Object object) {
113: return (this .getClass().isInstance(object));
114: }
115:
116: //
117: // context
118: //
119:
120: /**
121: */
122: abstract public boolean isInContext();
123:
124: /**
125: */
126: abstract public void removeFromContext() throws ReadOnlyException;
127:
128: //
129: // equals
130: //
131:
132: /**
133: */
134: public/*final*/boolean equals(Object object) {
135: return super .equals(object);
136:
137: // when TreeObjectList will compare inserted object by 'instance' instead of 'equals' we should final this method and use this impl:
138: // return equals (object, true);
139: }
140:
141: /**
142: */
143: public boolean equals(Object object, boolean deep) {
144: if (!!!isInstance(object))
145: return false;
146:
147: TreeObject peer = (TreeObject) object;
148:
149: return (this .readOnly == peer.readOnly);
150: }
151:
152: //
153: // merge
154: //
155:
156: /**
157: * <p>Update algorithm pattern that <b>reuses original tree instances</b>:
158: * <pre>
159: * // 1. optimalization
160: * if (this == treeObject) return;
161: *
162: * // 2. can merge just my instances (so no cross implemetation merge allowed)
163: * if (getClass().isAssignablFrom(treeObject.getClass())) throw CannotMergeException;
164: *
165: * // 3. let superclass do its merge
166: * super.merge(treeObject);
167: *
168: * // 4. cast to myself (see step 2)
169: * {getClass()} peer = ({getClass()}) treeObject;
170: *
171: * // 5. merge all fields at THIS CLASS HIEARCHY LEVEL but
172: * // fields that references object "parents"
173: * // use setters that just fires property changes, i.e. such that never fails
174: * // due to read-only or other constrains checks
175: *
176: * foreach field in suitableClassFields
177: * if field is simple
178: * set{field}Impl( peer.get{field}() )
179: * if field is collection or TreeObject
180: * {field}.merge(peer.{field})
181: * next field
182: *
183: * </pre>
184: * @param treeobject merge peer
185: * @throws CannotMergeException if can not merge with given node (invalid class)
186: */
187: public void merge(TreeObject treeObject)
188: throws CannotMergeException {
189: if (treeObject == this )
190: return;
191:
192: checkMergeObject(treeObject);
193:
194: TreeObject peer = treeObject;
195:
196: setReadOnly(peer.isReadOnly());
197: }
198:
199: /**
200: */
201: protected final void checkMergeObject(TreeObject treeObject)
202: throws CannotMergeException {
203: if (Util.THIS.isLoggable()) /* then */
204: Util.THIS
205: .debug("TreeObject::checkMergeObject: this = "
206: + this ); // NOI18N
207: if (Util.THIS.isLoggable()) /* then */
208: Util.THIS
209: .debug(" ::checkMergeObject: treeObject = "
210: + treeObject); // NOI18N
211: if (Util.THIS.isLoggable()) /* then */
212: Util.THIS
213: .debug(" checkMergeObject: isSameClass ? "
214: + isInstance(treeObject)); // NOI18N
215:
216: if ((treeObject == null) || (!!!isInstance(treeObject))) {
217: throw new CannotMergeException(treeObject);
218: }
219: }
220:
221: //
222: // read only
223: //
224:
225: /**
226: */
227: public final boolean isReadOnly() {
228: return readOnly;
229: }
230:
231: /**
232: */
233: protected void setReadOnly(boolean newReadOnly) {
234: if (readOnly == newReadOnly)
235: return;
236:
237: boolean oldReadOnly = this .readOnly;
238: this .readOnly = newReadOnly;
239: firePropertyChange(getEventChangeSupport().createEvent(
240: PROP_READ_ONLY,
241: oldReadOnly ? Boolean.TRUE : Boolean.FALSE,
242: newReadOnly ? Boolean.TRUE : Boolean.FALSE));
243: }
244:
245: /**
246: */
247: protected final void checkReadOnly() throws ReadOnlyException {
248: if (readOnly == true) {
249: throw new ReadOnlyException(this );
250: }
251: }
252:
253: //
254: // event model
255: //
256:
257: /**
258: * @return support that delegates to TreeEventManager
259: */
260: protected final TreeEventChangeSupport getEventChangeSupport() {
261: if (eventChangeSupport == null) {
262: eventChangeSupport = new TreeEventChangeSupport(this );
263: }
264: return eventChangeSupport;
265: }
266:
267: /**
268: * Get assigned event manager.
269: * Whole document should have only one and same EventManager. When there is not
270: * available manager, it returns null.
271: *
272: * @return assigned event manager (may be null).
273: */
274: public abstract TreeEventManager getEventManager();
275:
276: /**
277: */
278: // protected final void addEventManagerChangeListener (PropertyChangeListener listener) {
279: // getEventChangeSupport().addPropertyChangeListener (PROP_EVENT_MANAGER, listener);
280: // }
281: /**
282: */
283: // protected final void removeEventManagerChangeListener (PropertyChangeListener listener) {
284: // getEventChangeSupport().removePropertyChangeListener (PROP_EVENT_MANAGER, listener);
285: // }
286: /**
287: */
288: public final void addReadonlyChangeListener(
289: PropertyChangeListener listener) {
290: getEventChangeSupport().addPropertyChangeListener(
291: PROP_READ_ONLY, listener);
292: }
293:
294: /**
295: */
296: public final void removeReadonlyChangeListener(
297: PropertyChangeListener listener) {
298: getEventChangeSupport().removePropertyChangeListener(
299: PROP_READ_ONLY, listener);
300: }
301:
302: /**
303: * Add a PropertyChangeListener to the listener list.
304: * @param listener The listener to add.
305: */
306: public final void addPropertyChangeListener(
307: PropertyChangeListener listener) {
308: if (Util.THIS.isLoggable()) /* then */
309: Util.THIS.debug("Tree " + this + "attached listener"
310: + listener); // NOI18N
311:
312: getEventChangeSupport().addPropertyChangeListener(listener);
313: }
314:
315: /**
316: * Removes a PropertyChangeListener from the listener list.
317: * @param listener The listener to remove.
318: */
319: public final void removePropertyChangeListener(
320: PropertyChangeListener listener) {
321: getEventChangeSupport().removePropertyChangeListener(listener);
322: }
323:
324: /**
325: * Fire an existing TreeEvent to any registered listeners.
326: * No event is fired if the given event's old and new values are
327: * equal and non-null.
328: * @param evt The TreeEvent object.
329: */
330: protected final void firePropertyChange(TreeEvent evt) {
331: if (Util.THIS.isLoggable()) /* then */
332: Util.THIS.debug("TreeObject firing " + evt); // NOI18N
333:
334: getEventChangeSupport().firePropertyChange(evt);
335: bubblePropertyChange(evt);
336: }
337:
338: /** Add a PropertyChangeListener for a specific property to the listener list.
339: * @param propertyname Name of the property to listen on.
340: * @param listener The listener to add.
341: */
342: public final void addPropertyChangeListener(String propertyName,
343: PropertyChangeListener listener) {
344: getEventChangeSupport().addPropertyChangeListener(propertyName,
345: listener);
346: }
347:
348: /** Removes a PropertyChangeListener for a specific property from the listener list.
349: * @param propertyname Name of the property that was listened on.
350: * @param listener The listener to remove.
351: */
352: public final void removePropertyChangeListener(String propertyName,
353: PropertyChangeListener listener) {
354: getEventChangeSupport().removePropertyChangeListener(
355: propertyName, listener);
356: }
357:
358: /**
359: * Check if there are any listeners for a specific property.
360: *
361: * @param propertyName the property name.
362: * @return true if there are ore or more listeners for the given property
363: */
364: public final boolean hasPropertyChangeListeners(String propertyName) {
365: return getEventChangeSupport().hasPropertyChangeListeners(
366: propertyName);
367: }
368:
369: /**
370: * Report a bound property update to any registered listeners.
371: * No event is fired if old and new are equal and non-null.
372: *
373: * @param propertyName The programmatic name of the property that was changed.
374: * @param oldValue The old value of the property.
375: * @param newValue The new value of the property.
376: */
377: protected final void firePropertyChange(String propertyName,
378: Object oldValue, Object newValue) {
379: firePropertyChange(getEventChangeSupport().createEvent(
380: propertyName, oldValue, newValue));
381: }
382:
383: /**
384: * Propagate event to parents' listeners.
385: */
386: protected final void bubblePropertyChange(TreeEvent origEvt) {
387: if (Util.THIS.isLoggable()) /* then */
388: Util.THIS.debug("\nTreeObject [ " + this
389: + " ]::bubblePropertyChange: origEvt = "
390: + origEvt.getPropertyName()); // NOI18N
391:
392: TreeObject source = (TreeObject) origEvt.getSource();
393: if (source instanceof TreeAttribute) {
394: TreeAttribute attr = (TreeAttribute) source;
395: TreeElement ownElem = attr.getOwnerElement();
396: if (ownElem != null) {
397: ownElem.firePropertyChange(TreeElement.PROP_ATTRIBUTES,
398: attr, null);
399: }
400: } else if (source instanceof TreeChild) {
401: while (source != null) {
402: TreeChild child = (TreeChild) source;
403: TreeParentNode parent = child.getParentNode();
404:
405: if (Util.THIS.isLoggable()) /* then */
406: Util.THIS
407: .debug(" ::bubblePropertyChange::parentNode = "
408: + parent); // NOI18N
409:
410: if (parent != null) {
411: parent.getEventChangeSupport().firePropertyChange(
412: origEvt.createBubbling(parent));
413: }
414: source = parent;
415: }
416: }
417: }
418:
419: //
420: // debug
421: //
422:
423: /**
424: * For debugging purposes.
425: */
426: public final String listListeners() {
427: return getEventChangeSupport().listListeners();
428: }
429:
430: }
|