/* * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Stephane Lacoin */ package org.nuxeo.runtime.test.runner; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.junit.internal.AssumptionViolatedException; import org.junit.runners.model.TestClass; import com.google.inject.Binder; import com.google.inject.Module; class FeaturesLoader { protected enum Direction { FORWARD, BACKWARD } protected interface Callable { void call(Holder holder) throws Exception; } private final FeaturesRunner runner; /** * @param featuresRunner */ FeaturesLoader(FeaturesRunner featuresRunner) { runner = featuresRunner; } protected class Holder { protected final Class<? extends RunnerFeature> type; protected final TestClass testClass; protected RunnerFeature feature; Holder(Class<? extends RunnerFeature> aType) throws InstantiationException, IllegalAccessException { type = aType; testClass = new TestClass(aType); feature = aType.newInstance(); } @Override public String toString() { return "Holder [type=" + type + "]"; } } protected final Map<Class<? extends RunnerFeature>, Holder> index = new HashMap<>(); protected final List<Holder> holders = new LinkedList<>(); Iterable<Holder> holders() { return holders; } Iterable<RunnerFeature> features() { return new Iterable<RunnerFeature>() { @Override public Iterator<RunnerFeature> iterator() { return new Iterator<RunnerFeature>() { Iterator<Holder> iterator = holders.iterator(); @Override public boolean hasNext() { return iterator.hasNext(); } @Override public RunnerFeature next() { return iterator.next().feature; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } protected void apply(Direction direction, Callable callable) { apply(direction == Direction.FORWARD ? holders : reversed(holders), callable); } protected <T> List<T> reversed(List<T> list) { List<T> reversed = new ArrayList<>(list); Collections.reverse(reversed); return reversed; } protected void apply(Iterable<Holder> holders, Callable callable) { AssertionError errors = new AssertionError("invoke on features error " + holders); for (Holder each : holders) { try { callable.call(each); } catch (AssumptionViolatedException cause) { throw cause; } catch (Throwable cause) { errors.addSuppressed(cause); if (cause instanceof InterruptedException) { Thread.currentThread().interrupt(); throw new AssertionError("Interrupted on invoke features", errors); } } } if (errors.getSuppressed().length > 0) { throw errors; } } protected boolean contains(Class<? extends RunnerFeature> aType) { return index.containsKey(aType); } public void loadFeatures(Class<?> classToRun) throws Exception { FeaturesRunner.scanner.scan(classToRun); // load required features from annotation List<Features> annos = FeaturesRunner.scanner.getAnnotations(classToRun, Features.class); if (annos != null) { for (Features anno : annos) { for (Class<? extends RunnerFeature> cl : anno.value()) { loadFeature(new HashSet<Class<?>>(), cl); } } } } protected void loadFeature(HashSet<Class<?>> cycles, Class<? extends RunnerFeature> clazz) throws Exception { if (index.containsKey(clazz)) { return; } if (cycles.contains(clazz)) { throw new IllegalStateException("Cycle detected in features dependencies of " + clazz); } cycles.add(clazz); FeaturesRunner.scanner.scan(clazz); // load required features from annotation List<Features> annos = FeaturesRunner.scanner.getAnnotations(clazz, Features.class); if (annos != null) { for (Features anno : annos) { for (Class<? extends RunnerFeature> cl : anno.value()) { loadFeature(cycles, cl); } } } final Holder actual = new Holder(clazz); holders.add(actual); index.put(clazz, actual); } public <T extends RunnerFeature> T getFeature(Class<T> aType) { if (!index.containsKey(aType)) { return null; } return aType.cast(index.get(aType).feature); } protected Module onModule() { return new Module() { @SuppressWarnings("unchecked") @Override public void configure(Binder aBinder) { for (Holder each : holders) { each.feature.configure(runner, aBinder); aBinder.bind((Class) each.feature.getClass()).toInstance(each.feature); aBinder.requestInjection(each.feature); } } }; } }