org.apache.cayenne.query.PrefetchTreeNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cayenne-client-nodeps
Show all versions of cayenne-client-nodeps
Cayenne Object Persistence Framework
/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
****************************************************************/
package org.apache.cayenne.query;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.StringTokenizer;
import org.apache.cayenne.map.Entity;
import org.apache.cayenne.util.Util;
import org.apache.cayenne.util.XMLEncoder;
import org.apache.cayenne.util.XMLSerializable;
/**
* Defines a node in a prefetch tree.
*
* @since 1.2
* @author Andrus Adamchik
*/
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;
protected String name;
protected boolean phantom;
protected int semantics;
// 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 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 segmentPath) {
this.parent = parent;
this.name = segmentPath;
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 "";
}
StringBuffer path = new StringBuffer(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 adjacentJointNodes() {
Collection c = new ArrayList();
traverse(new AdjacentJoinsOperation(c));
return c;
}
/**
* Returns a collection of PrefetchTreeNodes in this tree with joint semantics.
*/
public Collection jointNodes() {
Collection c = new ArrayList();
traverse(new CollectionBuilderOperation(c, false, true, false, false));
return c;
}
/**
* Returns a collection of PrefetchTreeNodes with disjoint semantics.
*/
public Collection disjointNodes() {
Collection c = new ArrayList();
traverse(new CollectionBuilderOperation(c, true, false, false, false));
return c;
}
/**
* Returns a collection of PrefetchTreeNodes that are not phantoms.
*/
public Collection nonPhantomNodes() {
Collection c = new ArrayList();
traverse(new CollectionBuilderOperation(c, true, true, true, false));
return c;
}
/**
* 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 (isJointPrefetch()) {
result = processor.startJointPrefetch(this);
}
else {
result = processor.startUnknownPrefetch(this);
}
// process children unless processing is blocked...
if (result && children != null) {
Iterator it = children.iterator();
while (it.hasNext()) {
((PrefetchTreeNode) it.next()).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(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) {
Iterator it = children.iterator();
while (it.hasNext()) {
PrefetchTreeNode next = (PrefetchTreeNode) it.next();
if (segment.equals(next.getName())) {
return next;
}
}
}
return null;
}
public PrefetchTreeNode getParent() {
return parent;
}
/**
* Returns an unmodifiable collection of children.
*/
public Collection 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;
}
// **** 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()) {
Iterator it = children.iterator();
while (it.hasNext()) {
PrefetchTreeNode child = (PrefetchTreeNode) it.next();
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("");
encoder.print(node.getPath());
encoder.println(" ");
return true;
}
public boolean startJointPrefetch(PrefetchTreeNode node) {
encoder.print("");
encoder.print(node.getPath());
encoder.println(" ");
return true;
}
public boolean startUnknownPrefetch(PrefetchTreeNode node) {
encoder.print("");
encoder.print(node.getPath());
encoder.println(" ");
return true;
}
public void finishPrefetch(PrefetchTreeNode node) {
// noop
}
}
// An operation that collects all nodes in a single collection.
class CollectionBuilderOperation implements PrefetchProcessor {
Collection nodes;
boolean includePhantom;
boolean includeDisjoint;
boolean includeJoint;
boolean includeUnknown;
CollectionBuilderOperation(Collection nodes, boolean includeDisjoint,
boolean includeJoint, boolean includeUnknown, boolean includePhantom) {
this.nodes = nodes;
this.includeDisjoint = includeDisjoint;
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 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 nodes;
AdjacentJoinsOperation(Collection nodes) {
this.nodes = nodes;
}
public boolean startPhantomPrefetch(PrefetchTreeNode node) {
return true;
}
public boolean startDisjointPrefetch(PrefetchTreeNode node) {
return node == PrefetchTreeNode.this;
}
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) {
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy