blob: 2936883fce6a73f065b095144389d3398354d75d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.utility.internal.node;
import java.text.Collator;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.jpt.utility.model.Model;
/**
* This interface defines the methods that must be implemented
* by any class whose instances are to be part of a containment hierarchy
* that supports a "dirty" state and validation "problems".
*
* Note: Methods marked "INTRA-TREE API" are typically only used by
* the nodes themselves, as opposed to clients of the nodes. These
* methods are called by a node on either its parent or its children.
*/
public interface Node extends Model, Comparable<Node> {
// ********** containment hierarchy (parent/children) **********
/**
* INTRA-TREE API?
* Return the node's parent in the containment hierarchy.
* Most nodes must have a parent. The parent is immutable.
* @see #children()
*/
Node getParent();
/**
* INTRA-TREE API?
* Return the node's children, which are also nodes.
* @see #getParent()
*/
Iterator<Node> children();
/**
* INTRA-TREE API?
* Return the containment hierarchy's root node.
* Most nodes must have a root.
* @see #getParent()
*/
Node root();
/**
* Return whether the node is a descendant of the specified node.
* By definition, a node is a descendant of itself.
*/
boolean isDescendantOf(Node node);
/**
* INTRA-TREE API
* Add the node's "references", and all the node's descendants'
* "references", to the specified collection. "References" are
* objects that are "referenced" by another object, as opposed
* to "owned" by another object.
* This method is of particular concern to Handles, since most
* (hopefully all) "references" are held by Handles.
* @see Reference
* @see #children()
*/
void addBranchReferencesTo(Collection<Node.Reference> branchReferences);
/**
* INTRA-TREE API?
* Add all the nodes in the object's branch of the tree,
* including the node itself, to the specified collection.
* Only really used for testing and debugging.
*/
void addAllNodesTo(Collection<Node> nodes);
// ********** model synchronization support **********
/**
* INTRA-TREE API
* This is a general notification that the specified node has been
* removed from the tree. The node receiving this notification
* should perform any necessary updates to remain in synch
* with the tree (e.g. clearing out or replacing any references
* to the removed node or any of the removed node's descendants).
* @see #isDescendantOf(Node)
*/
void nodeRemoved(Node node);
/**
* INTRA-TREE API
* This is a general notification that the specified node has been
* renamed. The node receiving this notification should mark its
* branch dirty if necessary (i.e. it references the renamed node
* or one of its descendants). This method is of particular concern
* to Handles.
* @see #isDescendantOf(Node)
*/
void nodeRenamed(Node node);
// ********** dirty flag support **********
/**
* Return whether any persistent aspects of the node,
* or any of its descendants, have changed since the node and
* its descendants were last read or saved.
*/
boolean isDirtyBranch();
String DIRTY_BRANCH_PROPERTY = "dirtyBranch";
/**
* INTRA-TREE API
* Mark the node and its parent as dirty branches.
* This message is propagated up the containment
* tree when a particular node becomes dirty.
*/
void markBranchDirty();
/**
* Mark the node and all its descendants as dirty.
* This is used when the save location of some
* top-level node is changed and the entire
* containment tree must be marked dirty so it
* will be written out.
*/
void markEntireBranchDirty();
/**
* INTRA-TREE API
* A child node's branch has been marked clean. If the node
* itself is clean and if all of its children are also clean, the
* node's branch can be marked clean. Then, if the node's
* branch is clean, the node will notify its parent that it might
* be clean also. This message is propagated up the containment
* tree when a particular node becomes clean.
*/
void markBranchCleanIfPossible();
/**
* INTRA-TREE API
* Mark the node and all its descendants as clean.
* Typically used when the node has just been
* read in or written out.
* This method is for internal use only; it is not for
* client use.
* Not the best of method names.... :-(
*/
void cascadeMarkEntireBranchClean();
// ********** problems **********
/**
* INTRA-TREE API
* Return a validator that will be invoked whenever a
* "validated" aspect of the node tree changes.
* Typically only the root node directly holds a validator.
*/
Validator getValidator();
/**
* Set a validator that will be invoked whenever a
* "validated" aspect of the node tree changes.
* Typically only the root node directly holds a validator.
*/
void setValidator(Validator validator);
/**
* Validate the node and its descendants.
* This is an explicit request invoked by a client; and it will
* typically be followed by a call to one of the following methods:
* #branchProblems()
* #hasBranchProblems()
* Whether the node maintains its problems on the fly
* or waits until this method is called is determined by the
* implementation.
* @see Problem
*/
void validateBranch();
/**
* INTRA-TREE 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.
*/
boolean validateBranchInternal();
/**
* Return all the node's problems along with all the
* node's descendants' problems.
*/
ListIterator<Problem> branchProblems();
String BRANCH_PROBLEMS_LIST = "branchProblems";
/**
* Return the size of all the node's problems along with all the
* node's descendants' problems.
*/
int branchProblemsSize();
/**
* Return whether the node or any of its descendants have problems.
*/
boolean hasBranchProblems();
String HAS_BRANCH_PROBLEMS_PROPERTY = "hasBranchProblems";
/**
* Return whether the node contains the specified branch problem.
*/
boolean containsBranchProblem(Problem problem);
/**
* INTRA-TREE API
* Something changed, rebuild the node's collection of branch problems.
*/
void rebuildBranchProblems();
/**
* INTRA-TREE API
* Add the node's problems, and all the node's descendants'
* problems, to the specified list.
* A call to this method should be immediately preceded by a call to
* #validateBranch() or all of the problems might not be
* added to the list.
* @see Problem
*/
void addBranchProblemsTo(List<Problem> branchProblems);
/**
* Clear the node's "branch" problems and the "branch"
* problems of all of its descendants.
*/
void clearAllBranchProblems();
/**
* INTRA-TREE 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.
*/
boolean clearAllBranchProblemsInternal();
// ********** comment **********
/**
* Return the user comment concerning the node.
*/
String comment();
String COMMENT_PROPERTY = "comment";
/**
* Set the user comment concerning the node.
*/
void setComment(String comment);
// ********** displaying/sorting **********
/**
* Return a string representation of the model, suitable for sorting.
*/
String displayString();
// ********** sub-interfaces **********
/**
* Simple interface defining a "reference" between two nodes.
* @see Node#addBranchReferencesTo(java.util.Collection)
*/
interface Reference {
/**
* Return the "source" node of the reference, i.e. the node that
* references the "target" node.
*/
Node source();
/**
* Return the "target" node of the reference, i.e. the node that
* is referenced by the "source" node.
*/
Node target();
}
/**
* A validator will validate a node as appropriate.
* Typically the validation will
* - occur whenever a node has changed
* - encompass the entire tree containing the node
* - execute asynchronously
*/
interface Validator {
/**
* A "significant" aspect has changed;
* validate the node as appropriate
*/
void validate();
/**
* Stop all validation of the node until #resume() is called.
* This can be used to improve the performance of any long-running
* action that triggers numerous changes to the node. Be sure to
* match a call to this method with a corresponding call to
* #resume().
*/
void pause();
/**
* Resume validation of the node. This method can only be
* called after a matching call to #pause().
*/
void resume();
}
// ********** helper implementations **********
/**
* Straightforward implementation of the Reference interface
* defined above.
*/
public class SimpleReference implements Reference {
private Node source;
private Node target;
public SimpleReference(Node source, Node target) {
super();
if (source == null || target == null) {
throw new NullPointerException();
}
this.source = source;
this.target = target;
}
public Node source() {
return this.source;
}
public Node target() {
return this.target;
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.source + " => " + this.target);
}
}
/**
* Typical comparator that can be used to sort a collection of nodes.
* Sort based on display string:
* - identical objects are equal (which means that cannot
* co-exist in a SortedSet)
* - use the default collator (which typically interleaves
* lower- and upper-case letters)
* - allow duplicate display strings (from different objects)
* - try to return consistent results for same object pairs
*/
Comparator<Node> DEFAULT_COMPARATOR =
new Comparator<Node>() {
public int compare(Node node1, Node node2) {
// disallow duplicates based on object identity
if (node1 == node2) {
return 0;
}
// first compare display strings using the default collator
int result = Collator.getInstance().compare(node1.displayString(), node2.displayString());
if (result != 0) {
return result;
}
// then compare using object-id
result = System.identityHashCode(node1) - System.identityHashCode(node2);
if (result != 0) {
return result;
}
// It's unlikely that we get to this point; but, just in case, we will return -1.
// Unfortunately, this introduces some mild unpredictability to the sort order
// (unless the objects are always passed into this method in the same order).
return -1; // if all else fails, indicate that o1 < o2
}
@Override
public String toString() {
return "Node.DEFAULT_COMPARATOR";
}
};
/**
* This validator does nothing to validate the node.
*/
Validator NULL_VALIDATOR =
new PluggableValidator(PluggableValidator.Delegate.Null.instance()) {
@Override
public String toString() {
return "Node.NULL_VALIDATOR";
}
};
}