Package org.moresbycoffee.have

Source Code of org.moresbycoffee.have.MByHaveConfigurator

/*
* Moresby Coffee Bean
*
* Copyright (c) 2012, Barnabas Sudy (barnabas.sudy@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the <organization> nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.moresbycoffee.have;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.moresbycoffee.have.StepCandidate.MethodParameter;
import org.moresbycoffee.have.annotations.Given;
import org.moresbycoffee.have.annotations.Then;
import org.moresbycoffee.have.annotations.When;
import org.moresbycoffee.have.exceptions.MByHaveException;

import com.google.common.reflect.TypeToken;
import com.thoughtworks.paranamer.BytecodeReadingParanamer;
import com.thoughtworks.paranamer.CachingParanamer;
import com.thoughtworks.paranamer.Paranamer;

/**
* Utility class to create {@link MByHaveConfiguration configuration} for {@link MByHaveRunner}.
*
* @author Barnabas Sudy (barnabas.sudy@gmail.com)
* @since 2012
*/
public final class MByHaveConfigurator {

    /** Logger. */
    private static final Logger LOG = Logger.getLogger(MByHaveRunner.class.getName());

    private static final Map<Class<? extends Annotation>, StepKeyword>         KEYWORDS;

    static {
        final Map<Class<? extends Annotation>, StepKeyword> mutableKeywords = new HashMap<Class<? extends Annotation>, StepKeyword>();
        mutableKeywords.put(Given.class, new StepKeyword(Given.class, "Given"));
        mutableKeywords.put(When.class,  new StepKeyword(When.class,  "When" ));
        mutableKeywords.put(Then.class,  new StepKeyword(Then.class,  "Then" ));
        KEYWORDS = Collections.unmodifiableMap(mutableKeywords);

    }

    /**
     * Creates a {@link MByHaveConfiguration configuration} by the given testClass. This methos scans the
     * testClass for keyword annotated methods and if the parseJUnitAnnotations is true it will scan for the
     * JUnit {@link BeforeClass}, {@link Before}, {@link After} and {@link AfterClass} annotations.
     *
     * @param testClass The testclass to scan
     * @param parseJUnitAnnotations If it is <tt>true</tt> than JUnit annotated methods also will be picked up.
     * @return The configuration.
     */
    public static MByHaveConfiguration configure(final Class<?> testClass, final boolean parseJUnitAnnotations) {

        final Map<Class<? extends Annotation>, List<StepCandidate>> candidates;

        candidates = initStepCandidates(testClass);

        final List<Method> beforeClassMethods;
        final List<Method> beforeMethods;
        final List<Method> afterMethods;
        final List<Method> afterClassMethods;
        if (parseJUnitAnnotations) {
            beforeClassMethods = getAnnotatedMethods(testClass, BeforeClass.class, true );
            beforeMethods      = getAnnotatedMethods(testClass, Before.class,      false);
            afterMethods       = getAnnotatedMethods(testClass, After.class,       false);
            afterClassMethods  = getAnnotatedMethods(testClass, AfterClass.class,  true );
        } else {
            beforeClassMethods = Collections.emptyList();
            beforeMethods      = Collections.emptyList();
            afterMethods       = Collections.emptyList();
            afterClassMethods  = Collections.emptyList();
        }


        return new MByHaveConfiguration(KEYWORDS, candidates, testClass, beforeClassMethods, beforeMethods, afterMethods, afterClassMethods);
    }

    /**
     * Scans for the step candidates.
     *
     * @param testClass The test class.
     * @return The found stepcandidates in a map of keyword, stepcandidate list pairs.
     */
    private static Map<Class<? extends Annotation>, List<StepCandidate>> initStepCandidates(final Class<?> testClass) {
        final Map<Class<? extends Annotation>, List<StepCandidate>> mutableCandidates = new HashMap<Class<? extends Annotation>, List<StepCandidate>>();
        for (final Class<? extends Annotation> annotation : KEYWORDS.keySet()) {
            mutableCandidates.put(annotation, initStepCandidates(annotation, testClass));
        }
        return Collections.unmodifiableMap(mutableCandidates);
    }


    /**
     * Finds and initializes the step candidates.
     * This method looks for the <tt>annotated</tt> methods in the <tt>testClass</tt> and adds the found
     * methods as {@link StepCandidate}s to the <tt>stepCandidatesList</tt>
     *
     * @param <T> The type of the annotations
     * @param annotation The annotation the method is looking for.
     * @param testClass The class the method is scanning for annotated methods.
     * @return The list to which the annotated methods will be added as {@link StepCandidate}s.
     */
    private static <T extends Annotation> List<StepCandidate> initStepCandidates(final Class<T> annotation, final Class<?> testClass) {
        final List<StepCandidate> stepCandidatesList = new ArrayList<StepCandidate>();
        LOG.info("Init candidates");
        for (final Method method : testClass.getMethods()) {
            final String[] definitionValues = getAnnotationValue(annotation, method);
            final int priority = getAnnotationPriority(annotation, method);
            if (definitionValues != null) {
                for (final String definitionValue : definitionValues) {
                    LOG.finer("Step definition: " + definitionValue);
   
                    /* Retrieves the method parameters. */
                    final Param[] params = getParameters(method);
                    /* Finds the parameters in the step definition string. */
                    final Map<Integer, MethodParameter> parameterPositions = findParameterPositions(params, definitionValue);
                    /* Find the parameters representing return values. */
                    final List<MethodParameter> returnValueParameters = findReturnValueParameters(params);
                    /* Logs the method parameters. */
                    if (LOG.isLoggable(Level.FINER)) {
                        for (final Map.Entry<Integer, MethodParameter> paramPos : parameterPositions.entrySet()) {
                            LOG.finer("Position: " + paramPos.getKey() + " Param: " + paramPos.getValue().getParamName());
                        }
                    }
                    final String regEx = createRegEx(definitionValue, Arrays.asList(params));
   
                    LOG.finer("RegEx: " + regEx);
   
                    stepCandidatesList.add(new StepCandidate(definitionValue, method, parameterPositions, returnValueParameters, regEx, priority));
                }
            }
        }
       
        Collections.sort(stepCandidatesList);
       
        return stepCandidatesList;
    }

