/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 of the License, or (at your option) any later version. This program 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 General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ package org.lobobrowser.js; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.lobobrowser.util.Objects; 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; /** * The Class JavaFunctionObject. */ public class JavaFunctionObject extends ScriptableObject implements Function { /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1L; /** The Constant logger. */ private static final Logger logger = LogManager .getLogger(JavaFunctionObject.class.getName()); /** The Constant loggableInfo. */ private static final boolean loggableInfo = logger.isEnabled(Level.INFO); /** The class name. */ private final String className; /** The methods. */ private final ArrayList<Method> methods = new ArrayList<Method>(); /** * Instantiates a new java function object. * * @param name * the name */ public JavaFunctionObject(String name) { super(); this.className = name; } /** * Adds the method. * * @param m * the m */ public void addMethod(Method m) { this.methods.add(m); } /* * (non-Javadoc) * @see org.mozilla.javascript.ScriptableObject#getClassName() */ @Override public String getClassName() { return this.className; } /** * Gets the type name. * * @param object * the object * @return the type name */ private String getTypeName(Object object) { return object == null ? "[null]" : object.getClass().getName(); } /** * Gets the best method. * * @param args * the args * @return the best method */ private Method getBestMethod(Object[] args) { ArrayList<Method> methods = this.methods; int size = methods.size(); int matchingNumParams = 0; Method matchingMethod = null; for (int i = 0; i < size; i++) { Method m = methods.get(i); Class[] parameterTypes = m.getParameterTypes(); if (args == null) { if ((parameterTypes == null) || (parameterTypes.length == 0)) { return m; } } else if ((parameterTypes != null) && (args.length >= parameterTypes.length)) { if (Objects.areAssignableTo(args, parameterTypes)) { return m; } if ((matchingMethod == null) || (parameterTypes.length > matchingNumParams)) { matchingNumParams = parameterTypes.length; matchingMethod = m; } } } if (size == 0) { throw new IllegalStateException("zero methods"); } return matchingMethod; } /* * (non-Javadoc) * @see org.mozilla.javascript.Function#call(org.mozilla.javascript.Context, * org.mozilla.javascript.Scriptable, org.mozilla.javascript.Scriptable, * java.lang.Object[]) */ @Override public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { JavaObjectWrapper jcw = (JavaObjectWrapper) thisObj; Method method = this.getBestMethod(args); if (method == null) { throw new EvaluatorException("No method matching " + this.className + " with " + (args == null ? 0 : args.length) + " arguments."); } Class[] actualArgTypes = method.getParameterTypes(); int numParams = actualArgTypes.length; Object[] actualArgs = args == null ? new Object[0] : new Object[numParams]; boolean linfo = loggableInfo; if (linfo) { Object javaObject = jcw.getJavaObject(); logger.info("call(): Calling method " + method.getName() + " on object " + javaObject + " of type " + this.getTypeName(javaObject)); } JavaScript manager = JavaScript.getInstance(); for (int i = 0; i < numParams; i++) { Object arg = args[i]; Object actualArg = manager.getJavaObject(arg, actualArgTypes[i]); if (linfo) { logger.info("call(): For method=" + method.getName() + ": Converted arg=" + arg + " (type=" + this.getTypeName(arg) + ") into actualArg=" + actualArg + ". Type expected by method is " + actualArgTypes[i].getName() + "."); } actualArgs[i] = actualArg; } try { Object raw = method.invoke(jcw.getJavaObject(), actualArgs); return manager.getJavascriptObject(raw, scope); } catch (IllegalAccessException iae) { logger.error("Unable to call " + this.className + "."); } catch (InvocationTargetException ite) { logger.error("Unable to call " + this.className + " on " + jcw.getJavaObject() + "."); } catch (IllegalArgumentException iae) { StringBuffer argTypes = new StringBuffer(); for (int i = 0; i < actualArgs.length; i++) { if (i > 0) { argTypes.append(", "); } argTypes.append(actualArgs[i] == null ? "<null>" : actualArgs[i].getClass().getName()); } logger.error("Unable to call " + this.className + ". Argument types: " + argTypes + "."); } return manager; } /* * (non-Javadoc) * @see org.mozilla.javascript.ScriptableObject#getDefaultValue(java.lang.Class) */ @Override public java.lang.Object getDefaultValue(Class hint) { if (loggableInfo) { logger.info("getDefaultValue(): hint=" + hint + ",this=" + this); } if ((hint == null) || String.class.equals(hint)) { return "function " + this.className; } else { return super.getDefaultValue(hint); } } /* * (non-Javadoc) * @see * org.mozilla.javascript.Function#construct(org.mozilla.javascript.Context, * org.mozilla.javascript.Scriptable, java.lang.Object[]) */ @Override public Scriptable construct(Context cx, Scriptable scope, Object[] args) { throw new UnsupportedOperationException(); } }