/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.utility.node; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.Vector; import org.eclipse.persistence.tools.workbench.utility.AbstractModel; import org.eclipse.persistence.tools.workbench.utility.events.ChangeNotifier; import org.eclipse.persistence.tools.workbench.utility.events.ChangeSupport; import org.eclipse.persistence.tools.workbench.utility.iterators.CloneIterator; import org.eclipse.persistence.tools.workbench.utility.iterators.CloneListIterator; import org.eclipse.persistence.tools.workbench.utility.iterators.FilteringIterator; /** * Base class for Node Model classes. * Provides support for the following: * initialization * enforced object identity wrt #equals()/#hashCode() * containment hierarchy (parent/child) * user comment * dirty flag * problems * sorting * * Typically, subclasses should consider implementing the following methods: * the appropriate constructors * (with the appropriately-restrictive type declaration for parent) * #initialize() * #initialize(Node parentNode) * #checkParent(Node parentNode) * #addChildrenTo(List list) * #nodeRemoved(Node) * #getValidator() * #transientAspectNames() or * #addTransientAspectNamesTo(Set transientAspectNames) * #addProblemsTo(List currentProblems) * #insignificantAspectNames() * #addInsignificantAspectNamesTo(Set insignificantAspectNames) * #displayString() * #toString(StringBuffer sb) */ public abstract class AbstractNodeModel extends AbstractModel implements NodeModel { /** Containment hierarchy. */ private volatile Node parent; /** User comment. */ private volatile String comment; public static final String COMMENT_PROPERTY = "comment"; /** Track whether the node has changed. */ private volatile boolean dirty; private volatile boolean dirtyBranch; /** * The node's problems, as calculated during validation. * This list should only be modified via a ProblemSynchronizer, * allowing for asynchronous modification from another thread. */ private List problems; // pseudo-final private static final Object[] EMPTY_PROBLEM_MESSAGE_ARGUMENTS = new Object[0]; /** * Cache the node's "branch" problems, as calculated during validation. * This list should only be modified via a ProblemSynchronizer, * allowing for asynchronous modification from another thread. * This must be recalculated every time this node or one of its * descendants changes it problems. */ private List branchProblems; // pseudo-final /** * Sets of transient aspect names, keyed by class. * This is built up lazily, as the objects are modified. */ private static final Map transientAspectNameSets = new Hashtable(); /** * Sets of "insignificant" aspect names, keyed by class. * This is built up lazily, as the objects are modified. */ private static final Map insignificantAspectNameSets = new Hashtable(); // ********** constructors ********** /** * Default constructor - typically for internal use only. */ protected AbstractNodeModel() { super(); } /** * Most objects must have a parent. * Use this constructor to create a new object - the default * constructor should only be used internally. * @see #initialize(Node) */ protected AbstractNodeModel(Node parent) { this(); this.initialize(parent); } // ********** initialization ********** /** * Initialize a newly-created instance. * @see #initialize(Node) */ protected void initialize() { super.initialize(); this.comment = ""; // a new object is dirty, by definition this.dirty = true; this.dirtyBranch = true; this.problems = new Vector(); this.branchProblems = new Vector(); // when you override this method, don't forget to include: // super.initialize(); } /** * Initialize a newly-created instance. * @see #initialize() */ protected void initialize(Node parentNode) { this.checkParent(parentNode); this.parent = parentNode; // when you override this method, don't forget to include: // super.initialize(parentNode); } /** * Build a change support object that will notify the node * when one of the node's aspects has changed. * @see #aspectChanged(String) */ protected ChangeSupport buildDefaultChangeSupport() { return new ChangeSupport(this) { private static final long serialVersionUID = 1L; protected ChangeNotifier notifier() { return AbstractNodeModel.this.getChangeNotifier(); } protected ChangeSupport buildChildChangeSupport() { return AbstractNodeModel.this.buildChildChangeSupport(); } protected void sourceChanged(String aspectName) { super.sourceChanged(aspectName); AbstractNodeModel.this.aspectChanged(aspectName); } }; } /** * The aspect-specific change support objects do not need to * notify the node of changes (the parent will take care of that). */ protected ChangeSupport buildChildChangeSupport() { return new ChangeSupport(this) { private static final long serialVersionUID = 1L; protected ChangeNotifier notifier() { return AbstractNodeModel.this.getChangeNotifier(); } protected ChangeSupport buildChildChangeSupport() { // return AbstractNodeModel.this.buildChildChangeSupport(); throw new UnsupportedOperationException(); } }; } // ********** equality ********** /** * Enforce object identity - do not allow objects to be equal unless * they are the same object. * Do NOT override this method - we rely on object identity extensively. */ public final boolean equals(Object o) { return this == o; } /** * Enforce object identity - do not allow objects to be equal unless * they are the same object. * Do NOT override this method - we rely on object identity extensively. */ public final int hashCode() { return super.hashCode(); } // ********** containment hierarchy (parent/children) ********** /** * INTRA-NODE API? * Return the object's parent in the containment hierarchy. * Most objects must have a parent. * @see Node#getParent() */ public final Node getParent() { return this.parent; } /** * Set the object's parent in the containment hierarchy. * Most objects must have a parent. * @see Node#setParent(Node) */ public final void setParent(Node parentNode) { this.checkParent(parentNode); this.parent = parentNode; } /** * Throw an IllegalArgumentException if the parent is not valid * for the node. * By default require a non-null parent. Override if other restrictions exist * or the parent should be null */ protected void checkParent(Node parentNode) { if (parentNode == null) { throw new IllegalArgumentException("The parent node cannot be null"); } } /** * INTRA-NODE API? * Do NOT override this method. * Override #addChildrenTo(List). * @see #addChildrenTo(java.util.List) * @see Node#children() */ public final Iterator children() { List children = new ArrayList(); this.addChildrenTo(children); return children.iterator(); } /** * Subclasses should override this method to add their children * to the specified list. * @see #children() */ protected void addChildrenTo(List list) { // this class has no children, subclasses will... // when you override this method, don't forget to include: // super.addChildrenTo(list); } /** * Loop through the object's children setting their backpointers * to their parent, namely this object; then cascade down * through the tree. * @see Node#setChildBackpointers() */ public final void setChildBackpointers() { for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging child.setParent(this); child.setChildBackpointers(); } } /** * @see Node#isDescendantOf(Node) */ public final boolean isDescendantOf(Node node) { return (this == node) || this.parentIsDescendantOf(node); } protected boolean parentIsDescendantOf(Node node) { return (this.parent != null) && this.parent.isDescendantOf(node); } /** * Return a collection holding all the node's "references", and all * the node's descendants' "references". "References" are * objects that are "referenced" by another object, as opposed * to "owned" by another object. */ public final Iterator branchReferences() { Collection branchReferences = new ArrayList(1000); // start big this.addBranchReferencesTo(branchReferences); return branchReferences.iterator(); } /** * @see Node#addBranchReferencesTo(java.util.Collection) */ public final void addBranchReferencesTo(Collection branchReferences) { for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging child.addBranchReferencesTo(branchReferences); } } /** * Return all the nodes in the object's branch of the tree, * including the node itself. The nodes will probably returned * in "depth-first" order. * Only really used for testing and debugging. */ public final Iterator allNodes() { Collection nodes = new ArrayList(1000); // start big this.addAllNodesTo(nodes); return nodes.iterator(); } /** * INTRA-NODE API? * @see Node#addAllNodesTo(java.util.Collection) */ public final void addAllNodesTo(Collection nodes) { nodes.add(this); for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging child.addAllNodesTo(nodes); } } // ********** model synchronization support ********** /** * INTRA-NODE API * @see Node#nodeRemoved(Node) */ public void nodeRemoved(Node node) { for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging child.nodeRemoved(node); } // when you override this method, don't forget to include: // super.nodeRemoved(node); } /** * convenience method * return whether node1 is a descendant of node2; * node1 can be null */ protected boolean nodeIsDescendantOf(Node node1, Node node2) { return (node1 != null) && node1.isDescendantOf(node2); } /** * INTRA-NODE API * typically, only handles will implement this method * @see Node#nodeRenamed(Node) */ public void nodeRenamed(Node node) { for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging child.nodeRenamed(node); } // when you override this method, don't forget to include: // super.nodeRenamed(node); } // ********** user comment ********** /** * Return the object's user comment. */ public final String getComment() { return this.comment; } /** * Set the object's user comment. */ public final void setComment(String comment) { Object old = this.comment; this.comment = comment; this.firePropertyChanged(COMMENT_PROPERTY, old, comment); } // ********** change support ********** /** * INTRA-NODE API * Return a change notifier that will be used to forward * change notifications to listeners. * Typically only the root node directly holds a notifier. * @see Node#getChangeNotifier() */ public ChangeNotifier getChangeNotifier() { if (this.parent == null) { throw new IllegalStateException("This node should not be firing change events during its construction."); } return this.parent.getChangeNotifier(); } /** * Set a change notifier that will be used to forward * change notifications to listeners. * Typically only the root node directly holds a notifier. * @see Node#setChangeNotifier(ChangeNotifier) */ public void setChangeNotifier(ChangeNotifier changeNotifier) { if (this.parent == null) { throw new IllegalStateException("This root node should implement #setChangeNotifier(ChangeNotifier)."); } throw new UnsupportedOperationException("Only root nodes implement #setChangeNotifier(ChangeNotifier)."); } /** * An aspect of the node has changed: * - if it is a persistent aspect, mark the object dirty * - if it is a significant aspect, validate the object */ protected void aspectChanged(String aspectName) { if (this.aspectIsPersistent(aspectName)) { // System.out.println(Thread.currentThread() + " dirty change: " + this + ": " + aspectName); this.markDirty(); } if (this.aspectIsSignificant(aspectName)) { // System.out.println(Thread.currentThread() + " significant change: " + this + ": " + aspectName); this.getValidator().validate(); } } /** * INTRA-NODE API * Return a validator that will be invoked whenever a * "significant" aspect of the node tree changes. * Typically only the root node directly holds a validator. * @see Node#getValidator() */ public Node.Validator getValidator() { if (this.parent == null) { throw new IllegalStateException("This node should not be firing change events during its construction."); } return this.parent.getValidator(); } /** * Set a validator that will be invoked whenever a * "significant" aspect of the node tree changes. * Typically only the root node directly holds a validator. * @see Node#setValidator(Node.Validator) */ public void setValidator(Node.Validator validator) { if (this.parent == null) { throw new IllegalStateException("This root node should implement #setValidator(Node.Validator)."); } throw new UnsupportedOperationException("Only root nodes implement #setValidator(Node.Validator)."); } // ********** dirty flag support ********** /** * Return whether any persistent aspects of the object * have changed since the object was last read or saved. * This does NOT include changes to the object's descendants. */ public final boolean isDirty() { return this.dirty; } /** * Return whether any persistent aspects of the object, * or any of its descendants, have changed since the object and * its descendants were last read or saved. * @see Node#isDirtyBranch() */ public final boolean isDirtyBranch() { return this.dirtyBranch; } /** * Return whether the object is unmodified * since it was last read or saved. * This does NOT include changes to the object's descendants. */ public final boolean isClean() { return ! this.dirty; } /** * Return whether the object and all of its descendants * are unmodified since the object and * its descendants were last read or saved. */ public final boolean isCleanBranch() { return ! this.dirtyBranch; } /** * Set the dirty branch flag setting. This is set to true * when either the object or one of its descendants becomes dirty. */ private void setIsDirtyBranch(boolean dirtyBranch) { boolean old = this.dirtyBranch; this.dirtyBranch = dirtyBranch; this.firePropertyChanged(DIRTY_BRANCH_PROPERTY, old, dirtyBranch); } /** * Mark the object as dirty and as a dirty branch. * An object is marked dirty when either a "persistent" attribute * has changed or its save location has changed. */ private void markDirty() { this.dirty = true; this.markBranchDirty(); } /** * INTRA-NODE API * Mark the object and its parent as dirty branches * if necessary. * @see Node#markBranchDirty() */ public void markBranchDirty() { // short-circuit any unnecessary propagation if (this.dirtyBranch) { // if this is already a dirty branch, the parent must be also return; } this.setIsDirtyBranch(true); this.markParentBranchDirty(); } protected void markParentBranchDirty() { if (this.parent != null) { this.parent.markBranchDirty(); } } /** * Mark the object and all its descendants as dirty. * This is used when the save location of some * top-level object is changed and the entire * containment tree must be marked dirty so it * will be written out. * @see Node#markEntireBranchDirty() */ public final void markEntireBranchDirty() { this.markDirty(); for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging child.markEntireBranchDirty(); } } /** * Mark the object and all its descendants as clean. * Then notify the object's parent that it (the parent) * might now be a clean branch also. * Typically used when the object has just been * read in or written out. */ public final void markEntireBranchClean() { this.cascadeMarkEntireBranchClean(); this.markParentBranchCleanIfPossible(); } protected void markParentBranchCleanIfPossible() { if (this.parent != null) { this.parent.markBranchCleanIfPossible(); } } /** * INTRA-NODE API * Mark the object and all its descendants as clean. * This method is for internal use only; it is not for * client use. * @see Node#cascadeMarkEntireBranchClean() */ public final void cascadeMarkEntireBranchClean() { for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging child.cascadeMarkEntireBranchClean(); } this.dirty = false; this.setIsDirtyBranch(false); } /** * INTRA-NODE API * The object has been marked clean; possibly * its entire branch is now clean, as well as its parent's branch. * @see Node#markBranchCleanIfPossible() */ public final void markBranchCleanIfPossible() { // short-circuit any unnecessary propagation if (this.dirty) { // if the object is "locally" dirty, it is still a dirty branch return; } for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging if (child.isDirtyBranch()) { return; } } this.setIsDirtyBranch(false); this.markParentBranchCleanIfPossible(); } private boolean aspectIsPersistent(String aspectName) { return ! this.aspectIsTransient(aspectName); } private boolean aspectIsTransient(String aspectName) { return this.transientAspectNames().contains(aspectName); } /** * Return a set of the object's transient aspect names. * These are the aspects that, when they change, will NOT cause the * object to be marked dirty. * If you need instance-based calculation of your transient aspects, * override this method. If class-based calculation is sufficient, * override #addTransientAspectNamesTo(Set). */ protected final Set transientAspectNames() { synchronized (transientAspectNameSets) { Set transientAspectNames = (Set) transientAspectNameSets.get(this.getClass()); if (transientAspectNames == null) { transientAspectNames = new HashSet(); this.addTransientAspectNamesTo(transientAspectNames); transientAspectNameSets.put(this.getClass(), transientAspectNames); } return transientAspectNames; } } /** * Add the object's transient aspect names to the specified set. * These are the aspects that, when they change, will NOT cause the * object to be marked dirty. * If class-based calculation of your transient aspects is sufficient, * override this method. If you need instance-based calculation, * override #transientAspectNames(). */ protected void addTransientAspectNamesTo(Set transientAspectNames) { transientAspectNames.add(DIRTY_BRANCH_PROPERTY); transientAspectNames.add(BRANCH_PROBLEMS_LIST); transientAspectNames.add(HAS_BRANCH_PROBLEMS_PROPERTY); // when you override this method, don't forget to include: // super.addTransientAspectNamesTo(transientAspectNames); } /** * Return the dirty nodes in the object's branch of the tree, * including the node itself (if appropriate). * Only really used for testing and debugging. */ public final Iterator allDirtyNodes() { return new FilteringIterator(this.allNodes()) { protected boolean accept(Object o) { return (o instanceof AbstractNodeModel) && ((AbstractNodeModel) o).isDirty(); } }; } // ********** problems ********** /** * Return the node's problems. * This does NOT include the problems of the node's descendants. * @see #branchProblems() */ public final Iterator problems() { return new CloneIterator(this.problems); // removes are not allowed } /** * Return the size of the node's problems. * This does NOT include the problems of the node's descendants. * @see #branchProblemsSize() */ public final int problemsSize() { return this.problems.size(); } /** * Return whether the node has problems * This does NOT include the problems of the node's descendants. * @see #hasBranchProblems() */ public final boolean hasProblems() { return ! this.problems.isEmpty(); } /** * Return all the node's problems along with all the * node's descendants' problems. * @see Node#branchProblems() */ public final ListIterator branchProblems() { return new CloneListIterator(this.branchProblems); // removes are not allowed } /** * Return the size of all the node's problems along with all the * node's descendants' problems. * @see Node#branchProblemsSize() */ public final int branchProblemsSize() { return this.branchProblems.size(); } /** * Return whether the node or any of its descendants have problems. * @see Node#hasBranchProblems() */ public final boolean hasBranchProblems() { return ! this.branchProblems.isEmpty(); } /** * @see Node#containsBranchProblem(Problem) */ public final boolean containsBranchProblem(Problem problem) { return this.branchProblems.contains(problem); } protected final Problem buildProblem(String messageKey, Object[] messageArguments) { return new DefaultProblem(this, messageKey, messageArguments); } protected final Problem buildProblem(String messageKey) { return this.buildProblem(messageKey, EMPTY_PROBLEM_MESSAGE_ARGUMENTS); } protected final Problem buildProblem(String messageKey, Object messageArgument) { return this.buildProblem(messageKey, new Object[] {messageArgument}); } protected final Problem buildProblem(String messageKey, Object messageArgument1, Object messageArgument2) { return this.buildProblem(messageKey, new Object[] {messageArgument1, messageArgument2}); } protected final Problem buildProblem(String messageKey, Object messageArgument1, Object messageArgument2, Object messageArgument3) { return this.buildProblem(messageKey, new Object[] {messageArgument1, messageArgument2, messageArgument3}); } /** * Validate the node and all of its descendants, * and update their sets of "branch" problems. * If the node's "branch" problems have changed, * notify the node's parent. * @see Node#validateBranch() */ public void validateBranch() { if (this.validateBranchInternal()) { // if our "branch" problems have changed, then // our parent must rebuild its "branch" problems also this.rebuildParentBranchProblems(); } } protected void rebuildParentBranchProblems() { if (this.parent != null) { this.parent.rebuildBranchProblems(); } } /** * INTRA-NODE API * Validate the node and all of its descendants, * and update their sets of "branch" problems. * Return true if the collection of "branch" problems has changed. * This method is for internal use only; it is not for * client use. * @see Node#validateBranchInternal() */ public boolean validateBranchInternal() { // rebuild "branch" problems in children first for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging // ignore the return value because we are going to rebuild our "branch" // problems no matter what, to see if they have changed child.validateBranchInternal(); } this.problems.clear(); this.addProblemsTo(this.problems); return this.checkBranchProblems(); } /** * Check for any problems and add them to the specified list. * This method should ONLY add problems for this particular node; * it should NOT add problems for any of this node's descendants * or ancestors. (Although there will be times when it is debatable * as to which node a problem "belongs" to....) * * NB: This method should NOT modify ANY part of the node's state! * It is a READ-ONLY behavior. ONLY the list of current problems * passed in to the method should be modified. */ protected void addProblemsTo(List currentProblems) { // The default is to do nothing. // When you override this method, don't forget to include: // super.addProblemsTo(currentProblems); } /** * Rebuild the "branch" problems and return whether they have * changed. * NB: The entire collection of "branch" problems must be re-calculated * with EVERY "significant" change - we cannot keep it in synch via * change notifications because if a descendant with problems is * removed or replaced we will not receive notification that its * problems were removed from our "branch" problems. */ private boolean checkBranchProblems() { List oldBranchProblems = new Vector(this.branchProblems); int oldSize = this.branchProblems.size(); this.branchProblems.clear(); this.branchProblems.addAll(this.problems); for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging child.addBranchProblemsTo(this.branchProblems); } // if the size has changed to or from zero, our virtual flag has changed int newSize = this.branchProblems.size(); if ((oldSize == 0) && (newSize != 0)) { this.firePropertyChanged(HAS_BRANCH_PROBLEMS_PROPERTY, false, true); } else if ((oldSize != 0) && (newSize == 0)) { this.firePropertyChanged(HAS_BRANCH_PROBLEMS_PROPERTY, true, false); } if (oldBranchProblems.equals(this.branchProblems)) { return false; // our "branch" problems did not change } // our "branch" problems changed this.fireListChanged(BRANCH_PROBLEMS_LIST); return true; } /** * INTRA-NODE API * Add all the problems of the node and all * the problems of its descendants to the * specified collection. * @see Node#addBranchProblemsTo(java.util.List) */ public final void addBranchProblemsTo(List list) { list.addAll(this.branchProblems); } /** * INTRA-NODE API * A child node's "branch" problems changed; * therefore the node's "branch" problems have changed also and * must be rebuilt. * @see Node#rebuildBranchProblems() */ public final void rebuildBranchProblems() { if ( ! this.checkBranchProblems()) { throw new IllegalStateException("we should not get here unless our \"branch\" problems have changed"); } this.rebuildParentBranchProblems(); } /** * Clear the node's "branch" problems and the "branch" * problems of all of its descendants. * If the node's "branch" problems have changed, * notify the node's parent. * @see Node#clearAllBranchProblems() */ public final void clearAllBranchProblems() { if (this.clearAllBranchProblemsInternal()) { // if our "branch" problems have changed, then // our parent must rebuild its "branch" problems also this.rebuildParentBranchProblems(); } } /** * INTRA-NODE API * Clear the node's "branch" problems and the "branch" * problems of all of its descendants. * Return true if the collection of "branch" problems has changed. * This method is for internal use only; it is not for * client use. * @see Node#clearAllBranchProblemsInternal() */ public final boolean clearAllBranchProblemsInternal() { if (this.branchProblems.isEmpty()) { return false; } for (Iterator stream = this.children(); stream.hasNext(); ) { Node child = (Node) stream.next(); // pull out the child to ease debugging // ignore the return value because we are going to clear our "branch" // problems no matter what child.clearAllBranchProblemsInternal(); } this.problems.clear(); this.branchProblems.clear(); this.firePropertyChanged(HAS_BRANCH_PROBLEMS_PROPERTY, true, false); this.fireListChanged(BRANCH_PROBLEMS_LIST); return true; } private boolean aspectIsSignificant(String aspectName) { return ! this.aspectIsInsignificant(aspectName); } private boolean aspectIsInsignificant(String aspectName) { return this.insignificantAspectNames().contains(aspectName); } /** * Return a set of the object's "insignificant" aspect names. * These are the aspects that, when they change, will NOT cause the * object (or its containing tree) to be validated, i.e. checked for problems. * If you need instance-based calculation of your "insignificant" aspects, * override this method. If class-based calculation is sufficient, * override #addInsignificantAspectNamesTo(Set). */ protected final Set insignificantAspectNames() { synchronized (insignificantAspectNameSets) { Set insignificantAspectNames = (Set) insignificantAspectNameSets.get(this.getClass()); if (insignificantAspectNames == null) { insignificantAspectNames = new HashSet(); this.addInsignificantAspectNamesTo(insignificantAspectNames); insignificantAspectNameSets.put(this.getClass(), insignificantAspectNames); } return insignificantAspectNames; } } /** * Add the object's "insignificant" aspect names to the specified set. * These are the aspects that, when they change, will NOT cause the * object (or its containing tree) to be validated, i.e. checked for problems. * If class-based calculation of your "insignificant" aspects is sufficient, * override this method. If you need instance-based calculation, * override #insignificantAspectNames(). */ protected void addInsignificantAspectNamesTo(Set insignificantAspectNames) { insignificantAspectNames.add(COMMENT_PROPERTY); insignificantAspectNames.add(DIRTY_BRANCH_PROPERTY); insignificantAspectNames.add(BRANCH_PROBLEMS_LIST); insignificantAspectNames.add(HAS_BRANCH_PROBLEMS_PROPERTY); // when you override this method, don't forget to include: // super.addInsignificantAspectNamesTo(insignificantAspectNames); } // ********** display methods ********** /** * Compare display strings. * @see Comparable#compareTo(Object) */ public int compareTo(Object o) { return DEFAULT_COMPARATOR.compare(this, o); } /** * Return a developer-friendly String. If you want something useful for * displaying in a user interface, use #displayString(). * If you want to give more information in your #toString(), * override #toString(StringBuffer sb). * Whatever you add to that string buffer will show up between the parentheses. * @see org.eclipse.persistence.tools.workbench.utility.AbstractModel#toString(StringBuffer sb) * @see #displayString() */ public final String toString() { return super.toString(); } }