com.gargoylesoftware.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for com.gargoylesoftware.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration.java

Source

/*
 * Copyright (c) 2002-2016 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
 * http://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.configuration;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.javascript.HtmlUnitScriptable;
import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;

/**
 * An abstract container for all the JavaScript configuration information.
 *
 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
 * @author Chris Erskine
 * @author Ahmed Ashour
 * @author Ronald Brill
 * @author Frank Danek
 */
public abstract class AbstractJavaScriptConfiguration {

    private static final Log LOG = LogFactory.getLog(AbstractJavaScriptConfiguration.class);

    private static final Map<String, String> CLASS_NAME_MAP_ = new HashMap<>();

    private Map<Class<?>, Class<? extends HtmlUnitScriptable>> domJavaScriptMap_;

    private final Map<String, ClassConfiguration> configuration_;

    /**
     * Constructor.
     * @param browser the browser version to use
     */
    protected AbstractJavaScriptConfiguration(final BrowserVersion browser) {
        configuration_ = buildUsageMap(browser);
    }

    /**
     * @return the classes configured by this configuration
     */
    protected abstract Class<? extends SimpleScriptable>[] getClasses();

    /**
     * Gets all the configurations.
     * @return the class configurations
     */
    public Iterable<ClassConfiguration> getAll() {
        return configuration_.values();
    }

    private Map<String, ClassConfiguration> buildUsageMap(final BrowserVersion browser) {
        final Map<String, ClassConfiguration> classMap = new HashMap<>(getClasses().length);

        for (final Class<? extends SimpleScriptable> klass : getClasses()) {
            final ClassConfiguration config = getClassConfiguration(klass, browser);
            if (config != null) {
                classMap.put(config.getClassName(), config);
            }
        }
        return Collections.unmodifiableMap(classMap);
    }

    /**
     * Returns the class configuration of the given {@code klass}.
     *
     * @param klass the class
     * @param browser the browser version
     * @return the class configuration
     */
    public static ClassConfiguration getClassConfiguration(final Class<? extends HtmlUnitScriptable> klass,
            final BrowserVersion browser) {
        if (browser != null) {
            final String expectedBrowserName;
            if (browser.isIE()) {
                expectedBrowserName = "IE";
            } else if (browser.isFirefox()) {
                expectedBrowserName = "FF";
            } else if (browser.isEdge()) {
                expectedBrowserName = "EDGE";
            } else {
                expectedBrowserName = "CHROME";
            }
            final float browserVersionNumeric = browser.getBrowserVersionNumeric();

            final String hostClassName = klass.getName();
            final JsxClasses jsxClasses = klass.getAnnotation(JsxClasses.class);
            if (jsxClasses != null) {
                if (klass.getAnnotation(JsxClass.class) != null) {
                    throw new RuntimeException(
                            "Invalid JsxClasses/JsxClass annotation; class '" + hostClassName + "' has both.");
                }
                final JsxClass[] jsxClassValues = jsxClasses.value();

                final Set<Class<?>> domClasses = new HashSet<>();

                boolean isJsObject = false;
                boolean isDefinedInStandardsMode = false;
                String className = null;
                for (int i = 0; i < jsxClassValues.length; i++) {
                    final JsxClass jsxClass = jsxClassValues[i];

                    if (jsxClass != null
                            && isSupported(jsxClass.browsers(), expectedBrowserName, browserVersionNumeric)) {
                        domClasses.add(jsxClass.domClass());
                        if (jsxClass.isJSObject()) {
                            isJsObject = true;
                        }
                        if (jsxClass.isDefinedInStandardsMode()) {
                            isDefinedInStandardsMode = true;
                        }
                        if (!jsxClass.className().isEmpty()) {
                            className = jsxClass.className();
                        }
                    }
                }

                final ClassConfiguration classConfiguration = new ClassConfiguration(klass,
                        domClasses.toArray(new Class<?>[0]), isJsObject, isDefinedInStandardsMode, className);

                process(classConfiguration, hostClassName, expectedBrowserName, browserVersionNumeric);
                return classConfiguration;
            }

            final JsxClass jsxClass = klass.getAnnotation(JsxClass.class);
            if (jsxClass != null && isSupported(jsxClass.browsers(), expectedBrowserName, browserVersionNumeric)) {

                final Set<Class<?>> domClasses = new HashSet<>();
                final Class<?> domClass = jsxClass.domClass();
                if (domClass != null && domClass != Object.class) {
                    domClasses.add(domClass);
                }

                String className = jsxClass.className();
                if (className.isEmpty()) {
                    className = null;
                }
                final ClassConfiguration classConfiguration = new ClassConfiguration(klass,
                        domClasses.toArray(new Class<?>[0]), jsxClass.isJSObject(),
                        jsxClass.isDefinedInStandardsMode(), className);

                process(classConfiguration, hostClassName, expectedBrowserName, browserVersionNumeric);
                return classConfiguration;
            }
        }
        return null;
    }

