/*******************************************************************************
* 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.mappingsmodel.handles;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.persistence.tools.workbench.mappingsmodel.MWNode;
import org.eclipse.persistence.tools.workbench.mappingsmodel.db.MWDatabase;
import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.MWDescriptor;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWMapping;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClass;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassRepository;
import org.eclipse.persistence.tools.workbench.mappingsmodel.project.MWProject;
import org.eclipse.persistence.tools.workbench.utility.events.ChangeNotifier;
import org.eclipse.persistence.tools.workbench.utility.iterators.NullIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.NullListIterator;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
import org.eclipse.persistence.tools.workbench.utility.node.Problem;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
/**
* Handles are used to isolate the painful bits of code
* necessary to correctly handle references to model objects.
*
* All handles should subclass this abstract class.
*/
public abstract class MWHandle implements MWNode {
/** Containment hierarchy. */
private MWNode parent; // pseudo-final
/** This is used to synchronize the model when a node is removed. */
private NodeReferenceScrubber scrubber; // pseudo-final
/** A handle is dirty when the path to its node changes. */
private volatile boolean dirty;
// ********** constructors **********
/**
* default constructor - for TopLink use only
*/
protected MWHandle() {
super();
// a new object is dirty, by definition
this.dirty = true;
}
protected MWHandle(MWNode parent, NodeReferenceScrubber scrubber) {
this();
this.setParent(parent);
this.setScrubberInternal(scrubber);
}
// ********** containment hierarchy (parent/children) **********
public final Node getParent() {
return this.parent;
}
/**
* Set the object's parent in the containment hierarchy.
* Most objects must have a parent.
*/
public final void setParent(Node parent) {
if (parent == null) {
throw new NullPointerException();
}
this.parent = (MWNode) parent;
}
// handles do not have children
public final Iterator children() {
return NullIterator.instance();
}
// handles do not have children
public final void setChildBackpointers() {
// do nothing
}
public final boolean isDescendantOf(Node node) {
return (this == node) || this.parent.isDescendantOf(node);
}
public final void addBranchReferencesTo(Collection branchReferences) {
Node node = this.node();
if (node != null) {
branchReferences.add(new SimpleReference(this, node));
}
}
// handles do not have children
public final void addAllNodesTo(Collection nodes) {
nodes.add(this);
}
// ********** dirty flag support **********
public final boolean isDirtyBranch() {
return this.dirty;
}
public final void markBranchDirty() {
throw new IllegalStateException("handles shouldn't have children");
}
public final void markEntireBranchDirty() {
this.markDirty();
}
public final void cascadeMarkEntireBranchClean() {
this.dirty = false;
}
public final void markBranchCleanIfPossible() {
throw new IllegalStateException("handles shouldn't have children");
}
private void markDirty() {
this.dirty = true;
this.parent.markBranchDirty();
}
// ********** change support **********
public final ChangeNotifier getChangeNotifier() {
return this.parent.getChangeNotifier();
}
public final void setChangeNotifier(ChangeNotifier changeNotifier) {
throw new UnsupportedOperationException("Only root nodes implement #setChangeNotifier(ChangeNotifier): " + this);
}
// ********** problems **********
public final Node.Validator getValidator() {
return this.parent.getValidator();
}
public final void setValidator(Node.Validator validator) {
throw new UnsupportedOperationException("Only root nodes implement #setValidator(Node.Validator): " + this);
}
// handles will never have any problems, nor do they have any descendants
public final ListIterator branchProblems() {
return NullListIterator.instance();
}
// handles will never have any branch problems, nor do they have any descendants
public final boolean hasBranchProblems() {
return false;
}
// handles will never have any branch problems, nor do they have any descendants
public final boolean containsBranchProblem(Problem problem) {
return false;
}
// handles will never have any problems, their parents have the problems
public final void validateBranch() {
// do nothing
}
// handles will never have any problems, their parents have the problems
public final boolean validateBranchInternal() {
return false;
}
// handles will never have any branch problems, their parents have the problems
public final void rebuildBranchProblems() {
// do nothing
}
// handles will never have any problems, their parents have the problems
public final void addBranchProblemsTo(List branchProblems) {
// do nothing
}
// handles will never have any problems, their parents have the problems
public final int branchProblemsSize() {
return 0;
}
// handles will never have any problems, their parents have the problems
public final void clearAllBranchProblems() {
// do nothing
}
// handles will never have any problems, their parents have the problems
public final boolean clearAllBranchProblemsInternal() {
return false;
}
// ********** convenience methods **********
public final MWNode getMWParent() {
return this.parent;
}
/**
* Do NOT override this method.
* Every model object should be able to return the project.
*/
// If Java ever allows us to override with a different return type, we can call this
// getProject() and have MWRModel override and return an MWRProject.
// Supposedly, this will happen in jdk 1.5....
public final MWProject getProject() {
return this.getMWParent().getProject();
}
/**
* Do NOT override this method.
* Every model object in a relational project should be able to return the database.
*/
public final MWDatabase getDatabase() {
return this.getProject().getDatabase();
}
/**
* Do NOT override this method.
* Every model object should be able to return the class repository.
*/
public final MWClassRepository getRepository() {
return this.getProject().getClassRepository();
}
/**
* Do NOT override this method.
* Every model object should be able to fetch types.
* This returns a type that may be only a stub
* (i.e. it does not have any attributes, methods, etc. populated).
*
* Typically, this is the best method to call when you just need
* a class (as opposed to an attribute or method). If you need
* a fully-populated type, call this method to get the type,
* check whether the type is a stub, and, if it is a stub, refresh it.
* This may be require some interaction with the user.
*
* You may not *always* want to refresh a non-stub type,
* since that would probably drop any changes entered manually
* by the user since the last refresh.
*/
public final MWClass typeNamed(String typeName) {
return this.getRepository().typeNamedInternal(typeName);
}
/**
* Do NOT override this method.
* Every model object should be able to fetch types.
* This returns a type that may be only a stub
* (i.e. it does not have any attributes, methods, etc. populated).
*
* Typically, this is the best method to call when you just need
* a class (as opposed to an attribute or method). If you need
* a fully-populated type, call this method to get the type,
* check whether the type is a stub, and, if it is a stub, refresh it.
* This may be require some interaction with the user.
*
* You may not *always* want to refresh a non-stub type,
* since that would probably drop any changes entered manually
* by the user since the last refresh.
*/
public final MWClass typeFor(Class javaClass) {
return this.typeNamed(javaClass.getName());
}
// ********** model synchronization support **********
/**
* Return the node referenced by the handle.
*/
protected abstract Node node();
/**
* If the handle's node has been renamed, or it is a descendant of
* a node that has been renamed, the handle must mark its branch
* dirty so that the handle is saved with the new name.
*/
public void nodeRenamed(Node node) {
if ((this.node() != null) && this.node().isDescendantOf(node)) {
this.markDirty();
}
}
/**
* If the handle's node has been removed, or it is a descendant of
* a node that has been removed, notify the scrubber.
*/
public final void nodeRemoved(Node removedNode) {
if ((this.node() != null) && this.node().isDescendantOf(removedNode)) {
this.scrubber.nodeReferenceRemoved(this.node(), this);
}
}
// synchronization is handled by the parent object, do not override
public final void descriptorReplaced(MWDescriptor oldDescriptor, MWDescriptor newDescriptor) {
// do nothing
}
// synchronization is handled by the parent object, do not override
public final void mappingReplaced(MWMapping oldMapping, MWMapping newMapping) {
// do nothing
}
// synchronization is handled by the parent object, do not override
public final void descriptorUnmapped(Collection mappings) {
// do nothing
}
/**
* Subclasses will probably implement something like
* #setScrubber(NodeReferenceScrubber) that returns 'this'
*/
protected final void setScrubberInternal(NodeReferenceScrubber scrubber) {
if (scrubber == null) {
throw new NullPointerException();
}
this.scrubber = scrubber;
}
// ********** post-read methods **********
/**
* Override this method if there are objects in the hierarchy
* that depend on this handle being resolved before postProjectBuild().
* Do not override unless the handle is for a class or class sub-object
* (attribute, method, etc.)
*/
public void resolveClassHandles() {
// do nothing
}
/**
* Override this method if there are objects in the hierarchy
* that depend on this handle being resolved before postProjectBuild().
* Do not override unless the handle is for a descriptor or descriptor sub-object
* (mapping, xml data field, etc.)
*/
public void resolveDescriptorHandles() {
// do nothing
}
/**
* Override this method if there are objects in the hierarchy
* that depend on this handle being resolved before postProjectBuild().
* Do not override unless the handle is for a meta data object or sub-object
* (field, schema component, etc.)
*/
public void resolveMetadataHandles() {
// do nothing
}
/**
* Override this method if there are objects in the hierarchy
* that depend on this handle being resolved before postProjectBuild().
* Do not override unless the handle is for a meta data object or sub-object
* (column, schema component, etc.)
*/
public void resolveColumnHandles() {
// do nothing
}
/**
* Override this method if there are objects in the hierarchy
* that depend on this handle being resolved before postProjectBuild().
* Do not override unless the handle is for a meta data object or sub-object
* (field, schema component, etc.)
*/
public void resolveReferenceHandles() {
// do nothing
}
/**
* Override this method if there are objects in the hierarchy
* that depend on this handle being resolved before postProjectBuild().
* Do not override unless the handle is for a meta data object or sub-object
* (field, schema component, etc.)
*/
public void resolveMethodHandles() {
// do nothing
}
public void postProjectBuild() {
if (this.scrubber == null) {
throw new NullPointerException("This handle's 'scrubber' should have been set by its parent upon creation.");
}
}
// ********** display methods **********
/**
* handles are not displayed
*/
public final String displayString() {
throw new UnsupportedOperationException();
}
// ********** standard methods **********
public String toString() {
StringBuffer sb = new StringBuffer();
StringTools.buildSimpleToStringOn(this, sb);
sb.append(" (");
this.toString(sb);
sb.append(')');
return sb.toString();
}
public abstract void toString(StringBuffer sb);
// ********** member interface **********
/**
* This interface defines the method called by a handle when the node the
* handle references has been removed from the project. Typically the
* handle's parent will implement an adapter that will call the appropriate
* method to either remove or clear the handle. The handle itself will
* continue to hold the reference node - it is up to the parent to
* synchronize appropriately.
*/
public interface NodeReferenceScrubber {
/**
* The specified node is no longer referenced by the specified handle.
*/
void nodeReferenceRemoved(Node node, MWHandle handle);
NodeReferenceScrubber NULL_INSTANCE = new NodeReferenceScrubber() {
public void nodeReferenceRemoved(Node node, MWHandle handle) {
// do nothing
}
public String toString() {
return "NullReferenceScrubber";
}
};
}
}