Project: JDave
/*
 * Copyright 2009 the original author or 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 org.junit.internal.builders; 
 
import static java.util.Arrays.asList; 
 
import java.lang.reflect.Constructor; 
import java.lang.reflect.Modifier; 
import java.util.Arrays; 
import java.util.List; 
 
import org.junit.runner.RunWith; 
import org.junit.runner.Runner; 
import org.junit.runners.model.InitializationError; 
import org.junit.runners.model.RunnerBuilder; 
 
/**
 * <p> 
 * Just like AnnotatedBuilder in JUnit4 except it supports running single 
 * JDave behaviors. 
 * </p> 
 * <p> 
 * If jdave-junit4 is in your classpath before junit4 then this will be loaded 
 * first. 
 * </p> 
 * <p> 
 * When running in your buildtool etc.  you won't care about running a single behavior.  
 * This class should be equivalent to the default JUnit4 one, so in that case it won't matter which one is loaded first. 
 * </p> 
 *  
 * @author Tuomas Karkkainen 
 */
 
public class AnnotatedBuilder extends RunnerBuilder { 
    private static final String CONSTRUCTOR_ERROR_FORMAT = "Custom runner class %1$s should have a public constructor with signature %1$s(Class testClass) or %1$s(Class testClass, RunnerBuilder builder)"
 
    private final RunnerBuilder suiteBuilder; 
 
    public AnnotatedBuilder(final RunnerBuilder suiteBuilder) { 
        this.suiteBuilder = suiteBuilder; 
    } 
 
    @Override 
    public Runner runnerForClass(final Class<?> testClass) throws Exception { 
        final RunWith annotation = getRunWithAnnotation(testClass); 
        if (annotation != null) { 
            return buildRunner(annotation.value(), testClass); 
        } 
        return null
    } 
 
    private RunWith getRunWithAnnotation(final Class<?> testClass) { 
        if (testClass.isAnnotationPresent(RunWith.class)) { 
            return testClass.getAnnotation(RunWith.class); 
        } 
        if (testClassIsANonStaticInnerClass(testClass)) { 
            return testClass.getDeclaringClass().getAnnotation(RunWith.class); 
        } 
        return null
    } 
 
    private boolean testClassIsANonStaticInnerClass(final Class<?> testClass) { 
        return testClass.isMemberClass() && !Modifier.isStatic(testClass.getModifiers()); 
    } 
 
    private <T extends Runner> T buildRunner(final Class<T> runnerClass, final Class<?> testClass) 
            throws Exception { 
        return new InternalBuilder<T>(runnerClass, suiteBuilder).build(testClass); 
    } 
 
    private static class InternalBuilder<T extends Runner> { 
        private final RunnerBuilder suiteBuilder; 
 
        private final Class<T> runnerClass; 
 
        public InternalBuilder(final Class<T> runnerClass, final RunnerBuilder suiteBuilder) { 
            this.runnerClass = runnerClass; 
            this.suiteBuilder = suiteBuilder; 
        } 
 
        private T build(final Class<?> testClass) throws Exception { 
            if (hasValidConstructor()) { 
                return instantiateRunner(testClass); 
            } 
            throw new InitializationError(String.format(CONSTRUCTOR_ERROR_FORMAT, runnerClass 
                    .getSimpleName())); 
        } 
 
        private T instantiateRunner(final Class<?> testClass) throws Exception { 
            if (hasSimpleConstructor()) { 
                return getSimpleConstructor().newInstance(testClass); 
            } 
            return getConstructorWithSuite(runnerClass).newInstance(testClass, suiteBuilder); 
        } 
 
        private boolean hasValidConstructor() { 
            return hasSimpleConstructor() || hasConstructorWithSuite(); 
        } 
 
        private boolean hasSimpleConstructor() { 
            return getSimpleConstructor() != null
        } 
 
        private boolean hasConstructorWithSuite() { 
            return getConstructorWithSuite(runnerClass) != null
        } 
 
        private Constructor<T> getSimpleConstructor() { 
            for (final Constructor<T> constructor : getConstructors(runnerClass)) { 
                final List<Class<?>> parameters = asList(constructor.getParameterTypes()); 
                if (parameters.equals(Arrays.<Class<?>> asList(Class.class))) { 
                    return constructor; 
                } 
            } 
            return null
        } 
 
        private Constructor<T> getConstructorWithSuite(final Class<T> runnerClass) { 
            for (final Constructor<T> constructor : getConstructors(runnerClass)) { 
                final List<Class<?>> parameters = asList(constructor.getParameterTypes()); 
                if (parameters.equals(Arrays.<Class<?>> asList(Class.class, RunnerBuilder.class))) { 
                    return constructor; 
                } 
            } 
            return null
        } 
 
        @SuppressWarnings("unchecked"
        private List<Constructor<T>> getConstructors(final Class<T> clazz) { 
            return asList((Constructor<T>[]) clazz.getConstructors()); 
        } 
    } 
}