/*******************************************************************************
* 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/Group.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: Group.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.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.collections15.MultiMap;
import org.apache.commons.collections15.multimap.MultiHashMap;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.glitter.exception.GlitterRuntimeException;
import org.openanzo.glitter.query.QueryController;
import org.openanzo.glitter.query.QueryController.QueryStringPrintOptions;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Variable;
/**
* A group represents an ordered collection of graph patterns delimited by curly braces in a SPARQL query. A group also contains a set of filters that apply to
* the bindings generated from the patterns within the group.
*
* @author lee <lee@cambridgesemantics.com>
*
*/
public class Group extends GraphPattern {
private ArrayList<GraphPattern> patterns;
private HashSet<Expression> filters;
private MultiHashMap<Variable, Expression> assignments;
@Override
public Group clone() {
ArrayList<GraphPattern> gps = new ArrayList<GraphPattern>();
for (GraphPattern gp : this.patterns)
gps.add((GraphPattern) gp.clone());
return new Group(gps, this.filters, this.assignments);
}
/**
* Default constructor.
*/
public Group() {
this.patterns = new ArrayList<GraphPattern>();
this.filters = new HashSet<Expression>();
this.assignments = new MultiHashMap<Variable, Expression>();
}
/**
* Construct a group from a list of patterns and a set of filter expressions.
*
* @param patterns
* @param filters
* @param assignments
*/
@SuppressWarnings("unchecked")
public Group(ArrayList<GraphPattern> patterns, HashSet<Expression> filters, MultiHashMap<Variable, Expression> assignments) {
this.patterns = new ArrayList<GraphPattern>();
for (GraphPattern gp : patterns) {
this.patterns.add((GraphPattern) gp.clone());
}
this.filters = (HashSet<Expression>) filters.clone();
this.assignments = (MultiHashMap<Variable, Expression>) assignments.clone();
for (GraphPattern gp : this.patterns)
gp.setParent(this);
}
/**
* Construct a group with no filters from a list of graph patterns.
*
* @param patterns
* graph patterns to add to group
*/
public Group(ArrayList<GraphPattern> patterns) {
this.patterns = new ArrayList<GraphPattern>();
for (GraphPattern gp : patterns) {
this.patterns.add((GraphPattern) gp.clone());
}
this.filters = new HashSet<Expression>();
this.assignments = new MultiHashMap<Variable, Expression>();
for (GraphPattern gp : this.patterns)
gp.setParent(this);
}
/**
* Remove the given filter from this group.
*
* @param f
* expression to remove from group
* @return true if given filter was removed from this group.
*/
public boolean removeFilter(Expression f) {
invalidateCache();
return this.filters.remove(f);
}
/**
* Remove all filters from this group.
*/
private void removeAllFilters() {
invalidateCache();
this.filters.clear();
}
/**
* Add the given filter to this group.
*
* @param f
* expression to add group
*/
public void addFilter(Expression f) {
this.filters.add(f);
invalidateCache();
}
@Override
public Set<Expression> getFilters() {
return this.filters;
}
/**
* Remove the given assignment (variable, expression) pair from this group.
*
* @param v
* @param e
*
* @return true if given assignment was removed from this group.
*/
public boolean removeAssignment(Variable v, Expression e) {
invalidateCache();
return this.assignments.remove(v, e) != null;
}
/**
* Removes all assignments to a given variable attached to this group
*
* @param v
* @return true if any assignments were removed from this group
*/
public boolean removeVariableAssignments(Variable v) {
invalidateCache();
return this.assignments.remove(v) != null;
}
/**
* Remove all assignments from this group.
*/
private void removeAllAssignments() {
invalidateCache();
this.assignments.clear();
}
/**
* Add the given assignment to this group.
*
* @param v
* @param e
* the variable and expression to add as an assignment
*/
public void addAssignment(Variable v, Expression e) {
invalidateCache();
this.assignments.put(v, e);
}
/**
*
* @return The assignments associated with this group.
*/
public MultiMap<Variable, Expression> getAssignments() {
return this.assignments;
}
/**
* Removes the given graph pattern from this group.
*
* @param gp
* graph pattern to remove from group
* @return true if given patern was removed from the group
*/
public boolean removeGraphPattern(GraphPattern gp) {
boolean b = this.patterns.remove(gp);
if (b)
gp.setParent(null);
return b;
}
/**
* Adds the given graph pattern to this group.
*
* @param gp
* graph pattern to add to group
*/
public void addGraphPattern(GraphPattern gp) {
this.patterns.add(gp);
gp.setParent(this);
}
/**
* Add all the graph patterns from another {@link Group} to this group.
*
* @param other
* source group from which all patterns will be added to this group
*/
public void addPatternsFrom(Group other) {
for (GraphPattern gp : other.getPatterns())
this.addGraphPattern(gp);
}
/**
* Get an iterable collection of this group's graph patterns
*
* @return an iterable collection of this group's graph patterns
*/
public Collection<GraphPattern> getPatterns() {
return this.patterns;
}
/**
* Transforms the current patterns that comprise this group into the first parameter to an {@link Optional} construct, with <tt>mayMatch</tt> as the second.
* Replaces those patterns with the optional as the only pattern in this group.
*
* @param mayMatch
* The optional part of the new optional.
*/
public void replaceCurrentContentsWithOptional(Group mayMatch) {
/*
Optional opt = null;
if (this.patterns.size() > 0) {
opt = new Optional(this.patterns.get(this.patterns.size() - 1), mayMatch, mayMatch.getFilters());
this.patterns.remove(this.patterns.size() - 1);
mayMatch.removeAllFilters();
} else {
opt = new Optional(mayMatch);
}
addGraphPattern(opt);
*/
// if we don't have any patterns yet, just add in an optional
Optional opt = null;
if (this.patterns.size() == 0) {
opt = new Optional(mayMatch);
} else {
// filters & assignments must stay attached to the current group, not to the group inside the optional
Group mustMatch = this.clone();
mustMatch.removeAllFilters();
mustMatch.removeAllAssignments();
opt = new Optional(mustMatch, mayMatch, mayMatch.getFilters());
mayMatch.removeAllFilters();
this.patterns = new ArrayList<GraphPattern>();
}
// add in the optional
addGraphPattern(opt);
}
public void prettyPrintQueryPart(EnumSet<QueryStringPrintOptions> printFlags, int indentLevel, Map<String, String> uri2prefix, StringBuilder s) {
for (int i = 0; i < this.patterns.size(); i++) {
if (i > 0) {
if (patterns.get(i - 1) instanceof BGP) {
s.append(". ");
}
QueryController.printSeparator(printFlags, indentLevel, s);
}
GraphPattern gp = this.patterns.get(i);
if (gp instanceof Group) {
s.append("{");
indentLevel++;
QueryController.printSeparator(printFlags, indentLevel, s);
}
gp.prettyPrintQueryPart(printFlags, indentLevel, uri2prefix, s);
if (gp instanceof Group) {
indentLevel--;
QueryController.printSeparator(printFlags, indentLevel, s);
s.append("}");
}
}
if (assignments.size() > 0) {
for (Entry<Variable, Collection<Expression>> assignment : assignments.entrySet()) {
Variable v = assignment.getKey();
for (Expression e : assignment.getValue()) {
QueryController.printSeparator(printFlags, indentLevel, s);
s.append(" LET (");
s.append(v.toString());
s.append(" := ");
e.prettyPrintQueryPart(printFlags, indentLevel, uri2prefix, s);
s.append(")");
}
}
}
if (filters.size() > 0) {
for (Expression filter : filters) {
QueryController.printSeparator(printFlags, indentLevel, s);
s.append("FILTER (");
filter.prettyPrintQueryPart(printFlags, indentLevel, uri2prefix, s);
s.append(")");
}
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
//builder.append("{ ");
for (int i = 0; i < this.patterns.size(); i++) {
if (i > 0)
builder.append(" ");
GraphPattern gp = this.patterns.get(i);
if (gp instanceof Group)
builder.append("{ ");
builder.append(gp);
if (gp instanceof Group)
builder.append(" }");
}
if (assignments.size() > 0) {
for (Entry<Variable, Collection<Expression>> assignment : assignments.entrySet()) {
Variable v = assignment.getKey();
for (Expression e : assignment.getValue()) {
builder.append(" LET (");
builder.append(v.toString());
builder.append(" := ");
builder.append(e.toString());
builder.append(")");
}
}
}
if (filters.size() > 0) {
for (Expression filter : filters) {
builder.append(" FILTER (");
builder.append(filter.toString());
builder.append(")");
}
}
//builder.append(" }");
return builder.toString();
}
@Override
public List<GraphPattern> getChildren() {
return this.patterns;
}
@Override
public boolean replaceChild(TreeNode oldChild, TreeNode newChild) {
if (newChild instanceof GraphPattern) {
for (ListIterator<GraphPattern> it = this.patterns.listIterator(); it.hasNext();) {
GraphPattern cur = it.next();
if (oldChild == cur) {
oldChild.setParent(null);
newChild.setParent(this);
it.set((GraphPattern) newChild);
return true;
}
}
}
return false;
}
@Override
protected Map<Variable, Integer> getVariableCount(boolean onlyBindableVariables) {
Map<Variable, Integer> vars = super.getVariableCount(onlyBindableVariables);
// include variables on the LHS of LET-expressions
for (Variable v : this.assignments.keySet())
incrementVariableCount(vars, v, 1);
// if we want all variables, regardless of whether they can be bound to,
// we also include variables occuring inside filters and the RHS of LET-expressions
if (!onlyBindableVariables) {
for (Expression e : this.filters)
for (Variable v : e.getReferencedVariables())
incrementVariableCount(vars, v, 1);
for (Expression e : this.assignments.values())
for (Variable v : e.getReferencedVariables())
incrementVariableCount(vars, v, 1);
}
return vars;
}
@Override
public Collection<URI> getReferencedURIs() {
Collection<URI> uris = super.getReferencedURIs();
for (Expression e : this.filters)
uris.addAll(e.getReferencedURIs());
for (Expression e : this.assignments.values())
uris.addAll(e.getReferencedURIs());
return uris;
}
@Override
public boolean removeChild(TreeNode child) {
if (this.patterns.remove(child))
return true;
return (child instanceof Expression && this.filters.remove(child));
}
@Override
public void addChild(TreeNode child) {
if (child instanceof GraphPattern) {
addGraphPattern((GraphPattern) child);
} else
throw new GlitterRuntimeException(ExceptionConstants.GLITTER.ONLY_ADD, "GraphPattern", "Group Query");
}
@Override
protected boolean prettyPrintParams(StringBuilder output) {
boolean printedSomething = false;
if (filters.size() > 0) {
output.append("Filters(");
int i = 0;
for (Expression filter : filters) {
filter.prettyPrint(output);
if (++i < filters.size())
output.append(", ");
}
output.append(")");
printedSomething = true;
}
if (assignments.size() > 0) {
output.append("Assignments(");
int i = 0;
int size = assignments.totalSize();
for (Entry<Variable, Collection<Expression>> assignment : assignments.entrySet()) {
Variable v = assignment.getKey();
for (Expression e : assignment.getValue()) {
output.append(v.toString());
output.append(" := ");
e.prettyPrint(output);
if (++i < size)
output.append(", ");
}
}
output.append(")");
printedSomething = true;
}
return printedSomething;
}
}