/*******************************************************************************
* Copyright (c) 2004, 2007 IBM Corporation and Cambridge Semantics Incorporated.
* 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
*
* File: $Source: /cvsroot/slrp/glitter/com.ibm.adtech.glitter/src/com/ibm/adtech/glitter/syntax/abstrakt/TreeNode.java,v $
* Created by: Lee Feigenbaum (<a href="mailto:feigenbl@us.ibm.com">feigenbl@us.ibm.com</a>)
* Created on: 10/23/06
* Revision: $Id: TreeNode.java 164 2007-07-31 14:11:09Z mroy $
*
* Contributors: IBM Corporation - initial API and implementation
* Cambridge Semantics Incorporated - Fork to Anzo
*******************************************************************************/
package org.openanzo.glitter.syntax.abstrakt;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.collections.CollectionUtils;
import org.openanzo.glitter.query.QueryPart;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Variable;
import org.openanzo.rdf.utils.NullIterator;
import org.openanzo.rdf.utils.PrettyPrintable;
/**
* A TreeNode is the (abstract) base class for all nodes in the abstract syntax of a SPARQL query.
*
* TreeNodes also maintain a cache of properties calculated recursively over a node and its descendants.
*
* @author Lee Feigenbaum <lee@cambridgesemantics.com>
*
*/
public abstract class TreeNode implements PrettyPrintable, QueryPart {
static private final String varsetKey = "varset";
static private final String bindableVarsetKey = "bindable-varset";
static private final String urisKey = "uris";
static private final String inScopeFilterSetKey = "filterset";
static final protected Iterator<TreeNode> emptyIterator = new NullIterator<TreeNode>();
private TreeNode parent = null;
private final HashMap<String, Object> cache = new HashMap<String, Object>();
private final void clearCacheData(String key) {
if (key == null)
this.cache.clear();
else
this.cache.remove(key);
}
/**
* Clear the cache corresponding to the given <tt>key</tt>
*
* @param key
*/
private final void invalidateCache(String key) {
for (TreeNode node = this; node != null; node = node.getParent())
node.clearCacheData(key);
}
/**
* Invalidate the entire cache.
*/
public final void invalidateCache() {
invalidateCache((String) null);
}
/**
* Set the parent of this node
*
* @param p
* parent node
*/
public void setParent(TreeNode p) {
// Currently, all mutations of the tree touch on this point,
// so we can safely just put our calls to invalidate the cache
// here
this.parent = p;
if (p != null)
p.invalidateCache();
else
// setting this node as the root - do we need to invalidate the cache?
invalidateCache();
}
/**
* @return The parent of this {@link TreeNode}. If this is the root of a query, returns <tt>null</tt>.
*/
public TreeNode getParent() {
return this.parent;
}
/**
* Find all the ancestors for this node
*
* @param <T>
* type of ancestors to find
* @param clazz
* ancestor type
* @return ancestors of a certain type
*/
public final <T extends TreeNode> List<T> findAncestors(Class<T> clazz) {
List<T> matches = new ArrayList<T>();
TreeNode current = this.getParent();
while (current != null) {
if (clazz.isInstance(current)) {
matches.add(clazz.cast(current));
}
current = current.getParent();
}
return matches;
}
/**
*
* @return An {@link Iterator} over the children of this node.
*/
public abstract List<? extends TreeNode> getChildren();
/**
* Clone the tree node to produce an equivalent node that does not share references.
*
* @return the clone
*/
@Override
public abstract TreeNode clone();
/**
* Changes the tree below this node by replacing <tt>oldChild</tt> with <tt>newChild</tt>
*
* @param oldChild
* @param newChild
* @return <tt>true</tt> if the <tt>oldChild</tt> was found and replaced; <tt>false</tt> otherwise.
*/
public abstract boolean replaceChild(TreeNode oldChild, TreeNode newChild);
/**
* Removes the given <tt>child</tt> from the collection of children
*
* @param child
* @return <tt>true</tt> if the <tt>child</tt> was found and removed; <tt>false</tt> otherwise.
*/
public abstract boolean removeChild(TreeNode child);
/**
* Adds the given <tt>child</tt> at the end of this node's children.
*
* @param child
*/
public abstract void addChild(TreeNode child);
/**
* @param v
* @return whether or not this node and its subtree contains {@link Variable} <tt>v</tt>.
*/
final public boolean containsVariable(Variable v) {
return containsVariable(v, false);
}
/**
* @param v
* @return whether or not this node and its subtree contains {@link Variable} <tt>v</tt> in a position that it could receive bindings (e.g., a variable in a
* FILTER cannot receive bindings, but a variable in the GRAPH clause can.
*/
final public boolean mightBindVariable(Variable v) {
return containsVariable(v, true);
}
final private boolean containsVariable(Variable v, boolean canAddBindings) {
Integer c = getVariableCount(canAddBindings).get(v);
return c != null && c > 0;
}
@SuppressWarnings("unchecked")
protected Map<Variable, Integer> getVariableCount(boolean onlyBindableVariables) {
String k = onlyBindableVariables ? bindableVarsetKey : varsetKey;
Object o = this.cache.get(k);
if (o != null)
return (Map<Variable, Integer>) o;
HashMap<Variable, Integer> vars = new HashMap<Variable, Integer>();
for (TreeNode n: getChildren()) {
if (n != null) {
Map<Variable, Integer> childVars = n.getVariableCount(onlyBindableVariables);
for (Entry<Variable, Integer> e : childVars.entrySet())
incrementVariableCount(vars, e.getKey(), e.getValue());
}
}
this.cache.put(k, vars);
return vars;
}
@SuppressWarnings("unchecked")
public Collection<URI> getReferencedURIs() {
Object o = this.cache.get(TreeNode.urisKey);
if (o != null)
return (Collection<URI>) o;
HashSet<URI> uris = new HashSet<URI>();
for (TreeNode n: getChildren()) {
if (n != null)
uris.addAll(n.getReferencedURIs());
}
this.cache.put(TreeNode.urisKey, uris);
return uris;
}
static protected void incrementVariableCount(Map<Variable, Integer> m, Variable v, int incrementBy) {
Integer current = m.get(v);
m.put(v, (current != null ? current : 0) + incrementBy);
}
/**
* @return A map from variables to counts for all variables that appear in this node and its descendants.
*/
final public Map<Variable, Integer> getVariableCount() {
return getVariableCount(false);
}
/**
* @return A map from variables to counts for all variables that appear in this node and its descendants. This only counts instances of {@link Variable}s
* that can receive bindings. (As in {@link #mightBindVariable(Variable)}.
*/
final public Map<Variable, Integer> getBindableVariableCount() {
return getVariableCount(true);
}
/**
* @return A set of all variables that occur in this node and its descendants.
*/
final public Set<Variable> getReferencedVariables() {
return getVariableCount().keySet();
}
/**
* @return A set of all variables that occur in this node and its descendants that may receive bindings.
*/
final public Set<Variable> getBindableVariables() {
return getBindableVariableCount().keySet();
}
/**
*
* @return The root of the tree that contains this {@link TreeNode}.
*/
final public TreeNode getRoot() {
TreeNode root = this;
while (root.parent != null)
root = root.parent;
return root;
}
/**
*
* @return A set of all filters {@link Expression}s that are in scope (act on the bindings) for this tree node.
*/
@SuppressWarnings("unchecked")
final public Set<Expression> getInScopeFilterSet() {
Object o = this.cache.get(inScopeFilterSetKey);
if (o != null)
return (Set<Expression>) o;
HashSet<Expression> filters = new HashSet<Expression>();
CollectionUtils.addAll(filters, this.getFilters().iterator());
if (this.getParent() != null)
filters.addAll(this.getParent().getInScopeFilterSet());
this.cache.put(inScopeFilterSetKey, filters);
return filters;
}
/**
* Default implementation of {@link #getFilters()}. Most tree nodes cannot contain filters.
*
* @return Default implementation of {@link #getFilters()}.
*/
public Set<Expression> getFilters() {
return new HashSet<Expression>();
}
/**
* Pretty prints this tree node to the given {@link StringBuffer}.
*
* @param output
*/
public void prettyPrint(StringBuilder output) {
this.prettyPrint(output, true);
}
/**
* Pretty prints this tree node to the given {@link StringBuffer}.
*
* @param output
* @param deep
* If <tt>true</tt>, recurse into descendants. Otherwise, includes an elipsis ("...") for any children.
*/
public void prettyPrint(StringBuilder output, boolean deep) {
output.append(this.getClass().getSimpleName());
output.append("(");
List<? extends TreeNode> it = getChildren();
if (prettyPrintParams(output) && deep) {
if (it.size()>0)
output.append(",");
output.append(" ");
}
if (deep) {
int i = 0;
for (TreeNode tn:getChildren()) {
if (i++ > 0)
output.append(", ");
if (tn == null)
output.append("null");
else
tn.prettyPrint(output);
}
} else {
output.append("...");
}
output.append(")");
}
protected boolean prettyPrintParams(StringBuilder output) {
return false;
}
}