/*
* eXist Native XML Database
* Copyright (C) 2000-03, Wolfgang M. Meier (wolfgang@exist-db.org)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Id$
*/
package org.exist.xquery;
import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.List;
import org.exist.dom.QName;
import org.exist.xquery.parser.XQueryAST;
import org.exist.xquery.util.Error;
import org.exist.xquery.util.ExpressionDumper;
import org.exist.xquery.util.Messages;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
/**
* Abstract base class for all built-in and user-defined functions.
*
* Built-in functions just extend this class. A new function instance
* will be created for each function call. Subclasses <b>have</b> to
* provide a function signature to the constructor.
*
* User-defined functions extend class {@link org.exist.xquery.UserDefinedFunction},
* which is again a subclass of Function. They will not be called directly, but through a
* {@link org.exist.xquery.FunctionCall} object, which checks the type and cardinality of
* all arguments and takes care that the current execution context is saved properly.
*
* @author wolf
*/
public abstract class Function extends PathExpr {
// Declare it in Namespaces instead? /ljo
public final static String BUILTIN_FUNCTION_NS =
"http://www.w3.org/2005/xpath-functions";
// The signature of the function.
protected FunctionSignature mySignature;
// The parent expression from which this function is called.
private Expression parent;
private boolean argumentsChecked = false;
private XQueryAST astNode = null;
/**
* Internal constructor. Subclasses should <b>always</b> call this and
* pass the current context and their function signature.
*
* @param context
* @param signature
*/
protected Function(XQueryContext context, FunctionSignature signature) {
super(context);
this.mySignature = signature;
}
protected Function(XQueryContext context) {
super(context);
}
/* (non-Javadoc)
* @see org.exist.xquery.PathExpr#returnsType()
*/
public int returnsType() {
if(mySignature == null)
return Type.ITEM; // Type is not known yet
if(mySignature.getReturnType() == null)
throw new IllegalArgumentException("Return type for function " + mySignature.getName() +
" is not defined");
return mySignature.getReturnType().getPrimaryType();
}
/* (non-Javadoc)
* @see org.exist.xquery.AbstractExpression#getCardinality()
*/
public int getCardinality() {
if(mySignature.getReturnType() == null)
throw new IllegalArgumentException("Return type for function " + mySignature.getName() +
" is not defined");
return mySignature.getReturnType().getCardinality();
}
/**
* Create a built-in function from the specified class.
* @return the created function or null if the class could not be initialized.
*/
public static Function createFunction(
XQueryContext context,
XQueryAST ast,
FunctionDef def) throws XPathException {
Class fclass = def.getImplementingClass();
if (def == null || fclass == null)
throw new XPathException(ast.getLine(), ast.getColumn(), "Class for function is null");
try {
Object initArgs[] = { context };
Class constructorArgs[] = { XQueryContext.class };
Constructor construct = null;
try {
construct = fclass.getConstructor(constructorArgs);
} catch(NoSuchMethodException e) {
}
// not found: check if the constructor takes two arguments
if (construct == null) {
constructorArgs = new Class[2];
constructorArgs[0] = XQueryContext.class;
constructorArgs[1] = FunctionSignature.class;
construct = fclass.getConstructor(constructorArgs);
if(construct == null)
throw new XPathException(ast.getLine(), ast.getColumn(), "Constructor not found");
initArgs = new Object[2];
initArgs[0] = context;
initArgs[1] = def.getSignature();
}
Object obj = construct.newInstance(initArgs);
if (obj instanceof Function) {
((Function)obj).setLocation(ast.getLine(), ast.getColumn());
return (Function) obj;
} else
throw new XPathException(ast.getLine(), ast.getColumn(), "Function object does not implement interface function");
} catch (Exception e) {
LOG.debug(e.getMessage(), e);
throw new XPathException(ast.getLine(), ast.getColumn(), "Function implementation class " + fclass.getName() + " not found");
}
}
/**
* Set the parent expression of this function, i.e. the
* expression from which the function is called.
*
* @param parent
*/
public void setParent(Expression parent) {
this.parent = parent;
}
/**
* Returns the expression from which this function
* gets called.
*/
public Expression getParent() {
return parent;
}
/**
* Set the (static) arguments for this function from a list of expressions.
*
* This will also check the type and cardinality of the
* passed argument expressions.
*
* @param arguments
* @throws XPathException
*/
public void setArguments(List arguments) throws XPathException {
if ((!mySignature.isOverloaded())
&& arguments.size() != mySignature.getArgumentCount())
throw new XPathException(this,
"number of arguments to function "
+ getName()
+ " doesn't match function signature (expected "
+ mySignature.getArgumentCount()
+ ", got "
+ arguments.size()
+ ')');
steps = arguments;
argumentsChecked = false;
}
/**
* @throws XPathException
*/
protected void checkArguments() throws XPathException {
if (!argumentsChecked) {
SequenceType[] argumentTypes = mySignature.getArgumentTypes();
Expression next;
SequenceType argType = null;
for (int i = 0; i < getArgumentCount(); i++) {
if (argumentTypes != null && i < argumentTypes.length)
argType = argumentTypes[i];
next = checkArgument(getArgument(i), argType, i + 1);
steps.set(i, next);
}
}
argumentsChecked = true;
}
/**
* Statically check an argument against the sequence type specified in
* the signature.
*
* @param expr
* @param type
* @return The passed expression
* @throws XPathException
*/
protected Expression checkArgument(Expression expr, SequenceType type, int argPosition)
throws XPathException {
if (type == null)
return expr;
// check cardinality if expected cardinality is not zero or more
boolean cardinalityMatches =
type.getCardinality() == Cardinality.ZERO_OR_MORE;
if (!cardinalityMatches) {
cardinalityMatches =
(expr.getCardinality() | type.getCardinality())
== type.getCardinality();
if (!cardinalityMatches) {
if(expr.getCardinality() == Cardinality.ZERO
&& (type.getCardinality() & Cardinality.ZERO) == 0)
throw new XPathException(this,
Messages.getMessage(Error.FUNC_EMPTY_SEQ_DISALLOWED,
new Integer(argPosition), ExpressionDumper.dump(expr)));
}
}
expr =
new DynamicCardinalityCheck(
context,
type.getCardinality(),
expr, new Error(Error.FUNC_PARAM_CARDINALITY, String.valueOf(argPosition), mySignature));
expr.setLocation(getLine(), getColumn());
// check return type if both types are not Type.ITEM
int returnType = expr.returnsType();
if (returnType == Type.ANY_TYPE || returnType == Type.EMPTY)
returnType = Type.ITEM;
boolean typeMatches = type.getPrimaryType() == Type.ITEM;
typeMatches = Type.subTypeOf(returnType, type.getPrimaryType());
if (typeMatches && cardinalityMatches) {
if(type.getNodeName() != null)
expr = new DynamicNameCheck(context,
new NameTest(type.getPrimaryType(), type.getNodeName()), expr);
return expr;
}
//Loose argument check : we may move this, or a part hereof, to UntypedValueCheck
if (context.isBackwardsCompatible()) {
if (Type.subTypeOf(type.getPrimaryType(), Type.STRING)) {
if (!Type.subTypeOf(returnType, Type.ATOMIC)) {
expr = new Atomize(context, expr);
returnType = Type.ATOMIC;
}
expr = new AtomicToString(context, expr);
returnType = Type.STRING;
} else if (type.getPrimaryType() == Type.NUMBER
|| Type.subTypeOf(type.getPrimaryType(), Type.DOUBLE)) {
if (!Type.subTypeOf(returnType, Type.ATOMIC)) {
expr = new Atomize(context, expr);
returnType = Type.ATOMIC;
}
expr =
new UntypedValueCheck(context, type.getPrimaryType(), expr,
new Error(Error.FUNC_PARAM_TYPE, String.valueOf(argPosition), mySignature));
returnType = type.getPrimaryType();
}
// if the required type is an atomic type, convert the argument to an atomic
if (Type.subTypeOf(type.getPrimaryType(), Type.ATOMIC)) {
if(!Type.subTypeOf(returnType, Type.ATOMIC))
expr = new Atomize(context, expr);
if (!(type.getPrimaryType() == Type.ATOMIC))
expr =
new UntypedValueCheck(context, type.getPrimaryType(), expr,
new Error(Error.FUNC_PARAM_TYPE, String.valueOf(argPosition), mySignature));
returnType = expr.returnsType();
}
//Strict argument check : we may move this, or a part hereof, to UntypedValueCheck
} else {
// if the required type is an atomic type, convert the argument to an atomic
if (Type.subTypeOf(type.getPrimaryType(), Type.ATOMIC)) {
if(!Type.subTypeOf(returnType, Type.ATOMIC))
expr = new Atomize(context, expr);
//if (!(type.getPrimaryType() == Type.ATOMIC))
expr =
new UntypedValueCheck(context, type.getPrimaryType(), expr,
new Error(Error.FUNC_PARAM_TYPE, String.valueOf(argPosition), mySignature));
returnType = expr.returnsType();
}
}
if (returnType != Type.ITEM && !Type.subTypeOf(returnType, type.getPrimaryType())) {
if (!(Type.subTypeOf(type.getPrimaryType(), returnType) ||
//because () is seen as a node
(type.getPrimaryType() == Type.EMPTY && returnType == Type.NODE))) {
LOG.debug(ExpressionDumper.dump(expr));
throw new XPathException(this,
Messages.getMessage(Error.FUNC_PARAM_TYPE_STATIC,
String.valueOf(argPosition), mySignature,
type.toString(), Type.getTypeName(returnType)));
}
}
if (!typeMatches) {
if(type.getNodeName() != null)
expr = new DynamicNameCheck(context,
new NameTest(type.getPrimaryType(), type.getNodeName()), expr);
else
expr = new DynamicTypeCheck(context, type.getPrimaryType(), expr);
}
return expr;
}
/* (non-Javadoc)
* @see org.exist.xquery.PathExpr#analyze(org.exist.xquery.Expression)
*/
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
// statically check the argument list
checkArguments();
// call analyze for each argument
inPredicate = (contextInfo.getFlags() & IN_PREDICATE) > 0;
contextId = contextInfo.getContextId();
contextInfo.setParent(this);
for(int i = 0; i < getArgumentCount(); i++) {
AnalyzeContextInfo argContextInfo = new AnalyzeContextInfo(contextInfo);
getArgument(i).analyze(argContextInfo);
}
}
public abstract Sequence eval(
Sequence contextSequence,
Item contextItem)
throws XPathException;
public Sequence[] getArguments(Sequence contextSequence, Item contextItem) throws XPathException {
if(contextItem != null)
contextSequence = contextItem.toSequence();
final int argCount = getArgumentCount();
Sequence[] args = new Sequence[argCount];
for(int i = 0; i < argCount; i++) {
args[i] = getArgument(i).eval(contextSequence, contextItem);
}
return args;
}
/**
* Get an argument expression by its position in the
* argument list.
*
* @param pos
*/
public Expression getArgument(int pos) {
return getExpression(pos);
}
/**
* Get the number of arguments passed to this function.
*
* @return number of arguments
*/
public int getArgumentCount() {
return steps.size();
}
public void setPrimaryAxis(int axis) {
}
/**
* Return the name of this function.
*
* @return name of this function
*/
public QName getName() {
return mySignature.getName();
}
/**
* Get the signature of this function.
*
* @return signature of this function
*/
public FunctionSignature getSignature() {
return mySignature;
}
public boolean isCalledAs(String localName) {
return localName.equals(mySignature.getName().getLocalName());
}
/* (non-Javadoc)
* @see org.exist.xquery.AbstractExpression#getDependencies()
*/
public int getDependencies() {
return Dependency.CONTEXT_ITEM | Dependency.CONTEXT_SET;
}
/* (non-Javadoc)
* @see org.exist.xquery.PathExpr#dump(org.exist.xquery.util.ExpressionDumper)
*/
public void dump(ExpressionDumper dumper) {
dumper.display(getName());
dumper.display('(');
boolean moreThanOne = false;
for (Iterator i = steps.iterator(); i.hasNext();) {
Expression e = (Expression) i.next();
if (moreThanOne) dumper.display(", ");
moreThanOne = true;
e.dump(dumper);
}
dumper.display(')');
}
public String toString() {
StringBuilder result = new StringBuilder();
result.append(getName());
result.append('(');
boolean moreThanOne = false;
for (Iterator i = steps.iterator(); i.hasNext();) {
Expression e = (Expression) i.next();
if (moreThanOne) result.append(", ");
moreThanOne = true;
result.append(e.toString());
}
result.append(')');
return result.toString();
}
public void accept(ExpressionVisitor visitor) {
visitor.visitBuiltinFunction(this);
}
}