/* * Copyright (C) 2013 David Sowerby * * 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 uk.q3c.krail.core.view; import com.google.inject.Inject; import org.junit.Test; import org.reflections.Reflections; import org.reflections.scanners.ResourcesScanner; import org.reflections.scanners.SubTypesScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import uk.q3c.krail.core.guice.uiscope.UIScoped; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; /** * Checks for correct use of UIScope and Inject annotations * * @author David Sowerby 7 Sep 2013 */ public class ScopeAndInjectTest { /** * All implementations of KrailView should be have a scope of {@link UIScoped} to avoid issues when navigating and * going back to existing pages. * <p/> */ @Test public void confirmUIScope() { // given Reflections reflections = new Reflections(""); Set<Class<? extends KrailView>> subTypes = reflections.getSubTypesOf(KrailView.class); Set<Class<? extends KrailView>> concreteTypes = new HashSet<>(); Set<Class<? extends KrailView>> noInject = new HashSet<>(); // Set<Class<? extends KrailView>> noScope = new HashSet<>(); // when // remove interfaces, abstract classes and inner classes // inner classes are the responsibility of the test for (Class<? extends KrailView> clazz : subTypes) { if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && !clazz.isMemberClass()) { concreteTypes.add(clazz); } } for (Class<? extends KrailView> clazz : concreteTypes) { // if (!clazz.isAnnotationPresent(UIScoped.class)) { // noScope.add(clazz); // } if (!hasConstructorWithInject(clazz)) { noInject.add(clazz); } } // These are test classes which don't need to be UISCoped (and make test construction more difficult if they // are) // noScope.remove(fixture.testviews2.TestAnnotatedView.class); // noScope.remove(fixture1.TestAnnotatedView.class); // if (noScope.size() > 0) { // System.out // .println("\n\n-------- The following KrailView implementations are missing @UIScoped annotation // -------------\n"); // for (Class<? extends KrailView> clazz : noScope) { // System.out.println(clazz.getName()); // } // } if (noInject.size() > 0) { System.out.println("\n\n------ The following KrailView implementation has no constructor with a javax" + ".Inject" + " annotation (if have you used the com.google.Inject, " + "change it to javax.Inject ------\n"); for (Class<? extends KrailView> clazz : noInject) { System.out.println(clazz.getName()); } } // then // assertThat(noScope).isEmpty(); assertThat(noInject).isEmpty(); } /** * returns true if there is a constructor for {@code clazz} with either a {@link Inject} annotation or a * parameterless public constructor (as required by Guice) * * @param clazz * * @return */ protected boolean hasConstructorWithInject(Class<? extends KrailView> clazz) { Constructor<?>[] constructors = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { if (constructor.getAnnotation(Inject.class) != null) { return true; } if ((constructor.getParameterTypes().length == 0) && (Modifier.isPublic(constructor.getModifiers()))) { return true; } } return false; } /** * Looks for any classes using javax.inject.* instead of the com.google.inject.* equivalent. Convention for this * project is to use the Google annotations. Using mixed types can cause assignment incompatibility. */ @Test public void googleAnnotations() { // given // when testForJavaxAnnotation(javax.inject.Singleton.class); testForJavaxAnnotation(javax.inject.Inject.class); // then // report for test output } private void testForJavaxAnnotation(Class<? extends Annotation> annotation) { Reflections reflections = new Reflections(""); Set<Class<?>> googleInjects = reflections.getTypesAnnotatedWith(annotation); String outputMsg = "Testing for incorrect use of " + annotation.getName(); if (!googleInjects.isEmpty()) { StringBuilder buf = new StringBuilder(); for (Class<?> clazz : googleInjects) { buf.append(clazz.getName()); buf.append(";"); } outputMsg = buf.toString(); } assertThat(googleInjects).hasSize(0) .overridingErrorMessage(outputMsg); } /** * Looks for use of javax.inject.Provider (should be com.google.inject.Provider). For the scope classes, however, * the Provider has to be of the Google type, and are therefore excluded from the check */ @Test public void testForGoogleClasses() { // given List<Class<?>> targetTypes = new ArrayList<>(); targetTypes.add(javax.inject.Provider.class); Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner()) .setUrls(ClasspathHelper.forPackage("uk" + ".co.q3c"))); Set<Class<? extends Object>> allClasses = reflections.getSubTypesOf(Object.class); // when boolean failed = false; for (Class<? extends Object> clazz : allClasses) { Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { if (targetTypes.contains(field.getType())) { System.out.println("Found target type " + field.getType() + " in " + clazz.getName()); failed = true; } ; } } // then assertThat(failed).isFalse() .overridingErrorMessage("See console output if this fails"); } /** * All View classes should have an injected constructor. Krail standardises on the com.google.inject.Inject rather * than * the javax.inject.Inject, and this test will identify any implementations which do not have a constructor with a * Google Inject annotation (it will also accept a no-args public constructor as Guice will accept that) */ @Test public void googleInject() { // given SubTypesScanner scanner = new SubTypesScanner(false); Reflections reflections = new Reflections("", scanner); // when // This requires MethodAnnotationsScanner to be set up according to the javadoc, but it is not clear how that // should be done // Set<Constructor> googleInjects = reflections.getConstructorsAnnotatedWith(com.google.inject.Inject.class); // Use a big hammer instead Set<Class<?>> allClasses = reflections.getSubTypesOf(Object.class); Set<Class<?>> javaxInjects = new HashSet<>(); // then for (Class<? extends Object> clazz : allClasses) { if (classHasJavaxInjectedConstructor(clazz)) { javaxInjects.add(clazz); } } // report for test output String outputMsg = "none found"; if (!javaxInjects.isEmpty()) { StringBuilder buf = new StringBuilder(); for (Class<?> clazz : javaxInjects) { buf.append(clazz.getName()); buf.append(";"); } outputMsg = buf.toString(); } assertThat(javaxInjects).hasSize(0) .overridingErrorMessage(outputMsg); } private boolean classHasJavaxInjectedConstructor(Class<?> clazz) { Constructor<?>[] constructors = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { if (constructor.getAnnotation(javax.inject.Inject.class) != null) { return true; } } return false; } protected boolean classHasTestAnnotatedMethods(Class<? extends KrailView> clazz) { Method[] methods = clazz.getMethods(); for (Method method : methods) { if (method.getAnnotation(Test.class) != null) { return true; } } return false; } }