sf.net.experimaestro.manager.js.JSBaseObject.java Source code

Java tutorial

Introduction

Here is the source code for sf.net.experimaestro.manager.js.JSBaseObject.java

Source

package sf.net.experimaestro.manager.js;

/*
 * This file is part of experimaestro.
 * Copyright (c) 2014 B. Piwowarski <benjamin@bpiwowar.net>
 *
 * experimaestro 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 version 3 of the License, or
 * (at your option) any later version.
 *
 * experimaestro 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 Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with experimaestro.  If not, see <http://www.gnu.org/licenses/>.
 */

import org.apache.commons.lang.NotImplementedException;
import org.mozilla.javascript.*;
import org.w3c.dom.Node;
import sf.net.experimaestro.exceptions.XPMRhinoException;
import sf.net.experimaestro.manager.json.Json;
import sf.net.experimaestro.manager.scripting.ClassDescription;
import sf.net.experimaestro.manager.scripting.ConstructorFunction;
import sf.net.experimaestro.manager.scripting.Exposed;
import sf.net.experimaestro.manager.scripting.MethodFunction;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.ArrayList;

import static java.lang.String.format;

/**
 * Base class for all JS objects implementations
 *
 * @author B. Piwowarski <benjamin@bpiwowar.net>
 * @date 27/11/12
 */
abstract public class JSBaseObject implements Scriptable, JSConstructable, Callable {
    private XPMObject xpm;

    private ClassDescription classDescription;
    private Scriptable prototype;
    private Scriptable parentScope;

    public JSBaseObject() {
        final Class<? extends JSBaseObject> aClass = getClass();
        this.classDescription = ClassDescription.analyzeClass(aClass);
    }

    public JSBaseObject(Class<?> wrappedClass) {
        this.classDescription = ClassDescription.analyzeClass(wrappedClass);
    }

    /**
     * Create a new JS object
     */
    public static <T> T newObject(Context cx, Scriptable scope, Class<T> aClass, Object... args) {
        return (T) cx.newObject(scope, ClassDescription.getClassName(aClass), args);
    }

    /**
     * Get the XPM object (thread)
     */
    final static XPMObject xpm() {
        return XPMObject.getThreadXPM();
    }

    /**
     * Defines a new class.
     * <p/>
     * Used in order to plug our class constructor {@linkplain sf.net.experimaestro.manager.js.JSBaseObject.MyNativeJavaClass}
     * if the object is a {@linkplain sf.net.experimaestro.manager.js.JSBaseObject}
     *
     * @param scope
     * @param aClass
     * @throws IllegalAccessException
     * @throws java.lang.reflect.InvocationTargetException
     * @throws InstantiationException
     */
    public static void defineClass(Scriptable scope, Class<?> aClass)
            throws IllegalAccessException, InvocationTargetException, InstantiationException {
        if (Scriptable.class.isAssignableFrom(aClass)) {
            // If not a JSObject descendent, we handle this with standard JS procedure
            if (JSConstructable.class.isAssignableFrom(aClass)) {
                // Use our own constructor
                final String name = ClassDescription.getClassName(aClass);
                scope = ScriptableObject.getTopLevelScope(scope);
                final NativeJavaClass nativeJavaClass = new MyNativeJavaClass(scope,
                        (Class<? extends Scriptable>) aClass);
                scope.put(name, scope, nativeJavaClass);
            } else {
                ScriptableObject.defineClass(scope, (Class<? extends Scriptable>) aClass);
            }
        } else {
            final String name = ClassDescription.getClassName(aClass);
            scope.put(name, scope, new WrappedJavaObject.WrappedClass(aClass));
        }
    }

