package easik.sketch.vertex; //~--- non-JDK imports -------------------------------------------------------- //~--- JDK imports ------------------------------------------------------------ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.undo.AbstractUndoableEdit; import easik.Easik; import easik.model.constraint.EqualizerConstraint; import easik.model.constraint.LimitConstraint; import easik.model.constraint.ModelConstraint; import easik.model.constraint.ProductConstraint; import easik.model.constraint.PullbackConstraint; import easik.model.constraint.SumConstraint; import easik.model.keys.UniqueIndexable; import easik.model.keys.UniqueKey; import easik.model.path.ModelPath; import easik.model.vertex.ModelVertex; import easik.sketch.Sketch; import easik.sketch.edge.SketchEdge; import easik.sketch.util.graph.SketchGraphModel; import easik.ui.SketchFrame; import easik.xml.xsd.nodes.constraints.XSDKey; import easik.xml.xsd.nodes.elements.XSDElement; import easik.xml.xsd.nodes.types.XSDType; /** * An entity node represents a table in a db. It has a name, attributes, and * unique keys. This class keeps track of all these elements. */ public class EntityNode extends ModelVertex<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> { /** * F * */ private static final long serialVersionUID = -284442112529134937L; /** */ private XSDElement xsdElement; /** */ private XSDType xsdType; /** * Creates a new entity node with the name provided at position (0,0). * * @param nodeName The name of the new node * @param inSketch the sketch */ public EntityNode(final String nodeName, final Sketch inSketch) { this(nodeName, 0, 0, inSketch); } /** * Creates a new enity node with the name provided. Stores visual representation * information. * * @param nodeName Name of the new node * @param x X Coordinate of the new node * @param y Y Coordinate of the new node * @param inSketch the sketch */ public EntityNode(final String nodeName, final int x, final int y, final Sketch inSketch) { super(nodeName, x, y, inSketch); _treeNode = new DefaultMutableTreeNode(this); _treeNode.add(_attribNode); _treeNode.add(_keyNode); } /** * Changes the name of the EntityNode. This also updates the name in the * EntityNode's SketchFrame, to maintain consistency. Note that if you attempt * to rename an EntityNode to one that already exists in the same frame, a * trailing number will be added to make the name unique. This means, however, * that the name might not end up being what you set--if that's a problem, make * sure the new name isn't taken yet yourself, via SketchFrame.getEntity(). * * @param newName the new name to attempt to set. The actual name set may have a * trailing number appended/incremented. * @see easik.ui.SketchFrame */ @Override public void setName(String newName) { final String oldName = getName(); // The name might have to change, if there are duplicates. nodeRenamed // tells us the final name to use: newName = getMModel().nodeRenamed(this, oldName, newName); super.setName(newName); } /** * Returns a Set of SketchEdges of the outgoing edges of this EntityNode. Note * that edges for constraints are <b>not</b> included. * * @return Set of outgoing edges from this EntityNode */ @Override public Set<SketchEdge> getOutgoingEdges() { @SuppressWarnings("rawtypes") final List outEdges = _theModel.getGraphLayoutCache().getOutgoingEdges(this, Collections.emptySet(), false, true); final Set<SketchEdge> outgoing = new LinkedHashSet<>(outEdges.size()); for (final Object out : outEdges) { if (out instanceof SketchEdge) { outgoing.add((SketchEdge) out); } } return outgoing; } /** * Returns a Set of UniqueIndexable-implementing outgoing edges of this node. * * @return set of uniques */ @Override public Set<UniqueIndexable> getIndexableEdges() { final Set<SketchEdge> edges = getOutgoingEdges(); final Set<UniqueIndexable> attribs = new LinkedHashSet<>(edges.size()); for (final SketchEdge edge : edges) { if (edge instanceof UniqueIndexable) { attribs.add((UniqueIndexable) edge); } } return attribs; } /** * Add a constraint to the set of constraints of which this node is a part * * @param con the constraint */ @Override public void addConstraint( final ModelConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> con) { final boolean added = _constraints.add(con); if (added) { getMModel().getGraphModel().postEdit(new AbstractUndoableEdit() { /** * */ private static final long serialVersionUID = 2332966369924855958L; @Override public void undo() { super.undo(); _constraints.remove(con); } @Override public void redo() { super.redo(); _constraints.add(con); } }); } } /** * Gets the set of constraints of which this node is a part * * @return the constraints as an unmodifiable set */ @Override public Set<ModelConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge>> getConstraints() { return Collections.unmodifiableSet(_constraints); } /** * Returns the edges that might need to be "shadowed". This is used by various * constraints where we need to do automatic path insertion. For example, sum * constraints, where we have something like A -> B -> C, where A -> B * is the sum, but because B is auto-created by the insertion into A, we have to * specify the B -> C foreign key when inserting into A. * * @return LinkedList of SketchEdges that are not-null (in other words, * non-partial edges) */ public Collection<SketchEdge> getNonPartialEdges() { final Collection<SketchEdge> edges = new LinkedList<>(); for (final SketchEdge e : getOutgoingEdges()) { if (!e.isPartial()) { edges.add(e); } } return edges; } /** * This method returns the edges that will be "shadowed" in this entity for * allowing various types of constraints. The problem arises when we have * something like: A -> B -> C, where A is the summand of B, but B has to * be specified. In this case, the B to C edge will be returned as a "shadow" * edge. We handle this for other constraint types, too. For a good, working, * technical example, see the shadowEdges.easik sample sketch. * * @return a set of edges that will be shadowed by this entity node. * * Removing shadow edges completely. Started by Sarah Van der Laan * continued by Federico Mora because a partial solution is worse than * all or nothing * * public LinkedHashSet<SketchEdge> getShadowEdges() { return * getShadowEdges(new LinkedHashSet<EntityNode>(5), new * LinkedHashSet<SketchEdge>(5)); } */ // Package-only implementation of the above that breaks recursion by // ignoring // shadowed nodes that we already know about. /** * * * @param ignore * @param constraintEdges * * @return */ LinkedHashSet<SketchEdge> getShadowEdges(final Collection<EntityNode> ignore, final LinkedHashSet<SketchEdge> constraintEdges) { // These are the other entity node that we (potentially) need to shadow: final Collection<EntityNode> shadow = new LinkedHashSet<>(10); CONSTRAINT: for (final ModelConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> c : getMModel() .getConstraints().values()) { if (c instanceof SumConstraint) { final SumConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> s = (SumConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge>) c; for (final ModelPath<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> path : s .getPaths()) { // If this entity is the domain of a sum constraint path, // we need to include every entity along that path (except // for this entity, of course): if (path.getDomain() == this) { shadow.addAll(path.getEntities()); constraintEdges.addAll(path.getEdges()); continue CONSTRAINT; } } } else if (c instanceof ProductConstraint) { final ProductConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> p = (ProductConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge>) c; for (final ModelPath<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> path : p .getPaths()) { // If this entity is the codomain of a product constraint // path, // we need to include every entity, excluding the // codomains), along each product path if (path.getCoDomain() == this) { for (final ModelPath<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> prodPath : p .getPaths()) { // But we ignore all of the product path edges, // since they will be automatically generated: constraintEdges.addAll(prodPath.getEdges()); final Deque<EntityNode> pathNodes = new LinkedList<>(); pathNodes.addAll(prodPath.getEntities()); pathNodes.removeLast(); shadow.addAll(pathNodes); } continue CONSTRAINT; } } } else if (c instanceof EqualizerConstraint) { final EqualizerConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> e = (EqualizerConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge>) c; // If this entity is the domain of an equalizer, we need to // shadow the projection entity. // We actually shadow every node along the projection path, // which will handle multi-edge // projection paths, though EASIK doesn't actually support those // (primarily because deletion // is needed; an injective *path* is fine for creation, but // cannot be cleanly deleted--an // injective *edge* doesn't cause that problem). if (e.getSourceEntity() == this) { final ModelPath<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> projection = e .getProjection(); shadow.addAll(projection.getEntities()); // Ignore the projection edge itself: constraintEdges.addAll(projection.getEdges()); continue CONSTRAINT; } } else if (c instanceof PullbackConstraint) { final PullbackConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> pb = (PullbackConstraint<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge>) c; // A pullback is a little trickier than the above: any insertion // into one // of the pullback path domains (i.e. projection codomains) // means we have // to shadow all the entities along *both* projection paths. // WPBEDIT CF2012 for (int i = 0; i < pb.getWidth(); i++) { ModelPath<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> proj = pb .getProjectionPath(i); if (this == proj.getCoDomain()) { for (int j = 0; j < pb.getWidth(); j++) { proj = pb.getProjectionPath(j); Deque<EntityNode> projNodes = new LinkedList<>(proj.getEntities()); projNodes.removeLast(); shadow.addAll(projNodes); constraintEdges.addAll(proj.getEdges()); } continue CONSTRAINT; } } } else if (c instanceof LimitConstraint) { // TRIANGLES TODO CF2012 incomplete } } final LinkedHashSet<SketchEdge> shadowEdges = new LinkedHashSet<>(20); // All of the ignore entities, plus everything we just found should be // ignored by any recursion: final Collection<EntityNode> toIgnore = new LinkedHashSet<>(3); toIgnore.add(this); toIgnore.addAll(ignore); toIgnore.addAll(shadow); for (final EntityNode node : shadow) { // If it's ourself (which obviously doesn't need to be shadowed), or // a node // that a call up the stack is already shadowing the node, ignore // it: if ((node == this) || ignore.contains(node)) { continue; } // Otherwise, shadow its non-partial edges, and all of its shadow // edges: shadowEdges.addAll(node.getShadowEdges(toIgnore, constraintEdges)); shadowEdges.addAll(node.getNonPartialEdges()); shadowEdges.removeAll(constraintEdges); // Remove edges already // involved in the sum } return shadowEdges; } /** * Determines whether a set of attributes would enforce new uniqueness on this * entity, or whether uniqueness of those attributes is already enforced by an * existing unique key. Essentially, this boils down to two things: there can be * no existing unique key that is a duplicate of the specified elements, and * there can be no unique key that is a subset of the specified elements. The * latter is because if a unique key on (A, B) already exists, then (A, B, C) * will be already guaranteed to be unique because the (A, B) part has to be * unique. * * @param inElems The list/set/collection/array of attributes/edges to check * @return the unique key that already exists that enforces the uniqueness, or * null if the specified items would form new uniqueness. */ @Override public UniqueKey<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> uniqueKeyOn( final Collection<UniqueIndexable> inElems) { return uniqueKeyOn(new HashSet<>(inElems)); } /** * Array or multi-value version of uniqueKeyOn(...) * * @param inElems The list/set/collection/array of attributes/edges to check * @return the unique key that already exists that enforces the uniqueness, or * null if the specified items would form new uniqueness. */ @Override public UniqueKey<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> uniqueKeyOn( final UniqueIndexable... inElems) { return uniqueKeyOn(Arrays.asList(inElems)); } /** * Version of uniqueKeyOn() that takes a set of UniqueIndexable edges. * * @param inElemSet The list/set/collection/array of attributes/edges to check * @return the unique key that already exists that enforces the uniqueness, or * null if the specified items would form new uniqueness. */ @Override public UniqueKey<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> uniqueKeyOn( final Set<UniqueIndexable> inElemSet) { for (final UniqueKey<SketchFrame, SketchGraphModel, Sketch, EntityNode, SketchEdge> q : getUniqueKeys()) { if (inElemSet.containsAll(q.getElements())) { return q; } } return null; } /** * * * @param pattern * * @return */ public String getPrimaryKeyName(final String pattern) { return pattern.replaceAll("<table>", getName()); } /** * * * @param appliesTo * * @return */ public XSDKey createXMLPrimaryKey(final XSDElement appliesTo) { final String idName = Easik.getInstance().getSettings().getProperty("xml_id_name"); final boolean idIsAttribute = Boolean .valueOf(Easik.getInstance().getSettings().getProperty("xml_id_is_attribute")); if (idIsAttribute) { return new XSDKey(getXMLPrimaryKeyName(), appliesTo, '@' + idName); } return new XSDKey(getXMLPrimaryKeyName(), appliesTo, idName); } /** * * * @return */ public String getXMLPrimaryKeyName() { return getName() + "_PrimaryKey"; } /** * * * @return */ public XSDType getXsdType() { return xsdType; } /** * * * @param xsdType */ public void setXsdType(final XSDType xsdType) { this.xsdType = xsdType; } /** * * * @return */ public XSDElement getXsdElement() { return xsdElement; } /** * * * @param xsdElement */ public void setXsdElement(final XSDElement xsdElement) { this.xsdElement = xsdElement; } } // WPBEDIT CF2012 EDIT