org.richfaces.tests.metamer.ftest.MatrixConfigurator.java Source code

Java tutorial

Introduction

Here is the source code for org.richfaces.tests.metamer.ftest.MatrixConfigurator.java

Source

/*******************************************************************************
 * JBoss, Home of Professional Open Source
 * Copyright 2010-2013, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *******************************************************************************/
package org.richfaces.tests.metamer.ftest;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.regex.Pattern;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.jboss.test.selenium.listener.TestMethodSelector;
import org.richfaces.tests.metamer.Template;
import org.richfaces.tests.metamer.TemplatesList;
import org.richfaces.tests.metamer.ftest.annotations.Inject;
import org.richfaces.tests.metamer.ftest.annotations.Templates;
import org.richfaces.tests.metamer.ftest.annotations.Use;
import org.richfaces.tests.metamer.ftest.annotations.Uses;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.IMethodInstance;
import org.testng.IMethodInterceptor;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.annotations.ITestAnnotation;

/**
 * @author <a href="mailto:lfryc@redhat.com">Lukas Fryc</a>
 */
public class MatrixConfigurator extends TestMethodSelector
        implements IInvokedMethodListener, IMethodInterceptor, ITestListener {

    static Map<Field, Object> currentConfiguration;

    Map<Class<?>, Map<Method, Configuration>> configurations = new HashMap<Class<?>, Map<Method, Configuration>>();
    LinkedList<Method> methods = new LinkedList<Method>();
    boolean methodConfigured = false;
    Field templatesField = null;

    public static Map<Field, Object> getCurrentConfiguration() {
        return currentConfiguration;
    }

    public List<IMethodInstance> intercept(List<IMethodInstance> methodInstances, ITestContext context) {
        List<IMethodInstance> result = new LinkedList<IMethodInstance>();
        for (IMethodInstance methodInstance : methodInstances) {

            for (int i = 0; i < methodInstance.getMethod().getInvocationCount(); i++) {
                methods.add(methodInstance.getMethod().getConstructorOrMethod().getMethod());
            }

            if (methodInstance.getMethod().getInvocationCount() > 0) {
                result.add(methodInstance);
            }
        }
        return result;
    }

    public void beforeInvocation(IInvokedMethod invokedMethod, ITestResult testResult) {
        if (!methodConfigured) {
            if (isAfterConfiguration(invokedMethod.getTestMethod())) {
                return;
            }

            if (invokedMethod.getTestMethod().isBeforeSuiteConfiguration()) {
                return;
            }

            configureMethod(testResult);

            methodConfigured = true;
        }
    }

    public void onTestStart(ITestResult result) {
        if (!methodConfigured) {
            configureMethod(result);
            methodConfigured = true;
        }
    }

    public void onTestSuccess(ITestResult result) {
        methodConfigured = false;
    }

    public void onTestFailure(ITestResult result) {
        methodConfigured = false;
    }

    public void onTestSkipped(ITestResult result) {
        if (!methodConfigured) {
            configureMethod(result);
        }
        methodConfigured = false;
    }

    public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
        methodConfigured = false;
    }

    private boolean isAfterConfiguration(ITestNGMethod m) {
        return m.isAfterClassConfiguration() || m.isAfterGroupsConfiguration() || m.isAfterMethodConfiguration()
                || m.isAfterSuiteConfiguration() || m.isAfterTestConfiguration();
    }

    protected Method getCurrentRealMethod() {
        return ((Queue<Method>) methods).poll();
    }

    protected void configureMethod(ITestResult testResult) {
        Method realMethod = getCurrentRealMethod();

        if (realMethod == null) {
            throw new IllegalStateException("can't find more configured methods");
        }

        Class<?> realClass = realMethod.getDeclaringClass();

        final Object testInstance = testResult.getInstance();
        Configuration configuration = configurations.get(realClass).get(realMethod);
        if (!configuration.hasNext()) {
            throw new IllegalStateException("can't find more configurations");
        }
        currentConfiguration = configuration.next();

        try {
            for (Entry<Field, Object> entry : currentConfiguration.entrySet()) {
                final Field field = entry.getKey();
                final Object assignment = entry.getValue();

                setDeclaredFieldValue(testInstance, field, assignment);
            }
        } catch (Exception e) {
            throw new IllegalStateException("Cannot configure method", e);
        }
    }

    public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor,
            Method testMethod) {
        if (testMethod != null) {
            super.transform(annotation, testClass, testConstructor, testMethod);
            int invocationCount = createConfiguration(testMethod.getDeclaringClass(), testMethod);
            annotation.setInvocationCount(invocationCount);
        }
    }

    public int createConfiguration(Class<?> realClass, Method realMethod) {

        Map<Field, List<? extends Object>> parameters = new LinkedHashMap<Field, List<? extends Object>>();

        List<Field> unsatisfied = new LinkedList<Field>();

        // get a list of satisfied and unsatisfied parameters/injections
        Field[] allFields = getAllFields(realClass);
        for (Field field : allFields) {
            if (field.getAnnotation(Inject.class) != null) {
                if (field.getAnnotation(Use.class) != null) {
                    parameters.put(field,
                            getUseParameter(realClass, field.getType(), field.getAnnotation(Use.class)));
                } else if (field.getAnnotation(Templates.class) != null) {
                    parameters.put(field, getTemplatesParameter(realClass, field.getType(),
                            field.getAnnotation(Templates.class), parameters));
                    templatesField = field;
                } else {
                    parameters.put(field, null);
                    unsatisfied.add(field);
                }
            }
        }

        if (templatesField == null) {
            throw new IllegalStateException("there is no field annotated @Templates in " + realClass.getName());
        }

        try {
            // fulfill parameters by super classes' annotations
            fulfillParametersFromAnnotations(realClass, parameters, unsatisfied,
                    getAnnotationsForAllSuperClasses(realClass));

            // fulfill parameters by method annotations
            fulfillParametersFromAnnotations(realClass, parameters, unsatisfied, realMethod.getAnnotations());
        } finally {
            templatesField = null;
        }

        if (!unsatisfied.isEmpty()) {
            throw new IllegalStateException("cannot satisfy following injection points: " + unsatisfied.toString());
        }

        Configuration configuration = new Configuration(parameters);

        int count = 0;
        while (configuration.hasNext()) {
            configuration.next();
            count += 1;
        }
        configuration.reset();

        getClassConfigurations(realClass).put(realMethod, configuration);

        if (parameters.size() == 0) {
            return 1;
        }

        return count;
    }

    private void fulfillParametersFromAnnotations(Class<?> testClass, Map<Field, List<? extends Object>> parameters,
            List<Field> unsatisfied, Annotation... annotations) {
        for (Annotation annotation : annotations) {
            Use[] useAnnotations = null;
            if (annotation.annotationType() == Uses.class) {
                useAnnotations = ((Uses) annotation).value();
            }
            if (annotation.annotationType() == Use.class) {
                useAnnotations = new Use[] { (Use) annotation };
            }
            if (useAnnotations != null) {
                for (Use useAnnotation : useAnnotations) {
                    for (Field field : parameters.keySet()) {
                        if (field.getName().equals(useAnnotation.field())) {
                            parameters.put(field, getUseParameter(testClass, field.getType(), useAnnotation));
                            unsatisfied.remove(field);
                        }
                    }
                }
            }
            if (annotation.annotationType() == Templates.class) {
                Templates templatesAnnotation = (Templates) annotation;
                parameters.put(templatesField, getTemplatesParameter(testClass, templatesField.getType(),
                        templatesAnnotation, parameters));
            }
        }
    }

    private Field[] getAllFields(Class<?> testClass) {
        List<Field> fields = new ArrayList<Field>();

        Class<?> currentClass = testClass;
        while (currentClass != Object.class) {
            fields.addAll(Arrays.asList(currentClass.getDeclaredFields()));
            if (currentClass == AbstractMetamerTest.class) {
                break;
            }
            currentClass = currentClass.getSuperclass();
        }

        return fields.toArray(new Field[fields.size()]);
    }

    private Annotation[] getAnnotationsForAllSuperClasses(Class<?> testClass) {
        List<Annotation> annotations = new LinkedList<Annotation>();

        Class<?> currentClass = testClass;
        while (currentClass != Object.class) {
            annotations.addAll(Arrays.asList(currentClass.getAnnotations()));
            if (currentClass == AbstractMetamerTest.class) {
                break;
            }
            currentClass = currentClass.getSuperclass();
        }

        // needs to be returned in reversed order because of the lowel level annnotations needs to be processed first
        Collections.reverse(annotations);

        return annotations.toArray(new Annotation[annotations.size()]);
    }

    private Map<Method, Configuration> getClassConfigurations(Class<?> realClass) {
        if (!configurations.containsKey(realClass)) {
            configurations.put(realClass, new HashMap<Method, Configuration>());
        }
        return configurations.get(realClass);
    }

    private List<? extends Object> getUseParameter(Class<?> testClass, Class<?> parameterType, Use useAnnotation) {

        List<Object> result = new LinkedList<Object>();

        if (useAnnotation.empty()) {
            result.addAll(Arrays.asList(new Object[] { null }));
        }

        if (useAnnotation.ints().length > 0) {
            if (parameterType == int.class || parameterType == Integer.class) {
                result.addAll(Arrays.asList(ArrayUtils.toObject(useAnnotation.ints())));
            }
        }

        if (useAnnotation.decimals().length > 0) {
            if (parameterType == double.class || parameterType == Double.class) {
                result.addAll(Arrays.asList(ArrayUtils.toObject(useAnnotation.decimals())));
            }
        }

        if (useAnnotation.strings().length > 0) {
            if (parameterType == String.class) {
                result.addAll(Arrays.asList(useAnnotation.strings()));
            }
        }

        if (useAnnotation.booleans().length > 0) {
            if (parameterType == boolean.class || parameterType == Boolean.class) {
                result.addAll(Arrays.asList(ArrayUtils.toObject(useAnnotation.booleans())));
            }
        }

        if (useAnnotation.enumeration()) {
            if (!parameterType.isEnum()) {
                throw new IllegalArgumentException(parameterType + "have to be enumeration");
            }

            result.addAll(Arrays.asList(parameterType.getEnumConstants()));
        }

        // tries satisfy parameter from fields
        if (result.isEmpty()) {
            Object testInstance = getTestInstance(testClass);
            for (int i = 0; i < useAnnotation.value().length; i++) {
                boolean satisfied = false;
                String namePattern = useAnnotation.value()[i];
                namePattern = StringUtils.replace(namePattern, "*", ".+");
                namePattern = StringUtils.replace(namePattern, "?", ".");

                for (Field field : getAllFields(testClass)) {
                    Pattern pattern = Pattern.compile(namePattern);
                    if (pattern.matcher(field.getName()).matches()) {
                        boolean isArray = field.getType().isArray();
                        Class<?> representedType = field.getType();
                        if (!parameterType.isAssignableFrom(representedType) && isArray) {
                            representedType = field.getType().getComponentType();
                        } else {
                            isArray = false;
                        }
                        if (parameterType.isAssignableFrom(representedType)) {
                            Object[] assignments = getDeclaredFieldValues(testInstance, field, isArray);
                            for (Object assignment : assignments) {
                                result.add(assignment);
                            }
                            satisfied = true;
                        } else {
                            throw new IllegalStateException(
                                    "cannot satisfy parameter with declared field '" + field.getName() + "'");
                        }
                    }
                }
                if (satisfied) {
                    continue;
                }
                throw new IllegalStateException(
                        "cannot find the field satysfying injection point with name pattern: "
                                + useAnnotation.value()[i]);
            }
        }

        if (useAnnotation.useNull()) {
            if (parameterType.isPrimitive()) {
                throw new IllegalArgumentException("parameterType is primitive, can't use null value");
            }

            if (result.contains(null)) {
                result.addAll(null);
            }
        }

        return result;
    }

    private Object getTestInstance(Class<?> testClass) {
        try {
            return testClass.newInstance();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private void setDeclaredFieldValue(Object testInstance, Field field, Object assignment)
            throws IllegalArgumentException, IllegalAccessException {
        boolean isAccessible = field.isAccessible();
        if (!isAccessible) {
            field.setAccessible(true);
        }
        field.set(testInstance, assignment);
        field.setAccessible(isAccessible);
    }

    private Object[] getDeclaredFieldValues(Object testInstance, Field field, boolean isArray) {
        try {
            boolean isAccessible = field.isAccessible();
            if (!isAccessible) {
                field.setAccessible(true);
            }
            Object[] result;
            if (isArray) {
                result = (Object[]) field.get(testInstance);
            } else {
                result = new Object[] { field.get(testInstance) };
            }
            field.setAccessible(isAccessible);
            return result;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private List<TemplatesList> getTemplatesParameter(Class<?> testClass, Class<?> parameterType,
            Templates templatesAnnotation, Map<Field, List<? extends Object>> parameters) {
        if (parameterType != TemplatesList.class) {
            throw new IllegalStateException(
                    "only " + Template.class.getName() + " fields can be annotated with @Inject @Templates");
        }

        List<TemplatesList> templates = null;

        // if templates was already set, load them
        if (templatesField != null) {
            templates = (List<TemplatesList>) parameters.get(templatesField);
        }

        // get user defined templates
        List<TemplatesList> userTemplates = MetamerProperties.getTemplates();

        // if "value" is defined, takes precedens
        if (templatesAnnotation.value().length > 0) {
            templates = MetamerProperties.parseTemplates(templatesAnnotation.value());
        }

        // include all "include" to current list
        if (templatesAnnotation.include().length > 0) {
            List<TemplatesList> includeTemplates = MetamerProperties.parseTemplates(templatesAnnotation.include());
            if (templates == null) {
                templates = includeTemplates;
            } else {
                includeTemplates.removeAll(templates);
                templates.addAll(includeTemplates);
            }
        }

        // exclude all "exclude" from current list
        if (templatesAnnotation.exclude().length > 0) {
            List<TemplatesList> excludeTemplates = MetamerProperties.parseTemplates(templatesAnnotation.exclude());
            if (templates != null) {
                templates.removeAll(excludeTemplates);
            }
        }

        // remove all templates which wasn't in user defined set
        if (userTemplates != null) {
            if (templates == null) {
                templates = userTemplates;
            } else {
                templates.retainAll(userTemplates);
            }
        }

        return templates;
    }

    public class Configuration implements Iterator<Map<Field, Object>> {
        Map<Field, List<? extends Object>> parameters;
        Map<Field, Queue<? extends Object>> queues;
        Queue<? extends Object> lastQueue;

        public Configuration(Map<Field, List<? extends Object>> parameters) {
            this.parameters = parameters;
            reset();
        }

        public void reset() {
            queues = new LinkedHashMap<Field, Queue<? extends Object>>();
            for (Entry<Field, List<? extends Object>> entry : parameters.entrySet()) {
                final Field field = entry.getKey();
                List<? extends Object> parameter = entry.getValue();

                lastQueue = new LinkedList<Object>(parameter);
                queues.put(field, lastQueue);
            }
        }

        public Map<Field, Object> next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }

            Map<Field, Object> pass = new HashMap<Field, Object>();

            for (Entry<Field, Queue<? extends Object>> entry : queues.entrySet()) {
                final Field field = entry.getKey();
                final Queue<? extends Object> queue = entry.getValue();

                pass.put(field, queue.peek());
            }

            for (Entry<Field, Queue<? extends Object>> entry : queues.entrySet()) {
                final Field field = entry.getKey();
                final Queue<? extends Object> queue = entry.getValue();

                queue.poll();

                if (queue.isEmpty() && hasNext()) {
                    List<? extends Object> parameter = parameters.get(field);
                    queues.put(field, new LinkedList<Object>(parameter));
                } else {
                    break;
                }
            }

            return pass;
        }

        public boolean hasNext() {
            return !parameters.isEmpty() && !lastQueue.isEmpty();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    public void onStart(ITestContext context) {
    }

    public void onFinish(ITestContext context) {
    }

    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
    }
}