/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * ClassCache.java * Copyright (C) 2010-2012 University of Waikato, Hamilton, New Zealand */ package weka.core; import java.io.File; import java.io.FileFilter; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * A singleton that stores all classes on the classpath. * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 8034 $ */ public class ClassCache implements RevisionHandler { /** * For filtering classes. * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 8034 $ */ public static class ClassFileFilter implements FileFilter { /** * Checks whether the file is a class. * * @param pathname the file to check * @return true if a class file */ public boolean accept(File pathname) { return pathname.getName().endsWith(".class"); } } /** * For filtering classes. * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 8034 $ */ public static class DirectoryFilter implements FileFilter { /** * Checks whether the file is a directory. * * @param pathname the file to check * @return true if a directory */ public boolean accept(File pathname) { return pathname.isDirectory(); } } /** whether to output some debug information. */ public final static boolean VERBOSE = false; /** the key for the default package. */ public final static String DEFAULT_PACKAGE = "DEFAULT"; /** for caching all classes on the class path (package-name <-> HashSet with classnames). */ protected Hashtable<String,HashSet<String>> m_Cache; static { // notify if VERBOSE is still on if (VERBOSE) System.err.println(ClassCache.class.getName() + ": VERBOSE ON"); } /** * Initializes the cache. */ public ClassCache() { super(); initialize(); } /** * Fixes the classname, turns "/" and "\" into "." and removes ".class". * * @param classname the classname to process * @return the processed classname */ protected String cleanUp(String classname) { String result; result = classname; if (result.indexOf("/") > -1) result = result.replace("/", "."); if (result.indexOf("\\") > -1) result = result.replace("\\", "."); if (result.endsWith(".class")) result = result.substring(0, result.length() - 6); return result; } /** * Extracts the package name from the (clean) classname. * * @param classname the classname to extract the package from * @return the package name */ protected String extractPackage(String classname) { if (classname.indexOf(".") > -1) return classname.substring(0, classname.lastIndexOf(".")); else return DEFAULT_PACKAGE; } /** * Adds the classname to the cache. * * @param classname the classname, automatically removes ".class" and * turns "/" or "\" into "." * @return true if adding changed the cache */ public boolean add(String classname) { String pkgname; HashSet<String> names; // classname and package classname = cleanUp(classname); pkgname = extractPackage(classname); // add to cache if (!m_Cache.containsKey(pkgname)) m_Cache.put(pkgname, new HashSet<String>()); names = m_Cache.get(pkgname); return names.add(classname); } /** * Removes the classname from the cache. * * @param classname the classname to remove * @return true if the removal changed the cache */ public boolean remove(String classname) { String pkgname; HashSet<String> names; classname = cleanUp(classname); pkgname = extractPackage(classname); names = m_Cache.get(pkgname); if (names != null) return names.remove(classname); else return false; } /** * Fills the class cache with classes in the specified directory. * * @param prefix the package prefix so far, null for default package * @param dir the directory to search */ protected void initFromDir(String prefix, File dir) { File[] files; // check classes files = dir.listFiles(new ClassFileFilter()); for (File file: files) { if (prefix == null) add(file.getName()); else add(prefix + "." + file.getName()); } // descend in directories files = dir.listFiles(new DirectoryFilter()); for (File file: files) { if (prefix == null) initFromDir(file.getName(), file); else initFromDir(prefix + "." + file.getName(), file); } } /** * Fills the class cache with classes in the specified directory. * * @param dir the directory to search */ protected void initFromDir(File dir) { if (VERBOSE) System.out.println("Analyzing directory: " + dir); initFromDir(null, dir); } /** * Fills the class cache with classes from the specified jar. * * @param file the jar to inspect */ protected void initFromJar(File file) { JarFile jar; JarEntry entry; Enumeration enm; if (VERBOSE) System.out.println("Analyzing jar: " + file); if (!file.exists()) { System.out.println("Jar does not exist: " + file); return; } try { jar = new JarFile(file); enm = jar.entries(); while (enm.hasMoreElements()) { entry = (JarEntry) enm.nextElement(); if (entry.getName().endsWith(".class")) add(entry.getName()); } } catch (Exception e) { e.printStackTrace(); } } /** * Returns all the stored packages. * * @return the package names */ public Enumeration<String> packages() { return m_Cache.keys(); } /** * Returns all the classes for the given package. * * @param pkgname the package to get the classes for * @return the classes (sorted by name) */ public HashSet<String> getClassnames(String pkgname) { if (m_Cache.containsKey(pkgname)) return m_Cache.get(pkgname); else return new HashSet<String>(); } /** * Initializes the cache. */ protected void initialize() { String part; File file; URLClassLoader sysLoader; URL[] urls; m_Cache = new Hashtable<String,HashSet<String>>(); sysLoader = (URLClassLoader) getClass().getClassLoader(); urls = sysLoader.getURLs(); for (URL url: urls) { if (VERBOSE) System.out.println("Classpath-part: " + part); file = null; part = url.toString(); if (part.startsWith("file:")) { part = part.replace(" ", "%20"); try { file = new File(new java.net.URI(part)); } catch (URISyntaxException e) { e.printStackTrace(); } } else { file = new File(part); } if (file == null) { System.err.println("Skipping: " + part); continue; } // find classes if (file.isDirectory()) initFromDir(file); else if (file.exists()) initFromJar(file); } } /** * Find all classes that have the supplied matchText String in * their suffix. * * @param matchText the text to match * @return an array list of matching fully qualified class names. */ public ArrayList<String> find(String matchText) { ArrayList<String> result; Enumeration<String> packages; Iterator<String> names; String name; result = new ArrayList<String>(); packages = m_Cache.keys(); while (packages.hasMoreElements()) { names = m_Cache.get(packages.nextElement()).iterator(); while (names.hasNext()) { name = names.next(); if (name.contains(matchText)) result.add(name); } } if (result.size() > 1) Collections.sort(result); return result; } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 8034 $"); } /** * For testing only. * * @param args ignored */ public static void main(String[] args) { ClassCache cache = new ClassCache(); Enumeration<String> packages = cache.packages(); while (packages.hasMoreElements()) { String key = packages.nextElement(); System.out.println(key + ": " + cache.getClassnames(key).size()); } } }