package de.lmu.ifi.dbs.elki.utilities;
/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures
Copyright (C) 2011
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.ClassParameter;
/**
* A collection of inspection-related utility functions.
*
* @author Erich Schubert
*
* @apiviz.uses InspectionUtilFrequentlyScanned
*/
public class InspectionUtil {
/**
* Default package ignores.
*/
private static final String[] DEFAULT_IGNORES = {
// Sun Java
"java.", "com.sun.",
// Batik classes
"org.apache.",
// W3C / SVG / XML classes
"org.w3c.", "org.xml.", "javax.xml.",
// JUnit
"org.junit.", "junit.", "org.hamcrest.",
// Eclipse
"org.eclipse.",
// ApiViz
"org.jboss.apiviz.",
// JabRef
"spin.", "osxadapter.", "antlr.", "ca.odell.", "com.jgoodies.", "com.michaelbaranov.", "com.mysql.", "gnu.dtools.", "net.sf.ext.", "net.sf.jabref.", "org.antlr.", "org.gjt.", "org.java.plugin.", "org.jempbox.", "org.pdfbox.", "wsi.ra.",
// Wrappers don't have important own parameters, they just set some defaults
"experimentalcode.shared.wrapper." };
/**
* If we have a non-static classpath, we do more extensive scanning for user
* extensions.
*/
public static final boolean NONSTATIC_CLASSPATH;
// Check for non-jar entries in classpath.
static {
String[] classpath = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
boolean hasnonstatic = false;
for(String path : classpath) {
if(!path.endsWith(".jar")) {
hasnonstatic = true;
}
}
NONSTATIC_CLASSPATH = hasnonstatic;
}
private static WeakReference>> CLASS_CACHE = new WeakReference>>(null);
/**
* Cached version of "findAllImplementations". For Parameterizable classes
* only!
*
* @param c Class to scan for
* @return Found implementations
*/
public static List> cachedFindAllImplementations(Class> c) {
if (c == null) {
return Collections.emptyList();
}
if(InspectionUtilFrequentlyScanned.class.isAssignableFrom(c)) {
List> cache = CLASS_CACHE.get();
if(cache == null) {
cache = findAllImplementations(InspectionUtilFrequentlyScanned.class, false);
CLASS_CACHE = new WeakReference>>(cache);
}
ArrayList> list = new ArrayList>();
for(Class> cls : cache) {
if(c.isAssignableFrom(cls)) {
list.add(cls);
}
}
return list;
}
else {
// Need to scan - not cached.
// LoggingUtil.logExpensive(Level.FINE,
// "Slow scan for implementations: "+c.getName());
return findAllImplementations(c, false);
}
}
/**
* Find all implementations of a given class in the classpath.
*
* Note: returned classes may be abstract.
*
* @param c Class restriction
* @param everything include interfaces, abstract and private classes
* @return List of found classes.
*/
public static List> findAllImplementations(Class> c, boolean everything) {
String[] classpath = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
return findAllImplementations(classpath, c, DEFAULT_IGNORES, everything);
}
/**
* Find all implementations of a given class.
*
* @param classpath Classpath to use (JARs and folders supported)
* @param c Class restriction
* @param ignorepackages List of packages to ignore
* @param everything include interfaces, abstract and private classes
* @return List of found classes.
*/
public static List> findAllImplementations(String[] classpath, Class> c, String[] ignorepackages, boolean everything) {
// Collect iterators
Vector> iters = new Vector>(classpath.length);
for(String path : classpath) {
File p = new File(path);
if(path.endsWith(".jar")) {
iters.add(new JarClassIterator(path));
}
else if(p.isDirectory()) {
iters.add(new DirClassIterator(p));
}
}
ArrayList> res = new ArrayList>();
ClassLoader cl = ClassLoader.getSystemClassLoader();
for(Iterable iter : iters) {
for(String classname : iter) {
boolean ignore = false;
for(String pkg : ignorepackages) {
if(classname.startsWith(pkg)) {
ignore = true;
break;
}
}
if(ignore) {
continue;
}
try {
Class> cls = cl.loadClass(classname);
// skip abstract / private classes.
if(!everything && (Modifier.isInterface(cls.getModifiers()) || Modifier.isAbstract(cls.getModifiers()) || Modifier.isPrivate(cls.getModifiers()))) {
continue;
}
// skip classes where we can't get a full name.
if(cls.getCanonicalName() == null) {
continue;
}
if(c.isAssignableFrom(cls)) {
res.add(cls);
}
}
catch(ClassNotFoundException e) {
continue;
}
catch(NoClassDefFoundError e) {
continue;
}
catch(Exception e) {
continue;
}
}
}
Collections.sort(res, new ClassSorter());
return res;
}
/**
* Class to iterate over a Jar file.
*
* @author Erich Schubert
*
* @apiviz.exclude
*/
static class JarClassIterator implements Iterator, Iterable {
private Enumeration jarentries;
private String ne;
/**
* Constructor from Jar file.
*
* @param path Jar file entries to iterate over.
*/
public JarClassIterator(String path) {
try {
JarFile jf = new JarFile(path);
this.jarentries = jf.entries();
this.ne = findNext();
}
catch(IOException e) {
LoggingUtil.exception("Error opening jar file: " + path, e);
this.jarentries = null;
this.ne = null;
}
}
@Override
public boolean hasNext() {
// Do we have a next entry?
return (ne != null);
}
/**
* Find the next entry, since we need to skip some jar file entries.
*
* @return next entry or null
*/
private String findNext() {
while(jarentries.hasMoreElements()) {
JarEntry je = jarentries.nextElement();
String name = je.getName();
if(name.endsWith(".class")) {
String classname = name.substring(0, name.length() - ".class".length());
if(classname.endsWith(ClassParameter.FACTORY_POSTFIX) || !classname.contains("$")) {
return classname.replace("/", ".");
}
}
}
return null;
}
@Override
public String next() {
// Return the previously stored entry.
String ret = ne;
ne = findNext();
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public Iterator iterator() {
return this;
}
}
/**
* Class to iterate over a directory tree.
*
* @author Erich Schubert
*
* @apiviz.exclude
*/
static class DirClassIterator implements Iterator, Iterable {
private String prefix;
private Stack set = new Stack();
private String cur;
/**
* Constructor from Directory
*
* @param path Directory to iterate over
*/
public DirClassIterator(File path) {
this.prefix = path.getAbsolutePath();
this.set.push(path);
this.cur = findNext();
}
@Override
public boolean hasNext() {
// Do we have a next entry?
return (cur != null);
}
/**
* Find the next entry, since we need to skip some jar file entries.
*
* @return next entry or null
*/
private String findNext() {
while(set.size() > 0) {
File f = set.pop();
// recurse into directories
if(f.isDirectory()) {
// TODO: do not recurse into ignored packages!.
for(File newf : f.listFiles()) {
set.push(newf);
}
continue;
}
String name = f.getAbsolutePath();
if(name.startsWith(prefix)) {
int l = prefix.length();
if(name.charAt(l) == File.separatorChar) {
l += 1;
}
name = name.substring(l);
}
else {
LoggingUtil.warning("I was expecting all directories to start with '" + prefix + "' but '" + name + "' did not.");
}
if(name.endsWith(".class")) {
String classname = name.substring(0, name.length() - ".class".length());
if(classname.endsWith(ClassParameter.FACTORY_POSTFIX) || !classname.contains("$")) {
return classname.replace(File.separator, ".");
}
}
}
return null;
}
@Override
public String next() {
// Return the previously stored entry.
String ret = this.cur;
this.cur = findNext();
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public Iterator iterator() {
return this;
}
}
/**
* Sort classes by their class name. Package first, then class.
*
* @author Erich Schubert
*
* @apiviz.exclude
*/
public static class ClassSorter implements Comparator> {
@Override
public int compare(Class> o1, Class> o2) {
int pkgcmp = o1.getPackage().getName().compareTo(o2.getPackage().getName());
if(pkgcmp != 0) {
return pkgcmp;
}
return o1.getCanonicalName().compareTo(o2.getCanonicalName());
}
}
}