com.vilt.minium.impl.JQueryInvoker.java Source code

Java tutorial

Introduction

Here is the source code for com.vilt.minium.impl.JQueryInvoker.java

Source

/*
 * Copyright (C) 2013 The Minium Authors
 *
 * 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.vilt.minium.impl;

import static java.lang.String.format;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Collections2;
import com.google.common.io.CharStreams;
import com.google.common.io.Closeables;
import com.vilt.minium.WebElementsDriver;
import com.vilt.minium.WebElementsException;

/**
 * This class is responsible for injecting the necessary javascript code in the
 * current page so that jQuery expressions can be evaluated.
 *
 * @author Rui
 */
public class JQueryInvoker {

    private static final Logger LOGGER = LoggerFactory.getLogger(JQueryInvoker.class);

    public interface Compressor {
        String compress(ClassLoader classLoader, Collection<String> jsResources);
    }

    private static class NullCompressor implements Compressor {
        @Override
        public String compress(ClassLoader classLoader, Collection<String> jsResources) {
            return combineResources(jsResources);
        }
    }

    private static class ClasspathFileToStringFunction implements Function<String, String> {
        @Override
        public String apply(String input) {
            return getFileContent(input);
        }
    }

    private static Compressor compressor;

    static {
        try {
            Class.forName("com.google.javascript.jscomp.Compiler", false, JQueryInvoker.class.getClassLoader());
        } catch (Exception e) {
            LOGGER.info("Google Closure Compiler not found in classpath, javascript will not be compressed");
            compressor = new NullCompressor();
        } catch (UnsupportedClassVersionError e) {
            LOGGER.info(
                    "Google Closure Compiler found in classpath but not supported by his JVM. Please consider using a more recent JVM or another Google Closure Compiler version");
            compressor = new NullCompressor();
        }
        if (compressor == null) {
            compressor = new ClosureCompressor();
        }
    }

    // resource paths
    private final Collection<String> jsResources;
    private final Collection<String> cssResources;

    // concatenated styles resources content
    private String styles;

    private String lightInvokerScriptTemplate;
    private String fullInvokerScriptTemplate;

    public JQueryInvoker(Collection<String> jsResources, Collection<String> cssResources) {
        this.jsResources = jsResources;
        this.cssResources = cssResources;

        try {
            String jsScripts = compressor.compress(JQueryInvoker.class.getClassLoader(), jsResources);

            if (cssResources != null) {
                styles = combineResources(cssResources);
            }

            lightInvokerScriptTemplate = getFileContent("minium/templates/jquery-invoker-light.template");
            fullInvokerScriptTemplate = getFileContent("minium/templates/jquery-invoker-full.template");
            fullInvokerScriptTemplate = fullInvokerScriptTemplate.replace("{{jsScript}}", jsScripts);

        } catch (Exception e) {
            throw new WebElementsException(e);
        }
    }

    public <T> T invoke(JavascriptExecutor wd, String expression, Object... args) {
        return this.<T>invoke(wd, false, expression, args);
    }

    @SuppressWarnings("unchecked")
    public <T> T invoke(JavascriptExecutor wd, boolean async, String expression, Object... args) {
        try {
            args = convertToValidArgs(args);

            Object[] fullArgs = args == null ? new Object[1] : new Object[args.length + 1];
            fullArgs[0] = async;
            if (args != null)
                System.arraycopy(args, 0, fullArgs, 1, args.length);

            Object result = async ? wd.executeAsyncScript(lightInvokerScript(expression), fullArgs)
                    : wd.executeScript(lightInvokerScript(expression), fullArgs);

            if (result instanceof Boolean && !((Boolean) result).booleanValue()) {
                fullArgs = args == null ? new Object[2] : new Object[args.length + 2];
                fullArgs[0] = async;
                fullArgs[1] = styles;
                if (args != null)
                    System.arraycopy(args, 0, fullArgs, 2, args.length);
                result = async ? wd.executeAsyncScript(fullInvokerScript(expression), fullArgs)
                        : wd.executeScript(fullInvokerScript(expression), fullArgs);
            }

            if (!(result instanceof List))
                throw new IllegalStateException(format("Expected a list with the result in the first position..."));

            List<?> resultAsList = (List<?>) result;
            // results can only be empty if it was a result of a jQuery evaluation
            if (resultAsList.isEmpty())
                return (T) Collections.emptyList();

            if (resultAsList.get(0) instanceof WebElement) {
                // it's an array of web elements
                return (T) resultAsList;
            }

            return (T) resultAsList.get(0);
        } catch (Exception e) {
            throw new WebElementsException(e);
        }
    }

    public Object invokeExpression(WebElementsDriver<?> wd, boolean async, String expression, Object... args) {
        return invoke(wd, async, format("return %s;", expression), args);
    }

    protected String lightInvokerScript(String expression) {
        return lightInvokerScriptTemplate.replace("{{expression}}", expression);
    }

    protected String fullInvokerScript(String expression) {
        return fullInvokerScriptTemplate.replace("{{expression}}", expression);
    }

    protected Collection<String> getJsResources() {
        return jsResources;
    }

    protected Collection<String> getCssResources() {
        return cssResources;
    }

    private Object[] convertToValidArgs(Object[] args) {
        // we need to get the wrapped web element from every DelegateWebElement
        // in arguments
        Object[] validArgs = new Object[args.length];
        System.arraycopy(args, 0, validArgs, 0, args.length);

        for (int i = 0; i < validArgs.length; i++) {
            Object arg = validArgs[i];
            if (arg instanceof DelegateWebElement) {
                validArgs[i] = ((DelegateWebElement) arg).getWrappedWebElement();
            } else if (arg instanceof Object[]) {
                validArgs[i] = convertToValidArgs((Object[]) arg);
            }
        }
        return validArgs;
    }

    protected static String getFileContent(String filename) {
        InputStream in = null;
        try {
            in = getClasspathFileInputStream(filename);
            return CharStreams.toString(new InputStreamReader(in, Charsets.UTF_8));
        } catch (IOException e) {
            throw new WebElementsException(format("Could not load %s from classpath", filename), e);
        } finally {
            try {
                Closeables.close(in, true);
            } catch (IOException e) {
            }
        }
    }

    protected static InputStream getClasspathFileInputStream(String filename) {
        return JQueryInvoker.class.getClassLoader().getResourceAsStream(filename);
    }

    protected static String combineResources(Collection<String> resources) {
        return Joiner.on("\n\n").join(Collections2.transform(resources, new ClasspathFileToStringFunction()));
    }
}