package org.basex.query.func; import static javax.xml.datatype.DatatypeConstants.*; import static org.basex.query.QueryText.*; import static org.basex.query.util.Err.*; import static org.basex.util.Token.*; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; import java.net.URL; import java.util.Map; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import org.basex.query.QueryContext; import org.basex.query.QueryException; import org.basex.query.QueryModule; import org.basex.query.expr.Arr; import org.basex.query.expr.Expr; import org.basex.query.item.AtomType; import org.basex.query.item.Bln; import org.basex.query.item.Dbl; import org.basex.query.item.Empty; import org.basex.query.item.Flt; import org.basex.query.item.FuncType; import org.basex.query.item.Int; import org.basex.query.item.Jav; import org.basex.query.item.NodeType; import org.basex.query.item.QNm; import org.basex.query.item.Str; import org.basex.query.item.Type; import org.basex.query.item.Value; import org.basex.query.iter.ItemCache; import org.basex.query.iter.Iter; import org.basex.util.InputInfo; import org.basex.util.Reflect; import org.basex.util.TokenBuilder; import org.w3c.dom.Attr; import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.DocumentFragment; import org.w3c.dom.Element; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; /** * This class contains common methods for executing Java code and mapping * Java objects to XQuery values. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public abstract class JavaMapping extends Arr { /** New keyword. */ static final String NEW = "new"; /** Input Java types. */ private static final Class<?>[] JAVA = { String.class, boolean.class, Boolean.class, byte.class, Byte.class, short.class, Short.class, int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class, BigDecimal.class, BigInteger.class, QName.class, CharSequence.class, char.class, Character.class, URI.class, URL.class, Map.class }; /** Resulting XQuery types. */ private static final Type[] XQUERY = { AtomType.STR, AtomType.BLN, AtomType.BLN, AtomType.BYT, AtomType.BYT, AtomType.SHR, AtomType.SHR, AtomType.INT, AtomType.INT, AtomType.LNG, AtomType.LNG, AtomType.FLT, AtomType.FLT, AtomType.DBL, AtomType.DBL, AtomType.DEC, AtomType.ITR, AtomType.QNM, AtomType.STR, AtomType.STR, AtomType.STR, AtomType.URI, AtomType.URI, FuncType.ANY_FUN }; /** * Constructor. * @param ii input info * @param a arguments */ JavaMapping(final InputInfo ii, final Expr[] a) { super(ii, a); } @Override public final Iter iter(final QueryContext ctx) throws QueryException { return value(ctx).iter(); } @Override public final Value value(final QueryContext ctx) throws QueryException { final Value[] args = new Value[expr.length]; for(int a = 0; a < expr.length; ++a) { args[a] = ctx.value(expr[a]); if(args[a].isEmpty()) XPEMPTY.thrw(input, description()); } return toValue(eval(args)); } /** * Returns the result of the evaluated Java function. * @param args arguments * @return arguments * @throws QueryException query exception */ protected abstract Object eval(final Value[] args) throws QueryException; /** * Converts the specified result to an XQuery value. * @param res result object * @return value * @throws QueryException query exception */ public static Value toValue(final Object res) throws QueryException { if(res == null) return Empty.SEQ; if(res instanceof Value) return (Value) res; if(res instanceof Iter) return ((Iter) res).value(); // find XQuery mapping for specified type final Type type = type(res); if(type != null) return type.cast(res, null); if(!res.getClass().isArray()) return new Jav(res); final ItemCache ic = new ItemCache(); if(res instanceof boolean[]) { for(final boolean o : (boolean[]) res) ic.add(Bln.get(o)); } else if(res instanceof char[]) { ic.add(Str.get(new String((char[]) res))); } else if(res instanceof byte[]) { for(final byte o : (byte[]) res) ic.add(new Int(o, AtomType.BYT)); } else if(res instanceof short[]) { for(final short o : (short[]) res) ic.add(Int.get(o)); } else if(res instanceof int[]) { for(final int o : (int[]) res) ic.add(Int.get(o)); } else if(res instanceof long[]) { for(final long o : (long[]) res) ic.add(Int.get(o)); } else if(res instanceof float[]) { for(final float o : (float[]) res) ic.add(Flt.get(o)); } else if(res instanceof double[]) { for(final double o : (double[]) res) ic.add(Dbl.get(o)); } else { for(final Object o : (Object[]) res) { ic.add(o instanceof Value ? (Value) o : new Jav(o)); } } return ic.value(); } /** * Returns a new Java function instance. * @param name function name * @param args arguments * @param ctx query context * @param ii input info * @return Java function * @throws QueryException query exception */ static JavaMapping get(final QNm name, final Expr[] args, final QueryContext ctx, final InputInfo ii) throws QueryException { // resolve function name: convert dashes to upper-case initials final TokenBuilder m = new TokenBuilder(); final byte[] ln = name.local(); boolean dash = false; for(int p = 0; p < ln.length; p += cl(ln, p)) { final int ch = cp(ln, p); if(dash) { m.add(Character.toUpperCase(ch)); dash = false; } else { dash = ch == '-'; if(!dash) m.add(ch); } } final String mth = m.toString(); // check if class was imported as Java module final String clz = string(substring(name.uri(), JAVAPRE.length)); for(final QueryModule jm : ctx.javaModules.keySet()) { final Class<?> c = jm.getClass(); if(c.getName().equals(clz)) { for(final Method meth : c.getMethods()) { if(meth.getName().equals(mth)) return new JavaModuleFunc(ii, jm, meth, args); } WHICHJAVA.thrw(ii, clz + '.' + mth); } } Class<?> cls = Reflect.find(clz); if(cls == null && ctx.jars != null) cls = Reflect.find(clz, ctx.jars); if(cls == null) WHICHJAVA.thrw(ii, clz + '.' + mth); return new JavaFunc(ii, cls, mth, args); } /** * Returns an appropriate XQuery data type for the specified Java object. * @param o object * @return xquery type, or {@code null} if no appropriate type was found */ public static Type type(final Object o) { final Type t = type(o.getClass()); if(t != null) return t; if(o instanceof Element) return NodeType.ELM; if(o instanceof Document) return NodeType.DOC; if(o instanceof DocumentFragment) return NodeType.DOC; if(o instanceof Attr) return NodeType.ATT; if(o instanceof Comment) return NodeType.COM; if(o instanceof ProcessingInstruction) return NodeType.PI; if(o instanceof Text) return NodeType.TXT; if(o instanceof Duration) { final Duration d = (Duration) o; return !d.isSet(YEARS) && !d.isSet(MONTHS) ? AtomType.DTD : !d.isSet(HOURS) && !d.isSet(MINUTES) && !d.isSet(SECONDS) ? AtomType.YMD : AtomType.DUR; } if(o instanceof XMLGregorianCalendar) { final QName type = ((XMLGregorianCalendar) o).getXMLSchemaType(); if(type == DATE) return AtomType.DAT; if(type == DATETIME) return AtomType.DTM; if(type == TIME) return AtomType.TIM; if(type == GYEARMONTH) return AtomType.YMO; if(type == GMONTHDAY) return AtomType.MDA; if(type == GYEAR) return AtomType.YEA; if(type == GMONTH) return AtomType.MON; if(type == GDAY) return AtomType.DAY; } return null; } /** * Returns an appropriate XQuery data type for the specified Java class. * @param type Java type * @return xquery type */ static Type type(final Class<?> type) { for(int j = 0; j < JAVA.length; ++j) { if(JAVA[j].isAssignableFrom(type)) return XQUERY[j]; } return null; } }