Repository: cayenne Updated Branches: refs/heads/master fb8660e76 -> 4d8b2e1a6
http://git-wip-us.apache.org/repos/asf/cayenne/blob/4d8b2e1a/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java b/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java index 70b3a9f..11711fc 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java @@ -38,535 +38,579 @@ import org.apache.cayenne.util.XMLSerializable; */ public class PrefetchTreeNode implements Serializable, XMLSerializable { - public static final int UNDEFINED_SEMANTICS = 0; - public static final int JOINT_PREFETCH_SEMANTICS = 1; - public static final int DISJOINT_PREFETCH_SEMANTICS = 2; - public static final int DISJOINT_BY_ID_PREFETCH_SEMANTICS = 3; - - protected String name; - protected boolean phantom; - protected int semantics; - protected String ejbqlPathEntityId; - protected String entityName; - - // transient parent allows cloning parts of the tree via serialization - protected transient PrefetchTreeNode parent; - - // Using Collection instead of Map for children storage (even though there cases of - // lookup by segment) is a reasonable tradeoff considering that - // each node has no more than a few children and lookup by name doesn't happen on - // traversal, only during creation. - protected Collection<PrefetchTreeNode> children; - - /** - * Creates a root node of the prefetch tree. Children can be added to the parent by - * calling "addPath". - */ - public PrefetchTreeNode() { - this(null, null); - } - - /** - * Creates a phantom PrefetchTreeNode, initializing it with parent node and a name of - * a relationship segment connecting this node with the parent. - */ - protected PrefetchTreeNode(PrefetchTreeNode parent, String name) { - this.parent = parent; - this.name = name; - this.phantom = true; - this.semantics = UNDEFINED_SEMANTICS; - } - - public void encodeAsXML(XMLEncoder encoder) { - traverse(new XMLEncoderOperation(encoder)); - } - - /** - * Returns the root of the node tree. Root is the topmost parent node that itself has - * no parent set. - */ - public PrefetchTreeNode getRoot() { - return (parent != null) ? parent.getRoot() : this; - } - - /** - * Returns full prefetch path, that is a dot separated String of node names starting - * from root and up to and including this node. Note that root "name" is considered to - * be an empty string. - */ - public String getPath() { - return getPath(null); - } - - public String getPath(PrefetchTreeNode upTillParent) { - if (parent == null || upTillParent == this) { - return ""; - } - - StringBuilder path = new StringBuilder(getName()); - PrefetchTreeNode node = this.getParent(); - - // root node has no path - while (node.getParent() != null && node != upTillParent) { - path.insert(0, node.getName() + "."); - node = node.getParent(); - } - - return path.toString(); - } - - /** - * Returns a subset of nodes with "joint" semantics that are to be prefetched in the - * same query as the current node. Result excludes this node, regardless of its - * semantics. - */ - public Collection<PrefetchTreeNode> adjacentJointNodes() { - Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); - traverse(new AdjacentJoinsOperation(c)); - return c; - } - - /** - * Returns a collection of PrefetchTreeNodes in this tree with joint semantics. - */ - public Collection<PrefetchTreeNode> jointNodes() { - Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); - traverse(new CollectionBuilderOperation(c, false, false, true, false, false)); - return c; - } - - /** - * Returns a collection of PrefetchTreeNodes with disjoint semantics. - */ - public Collection<PrefetchTreeNode> disjointNodes() { - Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); - traverse(new CollectionBuilderOperation(c, true, false, false, false, false)); - return c; - } - - /** - * Returns a collection of PrefetchTreeNodes with disjoint semantics - * @since 3.1 - */ - public Collection<PrefetchTreeNode> disjointByIdNodes() { - Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); - traverse(new CollectionBuilderOperation(c, false, true, false, false, false)); - return c; - } - - /** - * Returns a collection of PrefetchTreeNodes that are not phantoms. - */ - public Collection<PrefetchTreeNode> nonPhantomNodes() { - Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); - traverse(new CollectionBuilderOperation(c, true, true, true, true, false)); - return c; - } - - /** - * Returns a clone of subtree that includes all joint children - * starting from this node itself and till the first occurrence of non-joint node - * - * @since 3.1 - */ - public PrefetchTreeNode cloneJointSubtree() { - return cloneJointSubtree(null); - } - - private PrefetchTreeNode cloneJointSubtree(PrefetchTreeNode parent) { - PrefetchTreeNode cloned = new PrefetchTreeNode(parent, getName()); - if (parent != null) { - cloned.setSemantics(getSemantics()); - cloned.setPhantom(isPhantom()); - } - - if (children != null) { - for (PrefetchTreeNode child : children) { - if (child.isJointPrefetch()) { - cloned.addChild(child.cloneJointSubtree(cloned)); - } - } - } - - return cloned; - } - - /** - * Traverses the tree depth-first, invoking callback methods of the processor when - * passing through the nodes. - */ - public void traverse(PrefetchProcessor processor) { - - boolean result = false; - - if (isPhantom()) { - result = processor.startPhantomPrefetch(this); - } - else if (isDisjointPrefetch()) { - result = processor.startDisjointPrefetch(this); - } - else if (isDisjointByIdPrefetch()) { - result = processor.startDisjointByIdPrefetch(this); - } - else if (isJointPrefetch()) { - result = processor.startJointPrefetch(this); - } - else { - result = processor.startUnknownPrefetch(this); - } - - // process children unless processing is blocked... - if (result && children != null) { - for (PrefetchTreeNode child : children) { - child.traverse(processor); - } - } - - // call finish regardless of whether children were processed - processor.finishPrefetch(this); - } - - /** - * Looks up an existing node in the tree desribed by the dot-separated path. Will - * return null if no matching child exists. - */ - public PrefetchTreeNode getNode(String path) { - if (Util.isEmptyString(path)) { - throw new IllegalArgumentException("Empty path: " + path); - } - - PrefetchTreeNode node = this; - StringTokenizer toks = new StringTokenizer(path, Entity.PATH_SEPARATOR); - while (toks.hasMoreTokens() && node != null) { - String segment = toks.nextToken(); - node = node.getChild(segment); - } - - return node; - } - - /** - * Adds a "path" with specified semantics to this prefetch node. All yet non-existent - * nodes in the created path will be marked as phantom. - * - * @return the last segment in the created path. - */ - public PrefetchTreeNode addPath(String path) { - if (Util.isEmptyString(path)) { - throw new IllegalArgumentException("Empty path: " + path); - } - - PrefetchTreeNode node = this; - StringTokenizer toks = new StringTokenizer(path, Entity.PATH_SEPARATOR); - while (toks.hasMoreTokens()) { - String segment = toks.nextToken(); - - PrefetchTreeNode child = node.getChild(segment); - if (child == null) { - child = new PrefetchTreeNode(node, segment); - node.addChild(child); - } - - node = child; - } - - return node; - } - - /** - * Removes or makes phantom a node defined by this path. If the node for this path - * doesn't have any children, it is removed, otherwise it is made phantom. - */ - public void removePath(String path) { - - PrefetchTreeNode node = getNode(path); - while (node != null) { - - if (node.children != null) { - node.setPhantom(true); - break; - } - - String segment = node.getName(); - - node = node.getParent(); - - if (node != null) { - node.removeChild(segment); - } - } - } - - public void addChild(PrefetchTreeNode child) { - - if (Util.isEmptyString(child.getName())) { - throw new IllegalArgumentException("Child has no segmentPath: " + child); - } - - if (child.getParent() != this) { - child.getParent().removeChild(child.getName()); - child.parent = this; - } - - if (children == null) { - children = new ArrayList<PrefetchTreeNode>(4); - } - - children.add(child); - } - - public void removeChild(PrefetchTreeNode child) { - if (children != null && child != null) { - children.remove(child); - child.parent = null; - } - } - - protected void removeChild(String segment) { - if (children != null) { - PrefetchTreeNode child = getChild(segment); - if (child != null) { - children.remove(child); - child.parent = null; - } - } - } - - protected PrefetchTreeNode getChild(String segment) { - if (children != null) { - for (PrefetchTreeNode child : children) { - if (segment.equals(child.getName())) { - return child; - } - } - } - - return null; - } - - public PrefetchTreeNode getParent() { - return parent; - } - - /** - * Returns an unmodifiable collection of children. - */ - public Collection<PrefetchTreeNode> getChildren() { - return children == null ? Collections.EMPTY_SET : Collections - .unmodifiableCollection(children); - } - - public boolean hasChildren() { - return children != null && !children.isEmpty(); - } - - public String getName() { - return name; - } - - public boolean isPhantom() { - return phantom; - } - - public void setPhantom(boolean phantom) { - this.phantom = phantom; - } - - public int getSemantics() { - return semantics; - } - - public void setSemantics(int semantics) { - this.semantics = semantics; - } - - public boolean isJointPrefetch() { - return semantics == JOINT_PREFETCH_SEMANTICS; - } - - public boolean isDisjointPrefetch() { - return semantics == DISJOINT_PREFETCH_SEMANTICS; - } - - public boolean isDisjointByIdPrefetch() { - return semantics == DISJOINT_BY_ID_PREFETCH_SEMANTICS; - } - - public String getEjbqlPathEntityId() { - return ejbqlPathEntityId; - } - - public void setEjbqlPathEntityId(String ejbqlPathEntityId) { - this.ejbqlPathEntityId = ejbqlPathEntityId; - } - - public String getEntityName() { - return entityName; - } - - public void setEntityName(String entityName) { - this.entityName = entityName; - } - - - // **** custom serialization that supports serializing subtrees... - - // implementing 'readResolve' instead of 'readObject' so that this would work with - // hessian - private Object readResolve() throws ObjectStreamException { - - if (hasChildren()) { - for (PrefetchTreeNode child : children) { - child.parent = this; - } - } - - return this; - } - - // **** common tree operations - - // An operation that encodes prefetch tree as XML. - class XMLEncoderOperation implements PrefetchProcessor { - - XMLEncoder encoder; - - XMLEncoderOperation(XMLEncoder encoder) { - this.encoder = encoder; - } - - public boolean startPhantomPrefetch(PrefetchTreeNode node) { - // don't encode phantoms - return true; - } - - public boolean startDisjointPrefetch(PrefetchTreeNode node) { - encoder.print("<prefetch type=\"disjoint\">"); - encoder.print(node.getPath()); - encoder.println("</prefetch>"); - return true; - } - - public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) { - encoder.print("<prefetch type=\"disjointById\">"); - encoder.print(node.getPath()); - encoder.println("</prefetch>"); - return true; - } - - public boolean startJointPrefetch(PrefetchTreeNode node) { - encoder.print("<prefetch type=\"joint\">"); - encoder.print(node.getPath()); - encoder.println("</prefetch>"); - return true; - } - - public boolean startUnknownPrefetch(PrefetchTreeNode node) { - encoder.print("<prefetch>"); - encoder.print(node.getPath()); - encoder.println("</prefetch>"); - - return true; - } - - public void finishPrefetch(PrefetchTreeNode node) { - // noop - } - } - - // An operation that collects all nodes in a single collection. - class CollectionBuilderOperation implements PrefetchProcessor { - - Collection<PrefetchTreeNode> nodes; - boolean includePhantom; - boolean includeDisjoint; - boolean includeDisjointById; - boolean includeJoint; - boolean includeUnknown; - - CollectionBuilderOperation(Collection<PrefetchTreeNode> nodes, boolean includeDisjoint, - boolean includeDisjointById, boolean includeJoint, boolean includeUnknown, boolean includePhantom) { - this.nodes = nodes; - - this.includeDisjoint = includeDisjoint; - this.includeDisjointById = includeDisjointById; - this.includeJoint = includeJoint; - this.includeUnknown = includeUnknown; - this.includePhantom = includePhantom; - } - - public boolean startPhantomPrefetch(PrefetchTreeNode node) { - if (includePhantom) { - nodes.add(node); - } - - return true; - } - - public boolean startDisjointPrefetch(PrefetchTreeNode node) { - if (includeDisjoint) { - nodes.add(node); - } - return true; - } - - public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) { - if (includeDisjointById) { - nodes.add(node); - } - return true; - } - - public boolean startJointPrefetch(PrefetchTreeNode node) { - if (includeJoint) { - nodes.add(node); - } - return true; - } - - public boolean startUnknownPrefetch(PrefetchTreeNode node) { - if (includeUnknown) { - nodes.add(node); - } - return true; - } - - public void finishPrefetch(PrefetchTreeNode node) { - } - } - - class AdjacentJoinsOperation implements PrefetchProcessor { - - Collection<PrefetchTreeNode> nodes; - - AdjacentJoinsOperation(Collection<PrefetchTreeNode> nodes) { - this.nodes = nodes; - } - - public boolean startPhantomPrefetch(PrefetchTreeNode node) { - return true; - } - - public boolean startDisjointPrefetch(PrefetchTreeNode node) { - return node == PrefetchTreeNode.this; - } - - public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) { - return startDisjointPrefetch(node); - } - - public boolean startJointPrefetch(PrefetchTreeNode node) { - if (node != PrefetchTreeNode.this) { - nodes.add(node); - } - return true; - } - - public boolean startUnknownPrefetch(PrefetchTreeNode node) { - return node == PrefetchTreeNode.this; - } - - public void finishPrefetch(PrefetchTreeNode node) { - } - } + private static final long serialVersionUID = 1112629504025820837L; + + public static final int UNDEFINED_SEMANTICS = 0; + public static final int JOINT_PREFETCH_SEMANTICS = 1; + public static final int DISJOINT_PREFETCH_SEMANTICS = 2; + public static final int DISJOINT_BY_ID_PREFETCH_SEMANTICS = 3; + + protected String name; + protected boolean phantom; + protected int semantics; + protected String ejbqlPathEntityId; + protected String entityName; + + // transient parent allows cloning parts of the tree via serialization + protected transient PrefetchTreeNode parent; + + // Using Collection instead of Map for children storage (even though there + // cases of + // lookup by segment) is a reasonable tradeoff considering that + // each node has no more than a few children and lookup by name doesn't + // happen on + // traversal, only during creation. + protected Collection<PrefetchTreeNode> children; + + /** + * Creates a root node of the prefetch tree. Children can be added to the + * parent by calling "addPath". + */ + public PrefetchTreeNode() { + this(null, null); + } + + /** + * Creates a phantom PrefetchTreeNode, initializing it with parent node and + * a name of a relationship segment connecting this node with the parent. + */ + protected PrefetchTreeNode(PrefetchTreeNode parent, String name) { + this.parent = parent; + this.name = name; + this.phantom = true; + this.semantics = UNDEFINED_SEMANTICS; + } + + public void encodeAsXML(XMLEncoder encoder) { + traverse(new XMLEncoderOperation(encoder)); + } + + /** + * Returns the root of the node tree. Root is the topmost parent node that + * itself has no parent set. + */ + public PrefetchTreeNode getRoot() { + return (parent != null) ? parent.getRoot() : this; + } + + /** + * Returns full prefetch path, that is a dot separated String of node names + * starting from root and up to and including this node. Note that root + * "name" is considered to be an empty string. + */ + public String getPath() { + return getPath(null); + } + + public String getPath(PrefetchTreeNode upTillParent) { + if (parent == null || upTillParent == this) { + return ""; + } + + StringBuilder path = new StringBuilder(getName()); + PrefetchTreeNode node = this.getParent(); + + // root node has no path + while (node.getParent() != null && node != upTillParent) { + path.insert(0, node.getName() + "."); + node = node.getParent(); + } + + return path.toString(); + } + + /** + * Returns a subset of nodes with "joint" semantics that are to be + * prefetched in the same query as the current node. Result excludes this + * node, regardless of its semantics. + */ + public Collection<PrefetchTreeNode> adjacentJointNodes() { + Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); + traverse(new AdjacentJoinsOperation(c)); + return c; + } + + /** + * Returns a collection of PrefetchTreeNodes in this tree with joint + * semantics. + */ + public Collection<PrefetchTreeNode> jointNodes() { + Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); + traverse(new CollectionBuilderOperation(c, false, false, true, false, false)); + return c; + } + + /** + * Returns a collection of PrefetchTreeNodes with disjoint semantics. + */ + public Collection<PrefetchTreeNode> disjointNodes() { + Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); + traverse(new CollectionBuilderOperation(c, true, false, false, false, false)); + return c; + } + + /** + * Returns a collection of PrefetchTreeNodes with disjoint semantics + * + * @since 3.1 + */ + public Collection<PrefetchTreeNode> disjointByIdNodes() { + Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); + traverse(new CollectionBuilderOperation(c, false, true, false, false, false)); + return c; + } + + /** + * Returns a collection of PrefetchTreeNodes that are not phantoms. + */ + public Collection<PrefetchTreeNode> nonPhantomNodes() { + Collection<PrefetchTreeNode> c = new ArrayList<PrefetchTreeNode>(); + traverse(new CollectionBuilderOperation(c, true, true, true, true, false)); + return c; + } + + /** + * Returns a clone of subtree that includes all joint children starting from + * this node itself and till the first occurrence of non-joint node + * + * @since 3.1 + */ + public PrefetchTreeNode cloneJointSubtree() { + return cloneJointSubtree(null); + } + + private PrefetchTreeNode cloneJointSubtree(PrefetchTreeNode parent) { + PrefetchTreeNode cloned = new PrefetchTreeNode(parent, getName()); + if (parent != null) { + cloned.setSemantics(getSemantics()); + cloned.setPhantom(isPhantom()); + } + + if (children != null) { + for (PrefetchTreeNode child : children) { + if (child.isJointPrefetch()) { + cloned.addChild(child.cloneJointSubtree(cloned)); + } + } + } + + return cloned; + } + + /** + * Traverses the tree depth-first, invoking callback methods of the + * processor when passing through the nodes. + */ + public void traverse(PrefetchProcessor processor) { + + boolean result = false; + + if (isPhantom()) { + result = processor.startPhantomPrefetch(this); + } else if (isDisjointPrefetch()) { + result = processor.startDisjointPrefetch(this); + } else if (isDisjointByIdPrefetch()) { + result = processor.startDisjointByIdPrefetch(this); + } else if (isJointPrefetch()) { + result = processor.startJointPrefetch(this); + } else { + result = processor.startUnknownPrefetch(this); + } + + // process children unless processing is blocked... + if (result && children != null) { + for (PrefetchTreeNode child : children) { + child.traverse(processor); + } + } + + // call finish regardless of whether children were processed + processor.finishPrefetch(this); + } + + /** + * Looks up an existing node in the tree desribed by the dot-separated path. + * Will return null if no matching child exists. + */ + public PrefetchTreeNode getNode(String path) { + if (Util.isEmptyString(path)) { + throw new IllegalArgumentException("Empty path: " + path); + } + + PrefetchTreeNode node = this; + StringTokenizer toks = new StringTokenizer(path, Entity.PATH_SEPARATOR); + while (toks.hasMoreTokens() && node != null) { + String segment = toks.nextToken(); + node = node.getChild(segment); + } + + return node; + } + + /** + * Adds a "path" with specified semantics to this prefetch node. All yet + * non-existent nodes in the created path will be marked as phantom. + * + * @return the last segment in the created path. + */ + public PrefetchTreeNode addPath(String path) { + if (Util.isEmptyString(path)) { + throw new IllegalArgumentException("Empty path: " + path); + } + + PrefetchTreeNode node = this; + StringTokenizer toks = new StringTokenizer(path, Entity.PATH_SEPARATOR); + while (toks.hasMoreTokens()) { + String segment = toks.nextToken(); + + PrefetchTreeNode child = node.getChild(segment); + if (child == null) { + child = new PrefetchTreeNode(node, segment); + node.addChild(child); + } + + node = child; + } + + return node; + } + + /** + * Merges {@link PrefetchTreeNode} into the current prefetch tree, cloning + * the nodes added to this tree. Merged nodes semantics (if defined) and + * non-phantom status are applied to the nodes of this tree. + * + * @param node + * a root node of a tree to merge into this tree. The path of the + * merged node within the resulting tree is determined from its + * name. + * + * @since 4.0 + */ + public void merge(PrefetchTreeNode node) { + if (node == null) { + throw new NullPointerException("Null node"); + } + + PrefetchTreeNode start = node.getName() != null ? addPath(node.getName()) : this; + merge(start, node); + } + + void merge(PrefetchTreeNode original, PrefetchTreeNode toMerge) { + + if (toMerge.getSemantics() != UNDEFINED_SEMANTICS) { + original.setSemantics(toMerge.getSemantics()); + } + + if (!toMerge.isPhantom()) { + original.setPhantom(false); + } + + for (PrefetchTreeNode childToMerge : toMerge.getChildren()) { + + PrefetchTreeNode childOrigin = original.getChild(childToMerge.getName()); + if (childOrigin == null) { + childOrigin = original.addPath(childToMerge.getName()); + } + + merge(childOrigin, childToMerge); + } + } + + /** + * Removes or makes phantom a node defined by this path. If the node for + * this path doesn't have any children, it is removed, otherwise it is made + * phantom. + */ + public void removePath(String path) { + + PrefetchTreeNode node = getNode(path); + while (node != null) { + + if (node.children != null) { + node.setPhantom(true); + break; + } + + String segment = node.getName(); + + node = node.getParent(); + + if (node != null) { + node.removeChild(segment); + } + } + } + + public void addChild(PrefetchTreeNode child) { + + if (Util.isEmptyString(child.getName())) { + throw new IllegalArgumentException("Child has no segmentPath: " + child); + } + + if (child.getParent() != this) { + child.getParent().removeChild(child.getName()); + child.parent = this; + } + + if (children == null) { + children = new ArrayList<PrefetchTreeNode>(4); + } + + children.add(child); + } + + public void removeChild(PrefetchTreeNode child) { + if (children != null && child != null) { + children.remove(child); + child.parent = null; + } + } + + protected void removeChild(String segment) { + if (children != null) { + PrefetchTreeNode child = getChild(segment); + if (child != null) { + children.remove(child); + child.parent = null; + } + } + } + + protected PrefetchTreeNode getChild(String segment) { + if (children != null) { + for (PrefetchTreeNode child : children) { + if (segment.equals(child.getName())) { + return child; + } + } + } + + return null; + } + + public PrefetchTreeNode getParent() { + return parent; + } + + /** + * Returns an unmodifiable collection of children. + */ + public Collection<PrefetchTreeNode> getChildren() { + return children == null ? Collections.<PrefetchTreeNode> emptySet() : children; + } + + public boolean hasChildren() { + return children != null && !children.isEmpty(); + } + + public String getName() { + return name; + } + + public boolean isPhantom() { + return phantom; + } + + public void setPhantom(boolean phantom) { + this.phantom = phantom; + } + + public int getSemantics() { + return semantics; + } + + public void setSemantics(int semantics) { + this.semantics = semantics; + } + + public boolean isJointPrefetch() { + return semantics == JOINT_PREFETCH_SEMANTICS; + } + + public boolean isDisjointPrefetch() { + return semantics == DISJOINT_PREFETCH_SEMANTICS; + } + + public boolean isDisjointByIdPrefetch() { + return semantics == DISJOINT_BY_ID_PREFETCH_SEMANTICS; + } + + public String getEjbqlPathEntityId() { + return ejbqlPathEntityId; + } + + public void setEjbqlPathEntityId(String ejbqlPathEntityId) { + this.ejbqlPathEntityId = ejbqlPathEntityId; + } + + public String getEntityName() { + return entityName; + } + + public void setEntityName(String entityName) { + this.entityName = entityName; + } + + // **** custom serialization that supports serializing subtrees... + + // implementing 'readResolve' instead of 'readObject' so that this would + // work with + // hessian + private Object readResolve() throws ObjectStreamException { + + if (hasChildren()) { + for (PrefetchTreeNode child : children) { + child.parent = this; + } + } + + return this; + } + + // **** common tree operations + + // An operation that encodes prefetch tree as XML. + class XMLEncoderOperation implements PrefetchProcessor { + + XMLEncoder encoder; + + XMLEncoderOperation(XMLEncoder encoder) { + this.encoder = encoder; + } + + public boolean startPhantomPrefetch(PrefetchTreeNode node) { + // don't encode phantoms + return true; + } + + public boolean startDisjointPrefetch(PrefetchTreeNode node) { + encoder.print("<prefetch type=\"disjoint\">"); + encoder.print(node.getPath()); + encoder.println("</prefetch>"); + return true; + } + + public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) { + encoder.print("<prefetch type=\"disjointById\">"); + encoder.print(node.getPath()); + encoder.println("</prefetch>"); + return true; + } + + public boolean startJointPrefetch(PrefetchTreeNode node) { + encoder.print("<prefetch type=\"joint\">"); + encoder.print(node.getPath()); + encoder.println("</prefetch>"); + return true; + } + + public boolean startUnknownPrefetch(PrefetchTreeNode node) { + encoder.print("<prefetch>"); + encoder.print(node.getPath()); + encoder.println("</prefetch>"); + + return true; + } + + public void finishPrefetch(PrefetchTreeNode node) { + // noop + } + } + + // An operation that collects all nodes in a single collection. + class CollectionBuilderOperation implements PrefetchProcessor { + + Collection<PrefetchTreeNode> nodes; + boolean includePhantom; + boolean includeDisjoint; + boolean includeDisjointById; + boolean includeJoint; + boolean includeUnknown; + + CollectionBuilderOperation(Collection<PrefetchTreeNode> nodes, boolean includeDisjoint, + boolean includeDisjointById, boolean includeJoint, boolean includeUnknown, boolean includePhantom) { + this.nodes = nodes; + + this.includeDisjoint = includeDisjoint; + this.includeDisjointById = includeDisjointById; + this.includeJoint = includeJoint; + this.includeUnknown = includeUnknown; + this.includePhantom = includePhantom; + } + + public boolean startPhantomPrefetch(PrefetchTreeNode node) { + if (includePhantom) { + nodes.add(node); + } + + return true; + } + + public boolean startDisjointPrefetch(PrefetchTreeNode node) { + if (includeDisjoint) { + nodes.add(node); + } + return true; + } + + public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) { + if (includeDisjointById) { + nodes.add(node); + } + return true; + } + + public boolean startJointPrefetch(PrefetchTreeNode node) { + if (includeJoint) { + nodes.add(node); + } + return true; + } + + public boolean startUnknownPrefetch(PrefetchTreeNode node) { + if (includeUnknown) { + nodes.add(node); + } + return true; + } + + public void finishPrefetch(PrefetchTreeNode node) { + } + } + + class AdjacentJoinsOperation implements PrefetchProcessor { + + Collection<PrefetchTreeNode> nodes; + + AdjacentJoinsOperation(Collection<PrefetchTreeNode> nodes) { + this.nodes = nodes; + } + + public boolean startPhantomPrefetch(PrefetchTreeNode node) { + return true; + } + + public boolean startDisjointPrefetch(PrefetchTreeNode node) { + return node == PrefetchTreeNode.this; + } + + public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) { + return startDisjointPrefetch(node); + } + + public boolean startJointPrefetch(PrefetchTreeNode node) { + if (node != PrefetchTreeNode.this) { + nodes.add(node); + } + return true; + } + + public boolean startUnknownPrefetch(PrefetchTreeNode node) { + return node == PrefetchTreeNode.this; + } + + public void finishPrefetch(PrefetchTreeNode node) { + } + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/4d8b2e1a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java index 8046a8c..b7a4fdf 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java @@ -588,11 +588,8 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery, * * @since 4.0 */ - public PrefetchTreeNode addPrefetch(PrefetchTreeNode prefetchElement) { - String path = prefetchElement.getPath(); - int semantics = prefetchElement.getSemantics(); - - return metaData.addPrefetch(path, semantics); + public void addPrefetch(PrefetchTreeNode prefetchElement) { + metaData.mergePrefetch(prefetchElement); } /** http://git-wip-us.apache.org/repos/asf/cayenne/blob/4d8b2e1a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextDisjointByIdPrefetch_ExtrasIT.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextDisjointByIdPrefetch_ExtrasIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextDisjointByIdPrefetch_ExtrasIT.java index 9b84973..daa9acd 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextDisjointByIdPrefetch_ExtrasIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextDisjointByIdPrefetch_ExtrasIT.java @@ -347,6 +347,7 @@ public class DataContextDisjointByIdPrefetch_ExtrasIT extends ServerCase { queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() { + @Override public void execute() { assertFalse(result.isEmpty());