    @Override
    public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
        MethodFunction function = getMethodFunction(null);
        if (function.isEmpty())
            throw new XPMRhinoException("Cannot call object of type %s", getClassName());
        JavaScriptContext jcx = new JavaScriptContext(cx, scope);
        XPMObject xpm = XPMObject.getThreadXPM();
        return function.call(jcx, xpm != null ? xpm.getScriptContext() : null, thisObj, args);
    }

    @Override
    public String getClassName() {
        return ClassDescription.getClassName(classDescription.getWrappedClass());
    }

    @Override
    public Object get(String name, Scriptable start) {
        // Search for a function
        MethodFunction function = getMethodFunction(name);
        if (!function.isEmpty()) {
            return new JavaScriptFunction(function);
        }

        // Search for a property
        final Field field = classDescription.getFields().get(name);
        if (field != null) {
            try {
                return field.get(this);
            } catch (IllegalAccessException e) {
                throw new XPMRhinoException("Illegal access to field [%s]", field.toString());
            }
        }
        return NOT_FOUND;
    }

    private MethodFunction getMethodFunction(String name) {
        MethodFunction function = new MethodFunction(name);

        ArrayList<Method> methods = this.classDescription.getMethods().get(name);
        if (methods != null && !methods.isEmpty())
            function.add(thisObject(), methods);

        // Walk the prototype chain
        for (Scriptable prototype = getPrototype(); prototype != null; prototype = prototype.getPrototype()) {
            if (prototype instanceof JSBaseObject) {
                JSBaseObject jsPrototype = (JSBaseObject) prototype;
                methods = jsPrototype.classDescription.getMethods().get(name);
                if (methods != null && !methods.isEmpty())
                    function.add(jsPrototype, methods);
            }
        }
        return function;
    }

    /**
     * Returns the underlying object
     *
     * @return An object
     */
    protected Object thisObject() {
        return this;
    }

    @Override
    public Object get(int index, Scriptable start) {
        return NOT_FOUND;
    }

    @Override
    public boolean has(String name, Scriptable start) {
        return classDescription.getMethods().containsKey(name) || classDescription.getFields().containsKey(name);
    }

    @Override
    public boolean has(int index, Scriptable start) {
        return false;
    }

    @Override
    public void put(String name, Scriptable start, Object value) {
        final Field field = classDescription.getFields().get(name);
        if (field != null) {
            if (classDescription.getFields().containsKey(name)) {
                try {
                    field.set(this, value);
                    return;
                } catch (IllegalAccessException e) {
                    throw new XPMRhinoException("Illegal access to field [%s]", field.toString());
                }
            }
        }

        throw new XPMRhinoException("Setting the value of a sealed object (" + getClassName() + ")");
    }

    @Override
    public void put(int index, Scriptable start, Object value) {
        throw new NotImplementedException();
    }

    @Override
    public void delete(String name) {
        throw new NotImplementedException();
    }

    @Override
    public void delete(int index) {
        throw new NotImplementedException();
    }

    @Override
    public Scriptable getPrototype() {
        return prototype;
    }

    @Override
    public void setPrototype(Scriptable prototype) {
        this.prototype = prototype;
    }

    @Override
    public Scriptable getParentScope() {
        return parentScope;
    }

    @Override
    public void setParentScope(Scriptable parent) {
        this.parentScope = parent;
    }

    @Override
    public Object[] getIds() {
        return new Object[] {};
    }

    @Override
    public Object getDefaultValue(Class<?> hint) {
        return toString();
    }

    @Override
    public boolean hasInstance(Scriptable instance) {
        throw new NotImplementedException();
    }

    public byte[] getBytes() {
        return toString().getBytes();
    }

    protected JSBaseObject setXPM(XPMObject xpm) {
        this.xpm = xpm;
        return this;
    }

    /**
     * The Experimaestro wrap factory to handle special cases
     */
    static public class XPMWrapFactory extends WrapFactory {
        public final static XPMWrapFactory INSTANCE = new XPMWrapFactory();

        private XPMWrapFactory() {
            setJavaPrimitiveWrap(false);
        }

        @Override
        public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class<?> staticType) {
            return super.wrapAsJavaObject(cx, scope, javaObject, staticType);
        }

        @Override
        public Scriptable wrapJavaClass(Context cx, Scriptable scope, Class javaClass) {
            return super.wrapJavaClass(cx, scope, javaClass);
        }

        @Override
        public Object wrap(Context cx, Scriptable scope, Object obj, Class<?> staticType) {
            if (obj == null) {
                return Undefined.instance;
            }

            if (obj instanceof Path)
                return new JSPath((Path) obj).setXPM(XPMObject.getXPM(scope));
            if (obj instanceof Node) {
                return new JSNode((Node) obj).setXPM(XPMObject.getXPM(scope));
            }
            if (obj instanceof Json) {
                return new JSJson((Json) obj).setXPM(XPMObject.getXPM(scope));
            }
            if (obj.getClass().getAnnotation(Exposed.class) != null) {
                return new WrappedJavaObject(cx, scope, obj);
            }
            return super.wrap(cx, scope, obj, staticType);
        }

        @Override
        public Scriptable wrapNewObject(Context cx, Scriptable scope, Object obj) {
            if (obj instanceof JSBaseObject)
                return (JSBaseObject) obj;

            if (obj instanceof JSTasks.TaskRef) {
                return (Scriptable) ((JSTasks.TaskRef) obj).get(cx);
            }

            if (obj instanceof Json) {
                return new JSJson((Json) obj).setXPM(XPMObject.getXPM(scope));
            }

            if (obj instanceof Node)
                return new JSNode((Node) obj).setXPM(XPMObject.getXPM(scope));

            if (obj instanceof Path) {
                return new JSPath((Path) obj).setXPM(XPMObject.getXPM(scope));
            }

            return super.wrapNewObject(cx, scope, obj);
        }

    }

    static private class MyNativeJavaObject extends NativeJavaObject {
        private MyNativeJavaObject(Scriptable scope, Object javaObject, Class<?> staticType, boolean isAdapter) {
            super(scope, javaObject, staticType, isAdapter);
        }

        @Override
        public String getClassName() {
            return ClassDescription.getClassName(this.staticType);
        }
    }

    private static class MyNativeJavaClass extends NativeJavaClass {
        public MyNativeJavaClass(Scriptable scriptable, Class<? extends Scriptable> aClass) {
            super(scriptable, aClass);
        }

        @Override
        public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
            ClassDescription description = ClassDescription.analyzeClass((Class) javaObject);
            String className = ClassDescription.getClassName((Class) javaObject);
            ConstructorFunction constructorFunction = new ConstructorFunction(className,
                    description.getConstructors());
            JavaScriptContext jcx = new JavaScriptContext(cx, scope);
            XPMObject threadXPM = XPMObject.getThreadXPM();
            Object object = constructorFunction.call(jcx, threadXPM != null ? threadXPM.getScriptContext() : null,
                    null, args);

            return (Scriptable) object;

        }
    }
}