package org.basex.query.func;
import static org.basex.query.QueryText.*;
import static org.basex.query.util.Err.*;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.basex.io.serial.Serializer;
import org.basex.query.QueryException;
import org.basex.query.expr.Expr;
import org.basex.query.item.Type;
import org.basex.query.item.Value;
import org.basex.util.InputInfo;
import org.basex.util.Token;
import org.basex.util.TokenBuilder;
/**
* Java function binding.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class JavaFunc extends JavaMapping {
/** Java class. */
private final Class<?> cls;
/** Java method. */
private final String mth;
/**
* Constructor.
* @param ii input info
* @param c Java class
* @param m Java method/field
* @param a arguments
*/
JavaFunc(final InputInfo ii, final Class<?> c, final String m,
final Expr[] a) {
super(ii, a);
cls = c;
mth = m;
}
@Override
protected Object eval(final Value[] args) throws QueryException {
try {
return mth.equals(NEW) ? constructor(args) : method(args);
} catch(final InvocationTargetException ex) {
throw JAVAERR.thrw(input, ex.getCause());
} catch(final Throwable ex) {
final TokenBuilder found = new TokenBuilder();
for(final Value c : args) {
if(found.size() != 0) found.add(", ");
found.add(c.type.toString());
}
throw JAVAFUN.thrw(input, name(), found);
}
}
/**
* Calls a constructor.
* @param ar arguments
* @return resulting object
* @throws Exception exception
*/
private Object constructor(final Value[] ar) throws Exception {
for(final Constructor<?> con : cls.getConstructors()) {
final Object[] arg = args(con.getParameterTypes(), ar, true);
if(arg != null) return con.newInstance(arg);
}
throw new Exception();
}
/**
* Calls a method.
* @param ar arguments
* @return resulting object
* @throws Exception exception
*/
private Object method(final Value[] ar) throws Exception {
// check if a field with the specified name exists
try {
final Field f = cls.getField(mth);
final boolean st = Modifier.isStatic(f.getModifiers());
if(ar.length == (st ? 0 : 1)) {
return f.get(st ? null : instObj(ar[0]));
}
} catch(final NoSuchFieldException ex) { /* ignored */ }
for(final Method meth : cls.getMethods()) {
if(!meth.getName().equals(mth)) continue;
final boolean st = Modifier.isStatic(meth.getModifiers());
final Object[] arg = args(meth.getParameterTypes(), ar, st);
if(arg != null) return meth.invoke(st ? null : instObj(ar[0]), arg);
}
throw new Exception();
}
/**
* Creates the instance on which a non-static field getter or method is
* invoked.
* @param v XQuery value
* @return Java object
* @throws QueryException query exception
*/
private Object instObj(final Value v) throws QueryException {
return cls.isInstance(v) ? v : v.toJava();
}
/**
* Checks if the arguments conform with the specified parameters.
* @param params parameters
* @param args arguments
* @param stat static flag
* @return argument array or {@code null}
* @throws QueryException query exception
*/
private static Object[] args(final Class<?>[] params, final Value[] args,
final boolean stat) throws QueryException {
final int s = stat ? 0 : 1;
final int l = args.length - s;
if(l != params.length) return null;
// function arguments
final Object[] val = new Object[l];
int a = 0;
for(final Class<?> par : params) {
final Value arg = args[s + a];
final Object next;
if(par.isInstance(arg)) {
next = arg;
} else {
final Type jtype = type(par);
if(jtype == null || !arg.type.instanceOf(jtype)
&& !jtype.instanceOf(arg.type)) return null;
next = arg.toJava();
}
val[a++] = next;
}
return val;
}
@Override
public void plan(final Serializer ser) throws IOException {
ser.openElement(this, NAM, Token.token(cls + "." + mth));
for(final Expr arg : expr) arg.plan(ser);
ser.closeElement();
}
@Override
public String description() {
return name() + (mth.equals(NEW) ? " constructor" : " method");
}
/**
* Returns the function descriptor.
* @return string
*/
private String name() {
return cls.getSimpleName() + '.' + mth;
}
@Override
public String toString() {
return cls + "." + mth + PAR1 + toString(SEP) + PAR2;
}
}