    private static void process(final ClassConfiguration classConfiguration, final String hostClassName,
            final String expectedBrowserName, final float browserVersionNumeric) {
        final String simpleClassName = hostClassName.substring(hostClassName.lastIndexOf('.') + 1);

        CLASS_NAME_MAP_.put(hostClassName, simpleClassName);
        final Map<String, Method> allGetters = new HashMap<>();
        final Map<String, Method> allSetters = new HashMap<>();
        for (final Constructor<?> constructor : classConfiguration.getHostClass().getDeclaredConstructors()) {
            for (final Annotation annotation : constructor.getAnnotations()) {
                if (annotation instanceof JsxConstructor) {
                    if (isSupported(((JsxConstructor) annotation).value(), expectedBrowserName,
                            browserVersionNumeric)) {
                        classConfiguration.setJSConstructor(constructor);
                    }
                }
            }
        }
        for (final Method method : classConfiguration.getHostClass().getDeclaredMethods()) {
            for (final Annotation annotation : method.getAnnotations()) {
                if (annotation instanceof JsxGetter) {
                    final JsxGetter jsxGetter = (JsxGetter) annotation;
                    if (isSupported(jsxGetter.value(), expectedBrowserName, browserVersionNumeric)) {
                        String property;
                        if (jsxGetter.propertyName().isEmpty()) {
                            final int prefix = method.getName().startsWith("is") ? 2 : 3;
                            property = method.getName().substring(prefix);
                            property = Character.toLowerCase(property.charAt(0)) + property.substring(1);
                        } else {
                            property = jsxGetter.propertyName();
                        }
                        allGetters.put(property, method);
                    }
                } else if (annotation instanceof JsxSetter) {
                    final JsxSetter jsxSetter = (JsxSetter) annotation;
                    if (isSupported(jsxSetter.value(), expectedBrowserName, browserVersionNumeric)) {
                        String property;
                        if (jsxSetter.propertyName().isEmpty()) {
                            property = method.getName().substring(3);
                            property = Character.toLowerCase(property.charAt(0)) + property.substring(1);
                        } else {
                            property = jsxSetter.propertyName();
                        }
                        allSetters.put(property, method);
                    }
                } else if (annotation instanceof JsxFunction) {
                    if (isSupported(((JsxFunction) annotation).value(), expectedBrowserName,
                            browserVersionNumeric)) {
                        classConfiguration.addFunction(method);
                    }
                } else if (annotation instanceof JsxStaticGetter) {
                    final JsxStaticGetter jsxStaticGetter = (JsxStaticGetter) annotation;
                    if (isSupported(jsxStaticGetter.value(), expectedBrowserName, browserVersionNumeric)) {
                        final int prefix = method.getName().startsWith("is") ? 2 : 3;
                        String property = method.getName().substring(prefix);
                        property = Character.toLowerCase(property.charAt(0)) + property.substring(1);
                        classConfiguration.addStaticProperty(property, method, null);
                    }
                } else if (annotation instanceof JsxStaticFunction) {
                    if (isSupported(((JsxStaticFunction) annotation).value(), expectedBrowserName,
                            browserVersionNumeric)) {
                        classConfiguration.addStaticFunction(method);
                    }
                } else if (annotation instanceof JsxConstructor) {
                    if (isSupported(((JsxConstructor) annotation).value(), expectedBrowserName,
                            browserVersionNumeric)) {
                        classConfiguration.setJSConstructor(method);
                    }
                }
            }
        }
        for (final Field field : classConfiguration.getHostClass().getDeclaredFields()) {
            final JsxConstant jsxConstant = field.getAnnotation(JsxConstant.class);
            if (jsxConstant != null
                    && isSupported(jsxConstant.value(), expectedBrowserName, browserVersionNumeric)) {
                classConfiguration.addConstant(field.getName());
            }
        }
        for (final Entry<String, Method> getterEntry : allGetters.entrySet()) {
            final String property = getterEntry.getKey();
            classConfiguration.addProperty(property, getterEntry.getValue(), allSetters.get(property));
        }
    }

    private static boolean isSupported(final WebBrowser[] browsers, final String expectedBrowserName,
            final float expectedVersionNumeric) {
        for (final WebBrowser browser : browsers) {
            if (browser.value().name().equals(expectedBrowserName) && browser.minVersion() <= expectedVersionNumeric
                    && browser.maxVersion() >= expectedVersionNumeric) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gets the class configuration for the supplied JavaScript class name.
     * @param hostClassName the JavaScript class name
     * @return the class configuration for the supplied JavaScript class name
     */
    public ClassConfiguration getClassConfiguration(final String hostClassName) {
        return configuration_.get(hostClassName);
    }

    /**
     * Returns the class name that the given class implements. If the class is
     * the input class, then the name is extracted from the type that the Input class
     * is masquerading as.
     * FIXME - Implement the Input class processing
     * @param clazz
     * @return the class name
     */
    String getClassnameForClass(final Class<?> clazz) {
        final String name = CLASS_NAME_MAP_.get(clazz.getName());
        if (name == null) {
            throw new IllegalStateException(
                    "Did not find the mapping of the class to the classname for " + clazz.getName());
        }
        return name;
    }

    /**
     * Returns an immutable map containing the DOM to JavaScript mappings. Keys are
     * java classes for the various DOM classes (e.g. HtmlInput.class) and the values
     * are the JavaScript class names (e.g. "HTMLAnchorElement").
     * @return the mappings
     */
    public Map<Class<?>, Class<? extends HtmlUnitScriptable>> getDomJavaScriptMapping() {
        if (domJavaScriptMap_ != null) {
            return domJavaScriptMap_;
        }

        final Map<Class<?>, Class<? extends HtmlUnitScriptable>> map = new HashMap<>(configuration_.size());

        final boolean debug = LOG.isDebugEnabled();
        for (final String hostClassName : configuration_.keySet()) {
            final ClassConfiguration classConfig = getClassConfiguration(hostClassName);
            for (final Class<?> domClass : classConfig.getDomClasses()) {
                // preload and validate that the class exists
                if (debug) {
                    LOG.debug("Mapping " + domClass.getName() + " to " + hostClassName);
                }
                map.put(domClass, classConfig.getHostClass());
            }
        }

        domJavaScriptMap_ = Collections.unmodifiableMap(map);

        return domJavaScriptMap_;
    }
}