/* * Copyright 2000-2016 Vaadin Ltd. * * 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 com.vaadin.tests.tb3; import java.io.File; import java.io.IOException; import java.lang.reflect.Modifier; import java.net.JarURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; public class TB3TestLocator { /** * Traverses the directory on the classpath (inside or outside a Jar file) * specified by 'basePackage'. Collects all classes inside the location * which can be assigned to 'baseClass' except for classes inside packages * listed in 'ignoredPackages'. * * @param baseClass * @param basePackage * @param ignorePackages * @return */ public Class<?>[] findTests(Class<? extends AbstractTB3Test> baseClass, String basePackage, String[] ignorePackages) { try { List<?> l = findClasses(baseClass, basePackage, ignorePackages); return l.toArray(new Class[] {}); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * Traverses the directory on the classpath (inside or outside a Jar file) * specified by 'basePackage'. Collects all classes inside the location * which can be assigned to 'baseClass' except for classes inside packages * listed in 'ignoredPackages'. * * @param baseClass * @param basePackage * @param ignoredPackages * @return * @throws IOException */ protected <T> List<Class<? extends T>> findClasses(Class<T> baseClass, String basePackage, String[] ignoredPackages) throws IOException { List<Class<? extends T>> classes = new ArrayList<>(); String basePackageDirName = "/" + basePackage.replace('.', '/'); URL location = baseClass.getResource(basePackageDirName); if (location.getProtocol().equals("file")) { try { File f = new File(location.toURI()); if (!f.exists()) { throw new IOException( "Directory " + f.toString() + " does not exist"); } findPackages(f, basePackage, baseClass, classes, ignoredPackages); } catch (URISyntaxException e) { throw new IOException(e.getMessage()); } } else if (location.getProtocol().equals("jar")) { JarURLConnection juc = (JarURLConnection) location.openConnection(); findClassesInJar(juc, basePackage, baseClass, classes); } Collections.sort(classes, new Comparator<Class<? extends T>>() { @Override public int compare(Class<? extends T> o1, Class<? extends T> o2) { return o1.getName().compareTo(o2.getName()); } }); return classes; } /** * Traverses the given directory and collects all classes which are inside * the given 'javaPackage' and can be assigned to the given 'baseClass'. The * found classes are added to 'result'. * * @param parent * The directory to traverse * @param javaPackage * The java package which 'parent' contains * @param baseClass * The class which the target classes extend * @param result * The collection to which found classes are added * @param ignoredPackages * A collection of packages (including sub packages) to ignore */ private <T> void findPackages(File parent, String javaPackage, Class<T> baseClass, Collection<Class<? extends T>> result, String[] ignoredPackages) { for (String ignoredPackage : ignoredPackages) { if (javaPackage.equals(ignoredPackage)) { return; } } for (File file : parent.listFiles()) { if (file.isDirectory()) { findPackages(file, javaPackage + "." + file.getName(), baseClass, result, ignoredPackages); } else if (file.getName().endsWith(".class")) { String fullyQualifiedClassName = javaPackage + "." + file.getName().replace(".class", ""); addClassIfMatches(result, fullyQualifiedClassName, baseClass); } } } /** * Traverses a Jar file using the given connection and collects all classes * which are inside the given 'javaPackage' and can be assigned to the given * 'baseClass'. The found classes are added to 'result'. * * @param javaPackage * The java package containing the classes (classes may be in a * sub package) * @param baseClass * The class which the target classes extend * @param result * The collection to which found classes are added * @throws IOException */ private <T> void findClassesInJar(JarURLConnection juc, String javaPackage, Class<T> baseClass, Collection<Class<? extends T>> result) throws IOException { String javaPackageDir = javaPackage.replace('.', '/'); Enumeration<JarEntry> ent = juc.getJarFile().entries(); while (ent.hasMoreElements()) { JarEntry e = ent.nextElement(); if (e.getName().endsWith(".class") && e.getName().startsWith(javaPackageDir)) { String fullyQualifiedClassName = e.getName().replace('/', '.') .replace(".class", ""); addClassIfMatches(result, fullyQualifiedClassName, baseClass); } } } /** * Verifies that the class represented by 'fullyQualifiedClassName' can be * loaded, assigned to 'baseClass' and is not an abstract or anonymous * class. * * @param result * The collection to add to * @param fullyQualifiedClassName * The candidate class * @param baseClass * The class 'fullyQualifiedClassName' should be assignable to */ @SuppressWarnings("unchecked") protected <T> void addClassIfMatches(Collection<Class<? extends T>> result, String fullyQualifiedClassName, Class<T> baseClass) { try { // Try to load the class Class<?> c = Class.forName(fullyQualifiedClassName); if (!baseClass.isAssignableFrom(c)) { return; } if (!includeInSuite(c)) { return; } if (!Modifier.isAbstract(c.getModifiers()) && !c.isAnonymousClass()) { result.add((Class<? extends T>) c); } } catch (Exception e) { // Could ignore that class cannot be loaded e.printStackTrace(); } catch (LinkageError e) { // Ignore. Client side classes will at least throw LinkageErrors } } /** * @return true if the class should be included in the suite, false if not */ private boolean includeInSuite(Class<?> c) { if (c.getAnnotation(ExcludeFromSuite.class) != null) { return false; } IncludeIfProperty includeIfProperty = c .getAnnotation(IncludeIfProperty.class); if (includeIfProperty != null) { String includeValue = includeIfProperty.value(); String systemPropertyValue = System .getProperty(includeIfProperty.property()); if (!includeValue.equals(systemPropertyValue)) { return false; } } return true; } }