Java tutorial
/** * Copyright (C) 2010-2016 Gordon Fraser, Andrea Arcuri and EvoSuite * contributors * * This file is part of EvoSuite. * * EvoSuite is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3.0 of the License, or * (at your option) any later version. * * EvoSuite is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>. */ package org.evosuite.classpath; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.evosuite.Properties; import org.evosuite.TestGenerationContext; import org.evosuite.runtime.InitializingListener; import org.evosuite.runtime.InitializingListenerUtils; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p> * Utilities to list class resources (ie .class files) available from the classpath * </p> * * * @author Gordon Fraser */ public class ResourceList { private static Logger logger = LoggerFactory.getLogger(ResourceList.class); private static class Cache { /** * Key -> a classpath entry (eg folder or jar file) * <p> * Value -> set of all classes in that CP entry */ public Map<String, Set<String>> mapCPtoClasses = new LinkedHashMap<>(); /** * Key -> full qualifying name of a class, eg org.some.Foo * <p> * Value -> the classpath entry in which it can be found */ public Map<String, String> mapClassToCP = new LinkedHashMap<>(); /** * Key -> package prefix * <p> * Value -> set of classpath entries having such prefix */ public Map<String, Set<String>> mapPrefixToCPs = new LinkedHashMap<>(); /** * Keep track of the classes that should be on the classpath but they are not */ public Set<String> missingClasses = new LinkedHashSet<>(); public void addPrefix(String prefix, String cpEntry) { Set<String> classPathEntries = mapPrefixToCPs.get(prefix); if (classPathEntries == null) { classPathEntries = new LinkedHashSet<String>(); mapPrefixToCPs.put(prefix, classPathEntries); } classPathEntries.add(cpEntry); if (!prefix.isEmpty()) { String parent = getParentPackageName(prefix); addPrefix(parent, cpEntry); } } /** * Keep track of all jars we opened. * Key -> the path of the jar file */ public Map<String, JarFile> openedJars = new LinkedHashMap<>(); public JarFile getJar(String entry) { if (openedJars.containsKey(entry)) { return openedJars.get(entry); } try { JarFile jar = new JarFile(entry); openedJars.put(entry, jar); return jar; } catch (IOException e) { logger.error("Error while reading jar file " + entry + ": " + e.getMessage(), e); return null; } } public void close() { for (JarFile jar : openedJars.values()) { try { jar.close(); } catch (IOException e) { logger.error("Cannot close jar file " + jar.getName() + ". " + e.toString()); } } } } /** * Current cache. Do not access directly, but rather use getCache(), as it can be null */ private Cache cache = null; /* * ResourceList for each ClassLoader */ private static Map<ClassLoader, ResourceList> instanceMap = new HashMap<ClassLoader, ResourceList>(); private final ClassLoader classLoader; /** Private constructor */ private ResourceList(ClassLoader classLoader) { this.classLoader = classLoader; } public static ResourceList getInstance(ClassLoader classLoader) { if (!instanceMap.containsKey(classLoader)) { instanceMap.put(classLoader, new ResourceList(classLoader)); } return instanceMap.get(classLoader); } // ------------------------------------------- // --------- public methods ----------------- // ------------------------------------------- public void resetCache() { if (cache != null) { cache.close(); } cache = null; } public static void resetAllCaches() { instanceMap.clear(); } /** * is the target class among the ones in the SUT classpath? * * @param className * a fully qualified class name * @return */ public boolean hasClass(String className) { return getCache().mapClassToCP.containsKey(className); } /** * * @param name a fully qualifying name, e.g. org.some.Foo * @return */ public InputStream getClassAsStream(String name) { String path = name.replace('.', '/') + ".class"; String windowsPath = name.replace(".", "\\") + ".class"; String cpEntry = getCache().mapClassToCP.get(name); if (cpEntry == null) { /* the cache is initialized based on what is on the project classpath. but that does not include the Java API, although it is accessed by the SUT. */ InputStream ins = getClassAsStreamFromClassLoader(name); if (ins != null) { return ins; } if (!getCache().missingClasses.contains(name)) { getCache().missingClasses.add(name); /* * Note: can't really have "warn" here, as the SUT can use the classloader, * and try to load garbage (eg random string generated as test data) that * would fill the logs */ logger.debug("The class " + name + " is not on the classpath"); //only log once } return null; } if (cpEntry.endsWith(".jar")) { JarFile jar = getCache().getJar(cpEntry); if (jar == null) { return null; } JarEntry entry = jar.getJarEntry(path); if (entry == null) { logger.error("Error: could not find " + path + " inside of jar file " + cpEntry); return null; } InputStream is = null; try { is = jar.getInputStream(entry); } catch (IOException e) { logger.error("Error while reading jar file " + cpEntry + ": " + e.getMessage(), e); return null; } return is; } else { //if not a jar, it is a folder File classFile = null; if (File.separatorChar != '/') { classFile = new File(cpEntry + File.separator + windowsPath); } else { classFile = new File(cpEntry + File.separator + path); } if (!classFile.exists()) { logger.error("Could not find " + classFile); } try { return new FileInputStream(classFile); } catch (FileNotFoundException e) { logger.error("Error while trying to open stream on: " + classFile.getAbsolutePath()); return null; } } } /** * Given the target classpath entry (eg folder or jar file), return the names (eg foo.Foo) of all the classes (.class files) * inside * * @param classPathEntry * @param includeInternalClasses should internal classes (ie static and anonymous having $ in their name) be included? * @return */ public Set<String> getAllClasses(String classPathEntry, boolean includeInternalClasses) { return getAllClasses(classPathEntry, "", includeInternalClasses); } /** * Given the target classpath entry (eg folder or jar file), return the names (eg foo.Foo) of all the classes (.class files) * inside * * @param classPathEntry * @param prefix * @param includeInternalClasses should internal classes (ie static and anonymous having $ in their name) be included? * @return */ public Set<String> getAllClasses(String classPathEntry, String prefix, boolean includeInternalClasses) { return getAllClasses(classPathEntry, prefix, includeInternalClasses, true); } /** * Given the target classpath entry (eg folder or jar file), return the names (eg foo.Foo) of all the classes (.class files) * inside * * @param classPathEntry * @param prefix * @param includeInternalClasses should internal classes (ie static and anonymous having $ in their name) be included? * @param excludeAnonymous if including internal classes, should though still exclude the anonymous? (ie keep only the static ones) * @return */ public Set<String> getAllClasses(String classPathEntry, String prefix, boolean includeInternalClasses, boolean excludeAnonymous) { if (classPathEntry.contains(File.pathSeparator)) { Set<String> retval = new LinkedHashSet<String>(); for (String element : classPathEntry.split(File.pathSeparator)) { retval.addAll(getAllClasses(element, prefix, includeInternalClasses, excludeAnonymous)); } return retval; } else { classPathEntry = (new File(classPathEntry)).getAbsolutePath(); addEntry(classPathEntry); //no need to scan the classpath entry cache if it does not have the given prefix Set<String> cps = getCache().mapPrefixToCPs.get(prefix); if (cps == null || !cps.contains(classPathEntry)) { return Collections.emptySet(); } Set<String> classes = new LinkedHashSet<>(); for (String className : getCache().mapCPtoClasses.get(classPathEntry)) { if (!className.startsWith(prefix)) { continue; } if (!includeInternalClasses && className.contains("$")) { continue; } if (includeInternalClasses && excludeAnonymous && className.matches(".*\\$\\d+$")) { continue; } classes.add(className); } return classes; } } public static boolean isInterface(String resource) throws IOException { InputStream input = ResourceList.class.getClassLoader().getResourceAsStream(resource); return isClassAnInterface(input); } public boolean isClassAnInterface(String className) throws IOException { InputStream input = getClassAsStream(className); return isClassAnInterface(input); } public boolean isClassDeprecated(String className) throws IOException { InputStream input = getClassAsStream(className); return isClassDeprecated(input); } public boolean isClassTestable(String className) throws IOException { InputStream input = getClassAsStream(className); return isClassTestable(input); } /** * <p> * Given a resource path, eg foo/Foo.class, return the class name, eg foo.Foo * * <p> * This method is able to handle different operating systems (Unix/Windows) and whether * the resource is in a folder or inside a jar file ('/' separator independent of operating system). * */ public static String getClassNameFromResourcePath(String resource) { //method had to be moved due to constraints on "runtime" module dependencies return InitializingListenerUtils.getClassNameFromResourcePath(resource); } // ------------------------------------------- // --------- private/protected methods ------ // ------------------------------------------- private static InputStream getClassAsStreamFromClassLoader(String name) { String path = name.replace('.', '/') + ".class"; String windowsPath = name.replace(".", "\\") + ".class"; //first try with system classloader InputStream is = ClassLoader.getSystemResourceAsStream(path); if (is != null) { return is; } if (File.separatorChar != '/') { is = ClassLoader.getSystemResourceAsStream(windowsPath); if (is != null) { return is; } } return null; } private static boolean isClassAnInterface(InputStream input) throws IOException { try { ClassReader reader = new ClassReader(input); ClassNode cn = new ClassNode(); reader.accept(cn, ClassReader.SKIP_FRAMES); return (cn.access & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE; } finally { input.close(); //VERY IMPORTANT, as ASM does not close the stream } } /** * Returns {@code true} if the class is deprecated; returns {@code false} otherwise. * * @param input the input stream * @return {@code true} if the class is deprecated, {@code false} otherwise * @throws IOException if an error occurs while reading the input stream */ private static boolean isClassDeprecated(InputStream input) throws IOException { try { ClassReader reader = new ClassReader(input); ClassNode cn = new ClassNode(); reader.accept(cn, ClassReader.SKIP_FRAMES); return (cn.access & Opcodes.ACC_DEPRECATED) == Opcodes.ACC_DEPRECATED; } finally { input.close(); //VERY IMPORTANT, as ASM does not close the stream } } /** * Returns {@code true} if there is at least one public method in the class; returns {@code false} otherwise. * * @param input the input stream * @return {@code true} if there is at least one public method in the class, {@code false} otherwise * @throws IOException if an error occurs while reading the input stream */ private static boolean isClassTestable(InputStream input) throws IOException { try { ClassReader reader = new ClassReader(input); ClassNode cn = new ClassNode(); reader.accept(cn, ClassReader.SKIP_FRAMES); @SuppressWarnings("unchecked") List<MethodNode> l = cn.methods; for (MethodNode m : l) { if ((m.access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC || (m.access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED || (m.access & Opcodes.ACC_PRIVATE) == 0 /* default */ ) { return true; } } return false; } finally { input.close(); //VERY IMPORTANT, as ASM does not close the stream } } /** * Remove last '.' token * * @param className * @return */ protected static String getParentPackageName(String className) { if (className == null || className.isEmpty()) { return className; } int index = className.lastIndexOf('.'); if (index < 0) { return ""; } return className.substring(0, index); } /** * Init the cache if null * @return */ private Cache getCache() { if (cache == null) { initCache(); } return cache; } private void initCache() { cache = new Cache(); String cp = ClassPathHandler.getInstance().getTargetProjectClasspath(); // If running in regression mode and current ClassLoader is the regression ClassLoader if (Properties.isRegression() && classLoader == TestGenerationContext.getInstance().getRegressionClassLoaderForSUT()) cp = org.evosuite.Properties.REGRESSIONCP; for (String entry : cp.split(File.pathSeparator)) { addEntry(entry); } } private void addEntry(String classPathElement) throws IllegalArgumentException { final File file = new File(classPathElement); classPathElement = file.getAbsolutePath(); if (getCache().mapCPtoClasses.containsKey(classPathElement)) { return; //this classpath entry has already been analyzed } getCache().mapCPtoClasses.put(classPathElement, new LinkedHashSet<String>()); if (!file.exists()) { throw new IllegalArgumentException( "The class path resource " + file.getAbsolutePath() + " does not exist"); } if (file.isDirectory()) { scanDirectory(file, classPathElement); } else if (file.getName().endsWith(".jar")) { scanJar(classPathElement); } else { throw new IllegalArgumentException( "The class path resource " + file.getAbsolutePath() + " is not valid"); } } private void scanDirectory(final File directory, final String classPathFolder) { if (!directory.exists()) { return; } if (!directory.isDirectory()) { return; } if (!directory.canRead()) { logger.warn("No permission to read: " + directory.getAbsolutePath()); return; } String prefix = directory.getAbsolutePath().replace(classPathFolder + File.separator, ""); prefix = prefix.replace(File.separatorChar, '.'); File[] fileList = directory.listFiles(); for (final File file : fileList) { if (file.isDirectory()) { /* * recursion till we get to a file that is not a folder. */ scanDirectory(file, classPathFolder); } else { if (!file.getName().endsWith(".class")) { continue; // we are only interested in class files } String relativeFilePath = file.getAbsolutePath().replace(classPathFolder + File.separator, ""); String className = getClassNameFromResourcePath(relativeFilePath); // The same class may exist in different classpath entries // and only the first one is kept if (getCache().mapClassToCP.containsKey(className)) continue; // If there is an outer class, then we also have a classpath // problem and should ignore this if (className.contains("$")) { String outerClass = className.substring(0, className.indexOf('$')); if (getCache().mapClassToCP.containsKey(outerClass)) { if (!getCache().mapClassToCP.get(outerClass).equals(classPathFolder)) { continue; } } } getCache().mapClassToCP.put(className, classPathFolder); getCache().mapCPtoClasses.get(classPathFolder).add(className); getCache().addPrefix(prefix, classPathFolder); } } } private void scanJar(String jarEntry) { JarFile zf = getCache().getJar(jarEntry); Enumeration<?> e = zf.entries(); while (e.hasMoreElements()) { JarEntry ze = (JarEntry) e.nextElement(); String entryName = ze.getName(); if (!entryName.endsWith(".class")) { continue; } String className = getClassNameFromResourcePath(entryName); // The same class may exist in different classpath entries // and only the first one is kept if (getCache().mapClassToCP.containsKey(className)) continue; if (className.contains("$")) { String outerClass = className.substring(0, className.indexOf('$')); if (getCache().mapClassToCP.containsKey(outerClass)) { if (!getCache().mapClassToCP.get(outerClass).equals(jarEntry)) { continue; } } } getCache().mapClassToCP.put(className, jarEntry);//getPackageName getCache().mapCPtoClasses.get(jarEntry).add(className); getCache().addPrefix(getParentPackageName(className), jarEntry); } } }