    private static <T extends Annotation> String[] getAnnotationValue(final Class<T> annotation, final Method method) {
        if (annotation == Given.class) {
            if (method.isAnnotationPresent(Given.class)) {
                final Given given = method.getAnnotation(Given.class);
                return given.value();
            }
        } else if (annotation == When.class) {
            if (method.isAnnotationPresent(When.class)) {
                final When when = method.getAnnotation(When.class);
                return when.value();
            }
        } else if (annotation == Then.class) {
            if (method.isAnnotationPresent(Then.class)) {
                final Then then = method.getAnnotation(Then.class);
                return then.value();
            }
        }
        return null;
    }

    private static <T extends Annotation> int getAnnotationPriority(final Class<T> annotation, final Method method) {
        if (annotation == Given.class) {
            if (method.isAnnotationPresent(Given.class)) {
                final Given given = method.getAnnotation(Given.class);
                return given.priority();
            }
        } else if (annotation == When.class) {
            if (method.isAnnotationPresent(When.class)) {
                final When when = method.getAnnotation(When.class);
                return when.priority();
            }
        } else if (annotation == Then.class) {
            if (method.isAnnotationPresent(Then.class)) {
                final Then then = method.getAnnotation(Then.class);
                return then.priority();
            }
        }
        return 0;
    }

    private static Param[] getParameters(final Method method) {
        final Paranamer paranamer = new CachingParanamer(new BytecodeReadingParanamer());
        final String[] paramNames = paranamer.lookupParameterNames(method, false);
        final Type[]   types      = method.getGenericParameterTypes();
        final Param[]  params     = new Param[types.length];
        for (int i = 0; i < types.length; i++) {
            params[i] = new Param(paramNames[i], types[i]);
        }
        return params;
    }

    private static String createRegEx(final String stepValue, final Collection<Param> params) {
        String regEx = Pattern.quote(stepValue);

        for (final Param param : params) {
            final String paramName = param.getName();
            final String paramPlaceHolder = getPlaceholderPattern(paramName);
            regEx = regEx.replace(paramPlaceHolder, "\\E(.*)\\Q");
        }
        return '^' + regEx + '$';
    }

    private static String getPlaceholderPattern(final String paramName) {
        return "$" + paramName;
    }

   
    private static class Param {
       
        private final String name;
        private final Type   type;
        /**
         * @param name
         * @param type
         */
        public Param(String name, Type type) {
            super();
            this.name = name;
            this.type = type;
        }
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
        /**
         * @return the type
         */
        public Type getType() {
            return type;
        }
       
    }
   
    public static List<MethodParameter> findReturnValueParameters(final Param[] params) {
        final List<MethodParameter> returnValueParameters = new ArrayList<MethodParameter>();
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                final Param  param            = params[i];
                if (isReturnValueParameter(param)) {
                    returnValueParameters.add(new MethodParameter(param.getName(), i, param.getType()));
                }
            }
        }
        return returnValueParameters;
               
    }

    private static boolean isReturnValueParameter(final Param param) {
        return TypeToken.of(ReturnValue.class).isAssignableFrom(param.getType());
    }
   
    public static Map<Integer, MethodParameter> findParameterPositions(final Param[] params, final String stepValue) {

        final Map<Integer, MethodParameter> result = new TreeMap<Integer, MethodParameter>();
        if (params != null) {
            for (int i = 0; i < params.length; i++) {

                final Param param = params[i];
                if (isReturnValueParameter(param)) {
                    continue;
                }
               
                final String paramPlaceHolder = getPlaceholderPattern(param.getName());
                final int    posInStepPattern = stepValue.indexOf(paramPlaceHolder);

                LOG.fine("Parameter: " + param);
                LOG.fine("Position:  " + posInStepPattern);
               
                /* Check there is only one appearance in the stepPattern. */
                if (posInStepPattern < 0) {
                    throw new MByHaveException("The pattern does not contain placeholder for the " + param.getName() + " parameter in the step definition: " + stepValue);
                }
                if (stepValue.indexOf(paramPlaceHolder, posInStepPattern + param.getName().length()) >= 0) {
                    throw new MByHaveException("The pattern does contain more than one placeholder for the " + param + " parameter");
                }

                /* Add to the map. */
                result.put(Integer.valueOf(posInStepPattern), new MethodParameter(param.getName(), i, param.getType()));
            }
        }

        return result;
    }

    private static List<Method> getAnnotatedMethods(final Class<?> testClass, final Class<? extends Annotation> annotation, final boolean isStatic) {
        final List<Method> methods = new ArrayList<Method>();
        for (final Method method : testClass.getDeclaredMethods()) {
            if (method.isAnnotationPresent(annotation)) {
                if (isStatic != Modifier.isStatic(method.getModifiers())) {
                    throw new IllegalArgumentException("The " + annotation + " should be applied on " + (isStatic ? "static" : "non static") + " method.");
                }
                methods.add(method);
            }
        }
        return methods;
    }

    /** Hidden constructor of utiltity class. */
    private MByHaveConfigurator() {
        /* NOP */
    }

}
TOP

Related Classes of org.moresbycoffee.have.MByHaveConfigurator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.