org.fit.cssbox.scriptbox.script.javascript.java.HostedJavaMethod.java Source code

Java tutorial

Introduction

Here is the source code for org.fit.cssbox.scriptbox.script.javascript.java.HostedJavaMethod.java

Source

/**
 * HostedJavaMethod.java
 * (c) Radim Loskot and Radek Burget, 2013-2014
 *
 * ScriptBox 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 3 of the License, or
 * (at your option) any later version.
 *  
 * ScriptBox 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 ScriptBox. If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package org.fit.cssbox.scriptbox.script.javascript.java;

import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

import javax.script.ScriptException;

import org.apache.commons.lang3.ClassUtils;
import org.fit.cssbox.scriptbox.dom.Html5DocumentImpl;
import org.fit.cssbox.scriptbox.script.BrowserScriptEngine;
import org.fit.cssbox.scriptbox.script.FunctionInvocation;
import org.fit.cssbox.scriptbox.script.WindowScriptEngine;
import org.fit.cssbox.scriptbox.script.exceptions.FunctionException;
import org.fit.cssbox.scriptbox.script.exceptions.InternalException;
import org.fit.cssbox.scriptbox.script.exceptions.UnknownException;
import org.fit.cssbox.scriptbox.script.javascript.WindowJavaScriptEngine;
import org.fit.cssbox.scriptbox.script.reflect.ClassField;
import org.fit.cssbox.scriptbox.script.reflect.ClassFunction;
import org.fit.cssbox.scriptbox.script.reflect.FunctionMember;
import org.fit.cssbox.scriptbox.script.reflect.InvocableMember;
import org.fit.cssbox.scriptbox.window.InvokeWindowScript;
import org.fit.cssbox.scriptbox.window.Window;
import org.fit.cssbox.scriptbox.window.WindowScriptSettings;
import org.mozilla.javascript.BaseFunction;
import org.mozilla.javascript.ConsString;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.FunctionObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.TopLevel;
import org.mozilla.javascript.TopLevel.Builtins;
import org.mozilla.javascript.Undefined;

/**
 * Creates function scope for native Java method - wraps the native Java method
 * and makes it callable from the JavaScript.
 * 
 * @author Radim Loskot
 * @version 0.9
 * @since 0.9 - 21.4.2014
 */
public class HostedJavaMethod extends FunctionObject {
    public final static Method FUNCTION_METHOD;

    static {
        Method functionMethod = null;
        try {
            functionMethod = HostedJavaMethod.class.getDeclaredMethod("invoke", Context.class, Scriptable.class,
                    Object[].class, Function.class);
        } catch (NoSuchMethodException e) {
        }

        FUNCTION_METHOD = functionMethod;
    }

    private static final long serialVersionUID = -5644060115581311028L;

    private Object object;
    private Set<FunctionMember> objectFunctions;

    /**
     * Constructs new hosted Java method for given parent scope, of the passed object and with the passed overloaded function members.
     * 
     * @param scope Scope to become the parent scope of this function.
     * @param object Object with the wrapped method.
     * @param objectFunctions Overloaded function members which should have the same name.
     */
    public HostedJavaMethod(Scriptable scope, Object object, Set<? extends FunctionMember> objectFunctions) {
        super(objectFunctions.iterator().next().getMember().getName(), FUNCTION_METHOD, scope);

        this.object = object;
        this.objectFunctions = new HashSet<FunctionMember>();
        this.objectFunctions.addAll(objectFunctions);

        TopLevel topLevel = WindowJavaScriptEngine.getObjectTopLevel(scope);
        Scriptable builtinFunction = topLevel.getBuiltinCtor(Builtins.Function);
        setPrototype(builtinFunction);
    }

    /**
     * Constructs new hosted Java method for given parent scope, of the passed object and with the passed function member.
     * 
     * @param scope Scope to become the parent scope of this function.
     * @param object Object with the wrapped method.
     * @param objectFunction Function member to be wrapped.
     */
    public HostedJavaMethod(Scriptable scope, Object object, final FunctionMember objectFunction) {
        this(scope, object, new HashSet<FunctionMember>() {
            private static final long serialVersionUID = -7257365623995694177L;
            {
                add(objectFunction);
            }
        });
    }

