/******************************************************************************* * 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/FunctionCall.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: FunctionCall.java 229 2007-08-07 15:22:00Z 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.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.builder.HashCodeBuilder; import org.openanzo.exceptions.ExceptionConstants; import org.openanzo.glitter.exception.ExpressionEvaluationException; import org.openanzo.glitter.exception.GlitterRuntimeException; import org.openanzo.glitter.exception.IncompatibleTypeException; import org.openanzo.glitter.exception.InvalidAggregateArgumentException; import org.openanzo.glitter.exception.InvalidAggregateFunctionException; import org.openanzo.glitter.exception.InvalidArgumentCountException; import org.openanzo.glitter.exception.UnknownFunctionException; import org.openanzo.glitter.expression.AggregateFunction; import org.openanzo.glitter.expression.BinaryFunction; import org.openanzo.glitter.expression.Function; import org.openanzo.glitter.expression.FunctionRegistry; import org.openanzo.glitter.expression.FunctionWithAttributes; import org.openanzo.glitter.expression.InfixOperator; import org.openanzo.glitter.expression.PrefixOperator; import org.openanzo.glitter.expression.ScalarFunction; import org.openanzo.glitter.expression.ScalarFunctionOnTerms; import org.openanzo.glitter.expression.ScalarFunctionOnValues; import org.openanzo.glitter.expression.ScalarFunctionOnValuesAndErrors; import org.openanzo.glitter.query.PatternSolution; import org.openanzo.glitter.query.PatternSolutionImpl; import org.openanzo.glitter.query.Projection; import org.openanzo.glitter.query.SolutionList; import org.openanzo.glitter.query.SolutionSet; import org.openanzo.glitter.query.QueryController.QueryStringPrintOptions; import org.openanzo.glitter.syntax.concrete.ParseException; import org.openanzo.rdf.URI; import org.openanzo.rdf.Value; import org.openanzo.rdf.Variable; /** * A {@link FunctionCall} is an {@link Expression} that represents the invocation of a particular {@link Function} against a list of arguments. * * @author lee <lee@cambridgesemantics.com> * */ public class FunctionCall implements Expression { private List<Expression> arguments = null; private Function function = null; private boolean distinct = false; // for aggregates private boolean star = false; // for aggregates private List<Variable> argumentsAsVariables = null; // for aggregates private Map<String, Object> attributes = null; private int hashCode = -1; @Override public boolean equals(Object obj) { if (!(obj instanceof FunctionCall)) return false; FunctionCall other = (FunctionCall) obj; return this.function.equals(other.function) && this.arguments.equals(other.arguments) && this.distinct == other.distinct && this.star == other.star && ( (this.argumentsAsVariables == null && other.argumentsAsVariables == null) || (this.argumentsAsVariables.equals(other.argumentsAsVariables)) ) && ( (this.attributes == null && other.attributes == null) || (this.attributes.equals(other.attributes)) ); } @Override public int hashCode() { if (hashCode == -1) { HashCodeBuilder hcb = new HashCodeBuilder(); hcb.append(this.function); hcb.append(this.arguments); hcb.append(this.distinct); hcb.append(this.star); hcb.append(this.argumentsAsVariables); hcb.append(this.attributes); hashCode = hcb.toHashCode(); } return hashCode; } /** * * @param f * @param arguments * @throws ParseException */ public FunctionCall(Function f, List<Expression> arguments) throws ParseException { this(f, arguments, false, false, null); } /** * Construct a {@link FunctionCall} from a {@link Function} and a list of arguemnts. * * @param f * @param arguments * @param star * @param distinct * @throws ParseException */ public FunctionCall(Function f, List<Expression> arguments, boolean star, boolean distinct) throws ParseException { this(f, arguments, star, distinct, null); } /** * Construct a {@link FunctionCall} from a {@link Function} and a list of arguemnts. * * @param u * @param arguments * @param star * @param distinct * @throws ParseException */ public FunctionCall(URI u, List<Expression> arguments, boolean star, boolean distinct) throws ParseException { this(u, arguments, star, distinct, null); } /** * Construct a {@link FunctionCall} from a {@link Function} and a list of arguemnts. * * @param f * @param arguments * @param star * @param distinct * @param attributes * @throws ParseException */ public FunctionCall(Function f, List<Expression> arguments, boolean star, boolean distinct, Map<String, Object> attributes) throws ParseException { initialize(f, arguments, star, distinct, attributes); } /** * Construct a {@link FunctionCall} from the name of a function (as a URI) and a list of arguments. Finds the {@link Function} in the static * {@link FunctionRegistry}. * * @param u * @param arguments * @param star * @param distinct * @param attributes * @throws ParseException */ public FunctionCall(URI u, List<Expression> arguments, boolean star, boolean distinct, Map<String, Object> attributes) throws ParseException { Function f = FunctionRegistry.getRegistry().getFunction(u); if (f == null) throw new UnknownFunctionException(u); initialize(f, arguments, star, distinct, attributes); } private void initialize(Function f, List<Expression> arguments, boolean star, boolean distinct, Map<String, Object> attributes) throws ParseException { this.function = f; this.arguments = arguments == null ? new ArrayList<Expression>() : arguments; this.distinct = distinct; this.star = star; this.attributes = attributes; if (this.function instanceof AggregateFunction) { this.argumentsAsVariables = new ArrayList<Variable>(); for (Expression e : this.arguments) { if (e instanceof SimpleExpression) { SimpleExpression se = (SimpleExpression) e; if (se.getTerm() instanceof Variable) { this.argumentsAsVariables.add((Variable) se.getTerm()); continue; } } throw new InvalidAggregateArgumentException(e); } // aggregates can't have both a * and a variable if (star && !this.arguments.isEmpty()) throw new InvalidAggregateArgumentException(this.arguments.get(0)); // aggregates can't be empty if (!star && this.arguments.isEmpty()) throw new InvalidAggregateArgumentException(); } else { // distinct and star are not valid for non-aggregate functions - this is a parse error if (star) throw new ParseException("'*' found as argument to non-aggregate function:" + f); if (distinct) throw new ParseException("'DISTINCT' found as modifier to non-aggregate function:" + f); } if (this.attributes != null && this.attributes.size() > 0) { if (this.function instanceof FunctionWithAttributes) { ((FunctionWithAttributes) this.function).setAttributes(this.attributes); } else { throw new ParseException("Attributes included for function that does not expect attributes."); } } } public Value evaluate(PatternSolution solution, SolutionSet group) throws ExpressionEvaluationException { if (this.function instanceof ScalarFunction) { ScalarFunction f = (ScalarFunction) this.function; // TODO - we can catch errors in, for example, numbers of arguments to a function // at parse time, but we don't currently do that if (f.operatesOnValues()) { if (!f.operatesOnTypeErrors() && f instanceof BinaryFunction) { if (arguments.size() != 2) { throw new InvalidArgumentCountException(arguments.size(), 2); } Value argValue1 = arguments.get(0).evaluate(solution, group); Value argValue2 = arguments.get(1).evaluate(solution, group); return ((BinaryFunction) f).call(argValue1, argValue2); } else { // evaluate all the arguments, storing them and any errors their // evaluation produces - some functions actually care about the errors, // while others blindly propagate them ArrayList<Value> evaluatedArguments = new ArrayList<Value>(); ArrayList<ExpressionEvaluationException> argumentExceptions = new ArrayList<ExpressionEvaluationException>(); for (Expression argument : this.arguments) { try { Value argValue = argument.evaluate(solution, group); if (argValue == null) // argument is unbound --> type error throw new IncompatibleTypeException(argument, "bound-value"); evaluatedArguments.add(argValue); argumentExceptions.add(null); } catch (ExpressionEvaluationException e) { if (e instanceof IncompatibleTypeException && f.operatesOnTypeErrors()) { evaluatedArguments.add(null); argumentExceptions.add(e); } else { throw e; } } } if (f.operatesOnTypeErrors()) return ((ScalarFunctionOnValuesAndErrors) f).call(evaluatedArguments, argumentExceptions); else return ((ScalarFunctionOnValues) f).call(evaluatedArguments); } } else { return ((ScalarFunctionOnTerms) f).call(this.arguments, solution); } } else if (this.function instanceof AggregateFunction) { AggregateFunction f = (AggregateFunction) this.function; if (group == null) throw new InvalidAggregateFunctionException(f); // screen the solution set to contain only the variables given by the arguments // if we need to distinct the arguments SolutionSet screened = group; if (this.distinct && this.arguments != null && this.arguments.size() > 0) { screened = new SolutionList(); for (PatternSolution sol : group) { PatternSolutionImpl psi = new PatternSolutionImpl(); Collection<Variable> bound = sol.getBoundVariables(); for (Variable v : this.argumentsAsVariables) if (bound.contains(v)) psi.setBinding(v, sol.getBinding(v)); screened.add(psi); } } if (this.distinct) screened = Projection.projectDistinctSolutions(screened, null, null); return f.call(this.argumentsAsVariables, screened); } else { throw new GlitterRuntimeException(ExceptionConstants.GLITTER.SCALER_OR_AGGREGATE); } } public Set<Variable> getReferencedVariables() { HashSet<Variable> vars = new HashSet<Variable>(); for (Expression e : this.arguments) vars.addAll(e.getReferencedVariables()); return vars; } /** * @return the function */ public Function getFunction() { return this.function; } /** * * @return The list of arguments to this function call. */ public List<Expression> getArguments() { return this.arguments; } /** * Is this a star argument * * @return true if this ia star argument */ public boolean starArgument() { return this.star; } public void prettyPrintQueryPart(EnumSet<QueryStringPrintOptions> printFlags, int indentLevel, Map<String, String> uri2prefix, StringBuilder s) { Function fn = this.getFunction(); List<Expression> args = this.getArguments(); if (fn instanceof PrefixOperator) { s.append("("); PrefixOperator pre = (PrefixOperator) fn; s.append(pre.getOperator()); args.get(0).prettyPrintQueryPart(printFlags, indentLevel, uri2prefix, s); s.append(")"); } else if (fn instanceof InfixOperator) { s.append("("); InfixOperator infix = (InfixOperator) fn; args.get(0).prettyPrintQueryPart(printFlags, indentLevel, uri2prefix, s); s.append(" "); s.append(infix.getOperator()); s.append(" "); args.get(1).prettyPrintQueryPart(printFlags, indentLevel, uri2prefix, s); s.append(")"); } else { s.append(fn); { s.append("("); if (this.distinct) s.append("DISTINCT "); if (this.star) { s.append("*"); } else { int j = 0; for (Expression arg : this.getArguments()) { arg.prettyPrintQueryPart(printFlags, indentLevel, uri2prefix, s); if (++j < this.getArguments().size()) s.append(", "); } } s.append(")"); } } } @Override public String toString() { StringBuilder builder = new StringBuilder(); Function fn = this.getFunction(); List<Expression> args = this.getArguments(); if (fn instanceof PrefixOperator) { builder.append("("); PrefixOperator pre = (PrefixOperator) fn; builder.append(pre.getOperator()); builder.append(args.get(0)); builder.append(")"); } else if (fn instanceof InfixOperator) { builder.append("("); InfixOperator infix = (InfixOperator) fn; builder.append(args.get(0)); builder.append(" "); builder.append(infix.getOperator()); builder.append(" "); builder.append(args.get(1)); builder.append(")"); } else { builder.append(fn); { builder.append("("); if (this.distinct) builder.append("DISTINCT "); if (this.star) { builder.append("*"); } else { int j = 0; for (Expression arg : this.getArguments()) { builder.append(arg); if (++j < this.getArguments().size()) builder.append(", "); } } builder.append(")"); } } return builder.toString(); } public void prettyPrint(StringBuilder output) { output.append(this.getFunction().getClass().getSimpleName()); output.append("("); { int j = 0; if (this.distinct) output.append("DISTINCT "); if (this.star) { output.append("*"); } else { for (Expression arg : this.getArguments()) { arg.prettyPrint(output); if (++j < this.getArguments().size()) output.append(", "); } } } output.append(")"); } public Collection<Variable> getBindableVariables() { return Collections.emptyList(); } public Collection<URI> getReferencedURIs() { HashSet<URI> uris = new HashSet<URI>(); URI f = this.function.getIdentifier(); if (f != null) uris.add(f); for (Expression e : this.arguments) uris.addAll(e.getReferencedURIs()); return uris; } }