All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gargoylesoftware.htmlunit.javascript.ScriptableWrapper Maven / Gradle / Ivy

There is a newer version: 2.70.0
Show newest version
/*
 * Copyright (c) 2002-2021 Gargoyle Software Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gargoylesoftware.htmlunit.javascript;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;

import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.FunctionObject;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;

/**
 * Simple wrapper to make "normal" object scriptable according to specific configuration
 * and allowing use of index properties.
 * TODO: Configuration of the
 * properties and functions should occur from the XML configuration according to
 * the browser to simulate.
 *
 * @author Marc Guillemot
 * @author Ronald Brill
 */
public class ScriptableWrapper extends ScriptableObject {
    private static final Class[] METHOD_PARAMS_INT = {Integer.TYPE};
    private static final Class[] METHOD_PARAMS_STRING = {String.class};

    private final Map properties_ = new HashMap<>();

    private Method getByIndexMethod_;
    private final Object javaObject_;
    private final String jsClassName_;
    private Method getByNameFallback_;

    /**
     * Constructs a wrapper for the java object.
     * @param scope the scope of the executing script
     * @param javaObject the javaObject to wrap
     * @param staticType the static type of the object
     */
    public ScriptableWrapper(final Scriptable scope, final Object javaObject,
            final Class staticType) {
        javaObject_ = javaObject;
        setParentScope(scope);

        // all these information should come from the XML js configuration file
        // just for a first time...
        if (NodeList.class.equals(staticType)
                || NamedNodeMap.class.equals(staticType)) {
            try {
                jsClassName_ = staticType.getSimpleName();

                // is there a better way that would avoid to keep local
                // information?
                // it seems that defineProperty with only accepts delegate if
                // its method takes a ScriptableObject
                // as parameter.
                final Method length = javaObject.getClass().getMethod("getLength", ArrayUtils.EMPTY_CLASS_ARRAY);
                properties_.put("length", length);

                final Method item = javaObject.getClass().getMethod("item", METHOD_PARAMS_INT);
                defineProperty("item", new MethodWrapper("item", staticType, METHOD_PARAMS_INT), 0);

                final Method toString = getClass().getMethod("jsToString", ArrayUtils.EMPTY_CLASS_ARRAY);
                defineProperty("toString", new FunctionObject("toString", toString, this), 0);

                getByIndexMethod_ = item;

                if (NamedNodeMap.class.equals(staticType)) {
                    final Method getNamedItem = javaObject.getClass().getMethod("getNamedItem", METHOD_PARAMS_STRING);
                    defineProperty("getNamedItem",
                            new MethodWrapper("getNamedItem", staticType, METHOD_PARAMS_STRING), 0);

                    getByNameFallback_ = getNamedItem;
                }
            }
            catch (final Exception e) {
                throw new RuntimeException("Method not found", e);
            }
        }
        else {
            throw new RuntimeException("Unknown type: " + staticType.getName());
        }
    }

    /**
     * {@inheritDoc}
     * @see ScriptableObject#get(java.lang.String,net.sourceforge.htmlunit.corejs.javascript.Scriptable)
     */
    @Override
    public Object get(final String name, final Scriptable start) {
        final Method propertyGetter = properties_.get(name);
        final Object response;
        if (propertyGetter != null) {
            response = invoke(propertyGetter);
        }
        else {
            final Object fromSuper = super.get(name, start);
            if (fromSuper != Scriptable.NOT_FOUND) {
                response = fromSuper;
            }
            else {
                final Object byName = invoke(getByNameFallback_, new Object[] {name});
                if (byName != null) {
                    response = byName;
                }
                else {
                    response = Scriptable.NOT_FOUND;
                }
            }
        }

        return Context.javaToJS(response, ScriptableObject
                .getTopLevelScope(start));
    }

    /**
     * {@inheritDoc}
     * @see ScriptableObject#has(java.lang.String, net.sourceforge.htmlunit.corejs.javascript.Scriptable)
     */
    @Override
    public boolean has(final String name, final Scriptable start) {
        return properties_.containsKey(name) || super.has(name, start);
    }

    /**
     * Invokes the method on the wrapped object.
     * @param method the method to invoke
     * @return the invocation result
     */
    protected Object invoke(final Method method) {
        return invoke(method, ArrayUtils.EMPTY_OBJECT_ARRAY);
    }

    /**
     * Invokes the method on the wrapped object.
     * @param method the method to invoke
     * @param args the argument to pass to the method
     * @return the invocation result
     */
    protected Object invoke(final Method method, final Object[] args) {
        try {
            return method.invoke(javaObject_, args);
        }
        catch (final Exception e) {
            throw new RuntimeException(
                    "Invocation of method on java object failed", e);
        }
    }

    /**
     * {@inheritDoc}
     * @see ScriptableObject#get(int, net.sourceforge.htmlunit.corejs.javascript.Scriptable)
     */
    @Override
    public Object get(final int index, final Scriptable start) {
        if (getByIndexMethod_ != null) {
            final Object byIndex = invoke(getByIndexMethod_, new Object[] {Integer.valueOf(index)});
            return Context.javaToJS(byIndex, ScriptableObject.getTopLevelScope(start));
        }
        return super.get(index, start);
    }

    /**
     * {@inheritDoc}
     * @see ScriptableObject#getDefaultValue(java.lang.Class)
     */
    @Override
    public Object getDefaultValue(final Class hint) {
        if (String.class.equals(hint) || hint == null) {
            return jsToString();
        }
        return super.getDefaultValue(hint);
    }

    /**
     * To use as "toString" function in JavaScript.
     * @return the string representation
     */
    public String jsToString() {
        return "[object " + getClassName() + "]";
    }

    /**
     * {@inheritDoc}
     * @see ScriptableObject#getClassName()
     */
    @Override
    public String getClassName() {
        return jsClassName_;
    }

    /**
     * Gets the java object made availabe to JavaScript through this wrapper.
     * @return the wrapped object
     */
    public Object getWrappedObject() {
        return javaObject_;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy