package org.hotswap.agent.util.scanner; import org.hotswap.agent.logging.AgentLogger; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Scan classpath for a directory and visits each file. * <p/> * Thread context classloader is used to scan. * * @author Jiri Bubnik */ public class ClassPathScanner implements Scanner { private static AgentLogger LOGGER = AgentLogger.getLogger(ClassPathScanner.class); // scan for files inside JAR file - e.g. jar:file:\J:\HotswapAgent\target\HotswapAgent-1.0.jar!\org\hotswap\agent\plugin public static final String JAR_URL_SEPARATOR = "!/"; public static final String JAR_URL_PREFIX = "jar:"; public static final String ZIP_URL_PREFIX = "zip:"; public static final String FILE_URL_PREFIX = "file:"; @Override public void scan(ClassLoader classLoader, String path, ScannerVisitor visitor) throws IOException { LOGGER.trace("Scanning path {}", path); // find all directories - classpath directory or JAR Enumeration<URL> en = classLoader == null ? ClassLoader.getSystemResources(path) : classLoader.getResources(path); while (en.hasMoreElements()) { URL pluginDirURL = en.nextElement(); File pluginDir = new File(pluginDirURL.getFile()); if (pluginDir.isDirectory()) { scanDirectory(pluginDir, visitor); } else { // JAR file String uri; try { uri = pluginDirURL.toURI().toString(); } catch (URISyntaxException e) { throw new IOException("Illegal directory URI " + pluginDirURL, e); } if (uri.startsWith(JAR_URL_PREFIX) || uri.startsWith(ZIP_URL_PREFIX)) { String jarFile = uri.substring(uri.indexOf(':') + 1); // remove the prefix scanJar(jarFile, visitor); } else { LOGGER.warning("Unknown resource type of file " + uri); } } } } /** * Recursively scan the directory. * * @param pluginDir directory. * @param visitor callback * @throws IOException exception from a visitor */ protected void scanDirectory(File pluginDir, ScannerVisitor visitor) throws IOException { LOGGER.trace("Scanning directory " + pluginDir.getName()); for (File file : pluginDir.listFiles()) { if (file.isDirectory()) { scanDirectory(file, visitor); } else if (file.isFile() && file.getName().endsWith(".class")) { visitor.visit(new FileInputStream(file)); } } } /** * Scan JAR file for all entries. * Resolve the JAR file itself and than iterate all entries and call visitor. * * @param urlFile URL to the file containing scanned directory * (e.g. jar:file:\J:\HotswapAgent\target\HotswapAgent-1.0.jar!\org\hotswap\agent\plugin) * @param visitor callback * @throws IOException exception from a visitor */ private void scanJar(String urlFile, ScannerVisitor visitor) throws IOException { LOGGER.trace("Scanning JAR file '{}'", urlFile); int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR); JarFile jarFile; String rootEntryPath; if (separatorIndex != -1) { String jarFileUrl = urlFile.substring(0, separatorIndex); rootEntryPath = urlFile.substring(separatorIndex + JAR_URL_SEPARATOR.length()); jarFile = getJarFile(jarFileUrl); } else { rootEntryPath = ""; jarFile = new JarFile(urlFile); } if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) { rootEntryPath = rootEntryPath + "/"; } for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); ) { JarEntry entry = entries.nextElement(); String entryPath = entry.getName(); // class files inside entry if (entryPath.startsWith(rootEntryPath) && entryPath.endsWith(".class")) { LOGGER.trace("Visiting JAR entry {}", entryPath); visitor.visit(jarFile.getInputStream(entry)); } } } /** * Resolve the given jar file URL into a JarFile object. */ protected JarFile getJarFile(String jarFileUrl) throws IOException { LOGGER.trace("Opening JAR file " + jarFileUrl); if (jarFileUrl.startsWith(FILE_URL_PREFIX)) { try { return new JarFile(toURI(jarFileUrl).getSchemeSpecificPart()); } catch (URISyntaxException ex) { // Fallback for URLs that are not valid URIs (should hardly ever happen). return new JarFile(jarFileUrl.substring(FILE_URL_PREFIX.length())); } } else { return new JarFile(jarFileUrl); } } /** * Create a URI instance for the given location String, * replacing spaces with "%20" quotes first. * * @param location the location String to convert into a URI instance * @return the URI instance * @throws URISyntaxException if the location wasn't a valid URI */ public static URI toURI(String location) throws URISyntaxException { return new URI(location.replace(" ", "%20")); } }