minium.web.internal.ExpressionInvocationHandler.java Source code

Java tutorial

Introduction

Here is the source code for minium.web.internal.ExpressionInvocationHandler.java

Source

/*
 * Copyright (C) 2015 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 minium.web.internal;

import java.lang.reflect.Method;
import java.util.Set;

import minium.BasicElements;
import minium.Elements;
import minium.internal.ElementsFactory;
import minium.internal.InternalElementsFactory;
import minium.internal.Reflections;
import minium.web.DocumentWebDriver;
import minium.web.MultipleDocumentDriversFoundException;
import minium.web.NoDocumentDriverFoundException;
import minium.web.WebElements;
import minium.web.internal.drivers.JavascriptInvoker;
import minium.web.internal.expression.Coercer;
import minium.web.internal.expression.Expression;
import minium.web.internal.expression.FunctionInvocationExpression;
import minium.web.internal.expression.VariableGenerator;

import com.google.common.base.Defaults;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.reflect.AbstractInvocationHandler;
import com.google.common.reflect.TypeToken;

public class ExpressionInvocationHandler<T extends WebElements> extends AbstractInvocationHandler {

    private static final Method SIZE_METHOD = Reflections.getDeclaredMethod(BasicElements.class, "size");

    private static class DocumentDriverInvoker implements Function<DocumentWebDriver, Object> {

        private final JavascriptInvoker javascriptInvoker;
        private final Expression expression;

        private boolean initialized;
        private String expressionJavascript;
        private Object[] expressionArgs;

        public DocumentDriverInvoker(JavascriptInvoker javascriptInvoker, Expression expression) {
            this.javascriptInvoker = javascriptInvoker;
            this.expression = expression;
        }

        @Override
        public Object apply(DocumentWebDriver documentDriver) {
            if (!initialized) {
                VariableGenerator varGenerator = new VariableGenerator.Impl();
                expressionJavascript = expression.getJavascript(varGenerator);
                expressionArgs = expression.getArgs();
                initialized = true;
            }
            return javascriptInvoker.invokeExpression(documentDriver, expressionJavascript, expressionArgs);
        }
    }

    @SuppressWarnings("serial")
    private final TypeToken<T> typeVariableToken = new TypeToken<T>(getClass()) {
    };

    private final ElementsFactory<?> factory;
    private final Coercer coercer;

    public ExpressionInvocationHandler(ElementsFactory<?> factory, Coercer coercer) {
        this.factory = factory;
        this.coercer = coercer;
    }

    @Override
    protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable {
        Preconditions.checkArgument(proxy instanceof WebElements);
        T parent = ((WebElements) proxy).as(typeVariableToken);
        ExpressionWebElements webElements = factory.as(InternalElementsFactory.class)
                .createMixin(parent, new DefaultExpressionWebElements<T>(method.getName(), args))
                .as(ExpressionWebElements.class);

        Class<?> returnClazz = method.getReturnType();

        if (returnClazz != Object.class && Elements.class.isAssignableFrom(returnClazz)) {
            return webElements.as(typeVariableToken);
        }

        JavascriptInvoker javascriptInvoker = parent.as(HasJavascriptInvoker.class).javascriptInvoker();
        Object result;

        if (returnClazz == Void.TYPE) {
            Iterable<DocumentWebDriver> documentDrivers = parent.as(InternalWebElements.class).documentDrivers();
            DocumentDriverInvoker documentDriverInvoker = new DocumentDriverInvoker(javascriptInvoker,
                    webElements.getExpression());
            for (DocumentWebDriver documentDriver : documentDrivers) {
                documentDriverInvoker.apply(documentDriver);
            }
            result = null;
        } else {
            // materialize document drivers
            Set<DocumentWebDriver> documentDrivers = Sets
                    .newLinkedHashSet(parent.as(InternalWebElements.class).documentDrivers());

            switch (documentDrivers.size()) {
            case 0:
                // special case for size, if no document was found then size is
                // 0 for sure
                if (method.equals(SIZE_METHOD)) {
                    return 0;
                }

                throw new NoDocumentDriverFoundException(
                        String.format("The expression %s has no frame or window to be evaluated to", parent));
            case 1:
                result = getSingleDocumentDriverResult(Iterables.get(documentDrivers, 0), javascriptInvoker,
                        webElements);
                break;
            default:
                Expression parentExpression = parent.as(ExpressionWebElements.class).getExpression();
                Expression sizeExpression = new FunctionInvocationExpression(parentExpression, "size");

                DocumentWebDriver webDriverWithResults = getCandidateDocumentDriver(javascriptInvoker,
                        sizeExpression, documentDrivers);

                if (webDriverWithResults != null) {
                    DocumentDriverInvoker documentDriverInvoker = new DocumentDriverInvoker(javascriptInvoker,
                            webElements.getExpression());
                    result = documentDriverInvoker.apply(webDriverWithResults);
                } else {
                    result = Defaults.defaultValue(returnClazz);
                }
                break;
            }
        }

        return coercer.coerce(result, method.getGenericReturnType());
    }

    protected DocumentWebDriver getCandidateDocumentDriver(JavascriptInvoker javascriptInvoker,
            Expression sizeExpression, Iterable<DocumentWebDriver> documentDrivers) {
        DocumentWebDriver webDriverWithResults = null;

        boolean multipleCandidates = false;
        for (DocumentWebDriver candidate : documentDrivers) {
            DocumentDriverInvoker documentDriverInvoker = new DocumentDriverInvoker(javascriptInvoker,
                    sizeExpression);
            long size = (Long) documentDriverInvoker.apply(candidate);
            if (size > 0) {
                if (webDriverWithResults == null) {
                    webDriverWithResults = candidate;
                } else {
                    multipleCandidates = true;
                }
            }
        }

        if (multipleCandidates) {
            throw new MultipleDocumentDriversFoundException(
                    "Several frames or windows match the same expression, so value cannot be computed");
        }
        return webDriverWithResults;
    }

    protected Object getSingleDocumentDriverResult(DocumentWebDriver documentDriver,
            JavascriptInvoker javascriptInvoker, ExpressionWebElements webElements) {
        DocumentDriverInvoker documentDriverInvoker = new DocumentDriverInvoker(javascriptInvoker,
                webElements.getExpression());
        return documentDriverInvoker.apply(documentDriver);
    }
}