org.cobraparser.js.JavaFunctionObject Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Cobra Show documentation
Show all versions of Cobra Show documentation
Cobra is the rendering engine designed for LoboBrowser
/*
GNU LESSER GENERAL PUBLIC LICENSE
Copyright (C) 2006 The Lobo Project
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Contact info: [email protected]
*/
package org.cobraparser.js;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.cobraparser.util.Objects;
import org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.WrappedException;
import org.w3c.dom.DOMException;
public class JavaFunctionObject extends ScriptableObject implements Function {
private static final long serialVersionUID = 3716471130167741876L;
private static final Logger logger = Logger.getLogger(JavaFunctionObject.class.getName());
private static final boolean loggableInfo = logger.isLoggable(Level.INFO);
private final String methodName;
private final String className;
private final ArrayList methods = new ArrayList<>();
public JavaFunctionObject(final String name, final String className) {
super();
this.methodName = name;
this.className = className;
// TODO: Review
// Quick hack for issue #98
defineProperty("call", new Callable() {
public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
if ((args.length > 0) && (args[0] instanceof JavaObjectWrapper)) {
final JavaObjectWrapper javaObjectWrapper = (JavaObjectWrapper) args[0];
return JavaFunctionObject.this.call(cx, scope, javaObjectWrapper, Arrays.copyOfRange(args, 1, args.length));
} else {
throw new RuntimeException("Unexpected condition");
}
}
}, ScriptableObject.READONLY);
}
public void addMethod(final Method m) {
this.methods.add(m);
}
@Override
public String getClassName() {
return "JavaFunctionObject";
}
/*
private static String getTypeName(final Object object) {
return object == null ? "[null]" : object.getClass().getName();
}*/
private final static class MethodAndArguments {
private final Method method;
private final Object[] args;
public MethodAndArguments(final Method method, final Object[] args) {
this.method = method;
this.args = args;
}
public Object invoke(final Object javaObject) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return method.invoke(javaObject, args);
}
@Override
public String toString() {
return "MethodAndArguments [method=" + method + ", args=" + Arrays.toString(args) + "]";
}
}
private MethodAndArguments getExactMethod(final Object[] args) {
final ArrayList methods = this.methods;
final int size = methods.size();
for (int i = 0; i < size; i++) {
final Method m = methods.get(i);
final Class[] parameterTypes = m.getParameterTypes();
if (args == null) {
if ((parameterTypes == null) || (parameterTypes.length == 0)) {
return new MethodAndArguments(m, null);
}
} else if (parameterTypes != null) {
if (args.length == parameterTypes.length) {
if (Objects.areSameTo(args, parameterTypes)) {
return new MethodAndArguments(m, args);
}
} else if ((parameterTypes.length == 1) && parameterTypes[0].isArray()) {
final Class arrayType = parameterTypes[0].getComponentType();
final boolean allSame = true;
for (int j = 0; j < args.length; j++) {
if (!Objects.isSameOrBox(args[j], arrayType)) {
break;
}
}
if (allSame) {
final Object[] argsInArray = (Object[]) Array.newInstance(arrayType, args.length);
for (int j = 0; j < args.length; j++) {
argsInArray[j] = args[j];
}
return new MethodAndArguments(m, new Object[] { argsInArray });
}
}
}
}
return null;
}
private MethodAndArguments getBestMethod(final Object[] args) {
final MethodAndArguments exactMethod = getExactMethod(args);
if (exactMethod != null) {
return exactMethod;
}
final ArrayList methods = this.methods;
final int size = methods.size();
int matchingNumParams = 0;
Method matchingMethod = null;
for (int i = 0; i < size; i++) {
final Method m = methods.get(i);
final Class[] parameterTypes = m.getParameterTypes();
if (args == null) {
if ((parameterTypes == null) || (parameterTypes.length == 0)) {
return new MethodAndArguments(m, new Object[0]);
}
} else if ((parameterTypes != null) && (args.length >= parameterTypes.length)) {
if (Objects.areAssignableTo(args, parameterTypes)) {
final Object[] actualArgs = convertArgs(args, parameterTypes.length, parameterTypes);
return new MethodAndArguments(m, actualArgs);
}
if ((matchingMethod == null) || (parameterTypes.length > matchingNumParams)) {
matchingNumParams = parameterTypes.length;
matchingMethod = m;
}
}
}
if (size == 0) {
throw new IllegalStateException("zero methods");
}
if (matchingMethod == null) {
return null;
} else {
final Class[] actualArgTypes = matchingMethod.getParameterTypes();
final Object[] actualArgs = convertArgs(args, matchingNumParams, actualArgTypes);
return new MethodAndArguments(matchingMethod, actualArgs);
}
}
private static Object[] convertArgs(final Object[] args, final int numConvert, final Class[] actualArgTypes) {
final JavaScript manager = JavaScript.getInstance();
final Object[] actualArgs = args == null ? new Object[0] : new Object[numConvert];
if (args != null) {
for (int i = 0; i < numConvert; i++) {
final Object arg = args[i];
actualArgs[i] = manager.getJavaObject(arg, actualArgTypes[i]);
}
}
return actualArgs;
}
public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
final MethodAndArguments methodAndArguments = this.getBestMethod(args);
if (methodAndArguments == null) {
throw new EvaluatorException("No method matching " + this.methodName + " with " + (args == null ? 0 : args.length) + " arguments in "
+ className + " .");
}
final JavaScript manager = JavaScript.getInstance();
try {
if (thisObj instanceof JavaObjectWrapper) {
final JavaObjectWrapper jcw = (JavaObjectWrapper) thisObj;
// if(linfo) {
// Object javaObject = jcw.getJavaObject();
// logger.info("call(): Calling method " + method.getName() +
// " on object " + javaObject + " of type " +
// this.getTypeName(javaObject));
// }
final Object raw = methodAndArguments.invoke(jcw.getJavaObject());
return manager.getJavascriptObject(raw, scope);
} else {
// if (args[0] instanceof Function ) {
// Function func = (Function) args[0];
// Object raw = func.call(cx, scope, scope, Arrays.copyOfRange(args, 1,
// args.length));
// return manager.getJavascriptObject(raw, scope);
// } else {
final Object raw = methodAndArguments.invoke(thisObj);
return manager.getJavascriptObject(raw, scope);
// }
// Based on http://stackoverflow.com/a/16479685/161257
// return call(cx, scope, getParentScope(), args);
}
} catch (final IllegalAccessException iae) {
throw new IllegalStateException("Unable to call " + this.methodName + ".", iae);
} catch (final InvocationTargetException ite) {
if (ite.getCause() instanceof DOMException) {
final DOMException domException = (DOMException) ite.getCause();
throw new WrappedException(domException);
}
throw new WrappedException(
new InvocationTargetException(ite.getCause(), "Unable to call " + this.methodName + " on " + thisObj + "."));
} catch (final IllegalArgumentException iae) {
final StringBuffer argTypes = new StringBuffer();
for (int i = 0; i < methodAndArguments.args.length; i++) {
if (i > 0) {
argTypes.append(", ");
}
argTypes.append(methodAndArguments.args[i] == null ? "" : methodAndArguments.args[i].getClass().getName());
}
throw new WrappedException(new IllegalArgumentException("Unable to call " + this.methodName + " in " + className
+ ". Argument types: " + argTypes + "." + "\n on method: " + methodAndArguments.method,
iae));
}
}
@Override
public Object getDefaultValue(final Class hint) {
if (loggableInfo) {
logger.info("getDefaultValue(): hint=" + hint + ",this=" + this);
}
if ((hint == null) || String.class.equals(hint)) {
return "function " + this.methodName;
} else {
return super.getDefaultValue(hint);
}
}
public Scriptable construct(final Context cx, final Scriptable scope, final Object[] args) {
throw new UnsupportedOperationException();
}
}