    /**
     * Returns attached object overloaded function members.
     * 
     * @return All associated function members.
     */
    public Set<? extends FunctionMember> getAttachedObjectFunctions() {
        return objectFunctions;
    }

    /**
     * Adds new overloaded member into this wrapped function object.
     * 
     * @param objectFunction New function member to be attached.
     */
    public void attachObjectFunction(FunctionMember objectFunction) {
        this.objectFunctions.add(objectFunction);
    }

    @Override
    public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
        InvocableMember<?> nearestInvocable = getNearestObjectFunction(args, objectFunctions);

        if (nearestInvocable == null) {
            throw new FunctionException("Unable to match nearest function");
        }

        FunctionMember nearestFunctionObject = (FunctionMember) nearestInvocable;

        Method functionMethod = nearestFunctionObject.getMember();
        Class<?> returnType = functionMethod.getReturnType();

        Class<?> expectedTypes[] = functionMethod.getParameterTypes();
        Object[] castedArgs = castArgs(expectedTypes, args);

        try {
            Object returned = functionMethod.invoke(object, castedArgs);
            return (returnType == Void.class) ? Undefined.instance : returned;
        } catch (Exception e) {
            throw new UnknownException(e);
        }
    }

    /**
     * Invokes given function object.
     * 
     * @param funObj Function object to be invoked.
     * @see Function#call(Context, Scriptable, Scriptable, Object[])
     */
    public static Object invoke(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        if (funObj instanceof HostedJavaMethod) {
            ((HostedJavaMethod) funObj).call(cx, thisObj, thisObj, args);
        }

        throw new FunctionException("Function object must be of class HostedJavaMethod");
    }

    /**
     * Returns the nearest function - searches inside set of functions and
     * tries to match the appropriate function according to given arguments.
     * 
     * @param args Arguments that are passed in the function call.
     * @param invocableMembers Set of the functions.
     * @return Function member if there was any that matched the passed arguments.
     */
    public static InvocableMember<?> getNearestObjectFunction(Object[] args,
            Set<? extends InvocableMember<?>> invocableMembers) {
        Class<?> argsTypes[] = new Class<?>[args.length];

        for (int i = 0; i < args.length; i++) {
            argsTypes[i] = (args[i] != null) ? args[i].getClass() : null;
        }

        for (InvocableMember<?> invocableMember : invocableMembers) {
            Class<?> methodTypes[] = invocableMember.getParameterTypes();

            Object[] castedArgs = castArgs(methodTypes, args);
            if (castedArgs == null) {
                continue;
            }

            boolean isAssignable = ClassFunction.isAssignableTypes(castedArgs, methodTypes);
            if (isAssignable) {
                return invocableMember;
            }
        }

        return null;
    }

    /**
     * Casts the given arguments into given expected types.
     * 
     * @param expectedTypes Types into which should be casted the given arguments.
     * @param args Arguments to be casted.
     * @return Array of the casted arguments if casting was successful, otherwise null.
     */
    public static Object[] castArgs(Class<?>[] expectedTypes, Object... args) {
        if (expectedTypes != null && args != null) {

            if (expectedTypes.length <= args.length + 1 && expectedTypes.length > 0) {
                Class<?> lastType = expectedTypes[expectedTypes.length - 1];
                if (lastType.isArray()) {
                    Class<?> arrayType = lastType.getComponentType();

                    boolean maybeVarargs = true;
                    if (expectedTypes.length == args.length) {
                        Object lastArg = args[args.length - 1];
                        Class<?> lastArgClass = (lastArg != null) ? lastArg.getClass() : null;
                        maybeVarargs = lastArgClass != null && !ClassUtils.isAssignable(lastArgClass, lastType);
                    }

                    if (maybeVarargs) {
                        for (int i = expectedTypes.length - 1; i < args.length; i++) {
                            if (args[i] == null) {
                                continue;
                            }
                            Class<?> argType = args[i].getClass();

                            if (!ClassUtils.isAssignable(argType, arrayType)) {
                                maybeVarargs = false;
                                break;
                            }
                        }

                        if (maybeVarargs) {
                            Object[] oldArgs = args;
                            args = new Object[expectedTypes.length];

                            for (int i = 0; i < expectedTypes.length - 1; i++) {
                                args[i] = oldArgs[i];
                            }

                            Object[] varargs = new Object[oldArgs.length - expectedTypes.length + 1];

                            for (int i = expectedTypes.length - 1; i < oldArgs.length; i++) {
                                varargs[i - expectedTypes.length + 1] = oldArgs[i];
                            }

                            args[expectedTypes.length - 1] = varargs;
                        }
                    }
                }
            }

            if (expectedTypes.length == args.length) {
                Object[] castedArgs = new Object[args.length];
                for (int i = 0; i < args.length; i++) {
                    Object arg = args[i];
                    Class<?> expectedType = expectedTypes[i];
                    if (arg == null) {
                        castedArgs[i] = null;
                    } else if (arg == Undefined.instance) {
                        castedArgs[i] = null;
                    } else if (arg instanceof ConsString) {
                        castedArgs[i] = ((ConsString) arg).toString();
                    } else if (arg instanceof Double
                            && (expectedType.equals(Integer.class) || expectedType.equals(int.class)
                                    || expectedType.equals(Long.class) || expectedType.equals(long.class))) {
                        castedArgs[i] = ((Double) arg).intValue();
                    } else {
                        castedArgs[i] = WindowJavaScriptEngine.jsToJava(arg);
                        //castedArgs[i] = Context.jsToJava(castedArgs[i], expectedType);
                    }

                    castedArgs[i] = ClassField.wrap(expectedTypes[i], castedArgs[i]);
                }

                return castedArgs;
            }
        }

        return null;
    }

    public static String getFunctionName(Function function) {
        if (function instanceof BaseFunction) {
            return "function " + ((BaseFunction) function).getFunctionName() + "()";
        } else {
            return "function ()";
        }
    }

    /**
     * Calls JavaScript native function.
     * 
     * @param function Function to be called.
     * @param args Call arguments.
     */
    public static void call(final Function function, final Object... args) {
        // FIXME?: Should not be here an origin check?

        Scriptable scope = function.getParentScope();
        ObjectTopLevel topLevel = WindowJavaScriptEngine.getObjectTopLevel(scope);
        if (topLevel == null) {
            throw new InternalException("Top level scope is null");
        }

        BrowserScriptEngine engine = topLevel.getBrowserScriptEngine();
        WindowScriptEngine windowEngine = null;
        if (engine instanceof WindowScriptEngine) {
            windowEngine = (WindowScriptEngine) engine;
        } else {
            throw new InternalException("Scripting engine is not WindowScriptEngine");
        }

        Window window = windowEngine.getWindow();
        WindowScriptSettings settings = window.getScriptSettings();
        Html5DocumentImpl document = window.getDocumentImpl();
        final URL address = document.getAddress();

        FunctionInvocation invocation = new FunctionInvocation() {
            @Override
            public Object getThiz() {
                return function;
            }

            @Override
            public String getName() {
                return "";
            }

            @Override
            public Object[] getArgs() {
                return args;
            }

            @Override
            public URL getOrigin() {
                return address;
            }
        };

        InvokeWindowScript script = new InvokeWindowScript(invocation, address,
                WindowJavaScriptEngine.JAVASCRIPT_LANGUAGE, settings, false);
        ScriptException scriptException = script.getException();

        if (scriptException != null) {
            document.reportScriptError(script);
        }

        /*Scriptable scope = function.getParentScope();
        ObjectTopLevel topLevel = getObjectTopLevel(scope);
        if (topLevel != null) {
           Context cx = topLevel.getBrowserScriptEngine().enterContext();
           try {
        Object arg = WindowJavaScriptEngine.javaToJS(event, scope);
        Object[] args = {arg};
        function.call(cx, scope, scope, args);
           } catch (Exception ex) {
        try {
           WindowJavaScriptEngine.throwWrappedScriptException(ex);
        } catch (ScriptException e) {
           throw new WrappedException(e);
        }
           } finally {
        Context.exit();
           }
        }*/
    }
}