package org.opentrafficsim.road;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
* Build a list of the classes in the project (or under a specific directory/package in the project).
* Adapted from
* http://stackoverflow.com/questions/3923129/get-a-list-of-resources-from-classpath-directory which apparently copied the
* code from
* http://forums.devx.com/showthread.php?153784-how-to-list-resources-in-a-package. Original poster stoughto has not visited
* that forum after 2006.
*
* Copyright (c) 2006 by stoughto! TODO replace this by something that is provably free code.
*
* @version $Revision$, $LastChangedDate$, by $Author$, initial version Apr 8, 2016
* @author Alexander Verbraeck
* @author Peter Knoppers
*/
public final class ClassList
{
/**
* Do not instantiate this class.
*/
private ClassList()
{
// This class cannot be instantiated
}
/**
* For all elements of java.class.path get a Collection of resources Pattern pattern = Pattern.compile(".*"); gets all
* resources.
* @param pattern the pattern to match
* @return the resources in the order they are found
*/
public static Collection getResources(final Pattern pattern)
{
final ArrayList retval = new ArrayList();
final String classPath = System.getProperty("java.class.path", ".");
final String[] classPathElements = classPath.split(System.getProperty("path.separator"));
for (final String element : classPathElements)
{
retval.addAll(getResources(element, pattern));
}
return retval;
}
/**
* Recursively load names from a resource tree.
* @param element String; root of the tree to load
* @param pattern Pattern; only return names matching this pattern
* @return Collection<String>; a list of names
*/
private static Collection getResources(final String element, final Pattern pattern)
{
final ArrayList retval = new ArrayList();
final File file = new File(element);
if (file.isDirectory())
{
retval.addAll(getResourcesFromDirectory(file, pattern));
}
else
{
retval.addAll(getResourcesFromJarFile(file, pattern));
}
return retval;
}
/**
* Recursively load names from a jar file.
* @param file File; root of the tree to load
* @param pattern Pattern; only return names matching this pattern
* @return Collection<String>; a list of names
*/
private static Collection getResourcesFromJarFile(final File file, final Pattern pattern)
{
final ArrayList retval = new ArrayList();
ZipFile zf;
try
{
zf = new ZipFile(file);
}
catch (final ZipException e)
{
throw new Error(e);
}
catch (final IOException e)
{
throw new Error(e);
}
final Enumeration> e = zf.entries();
while (e.hasMoreElements())
{
final ZipEntry ze = (ZipEntry) e.nextElement();
final String fileName = ze.getName();
final boolean accept = pattern.matcher(fileName).matches();
if (accept)
{
retval.add(fileName);
}
}
try
{
zf.close();
}
catch (final IOException e1)
{
throw new Error(e1);
}
return retval;
}
/**
* Recursively load names from a directory.
* @param directory File; root of the tree to load
* @param pattern Pattern; only return names matching this pattern
* @return Collection<String>; a list of names
*/
private static Collection getResourcesFromDirectory(final File directory, final Pattern pattern)
{
final ArrayList retval = new ArrayList();
final File[] fileList = directory.listFiles();
if (null == fileList)
{
throw new Error("Could not list files in directory " + directory.toString());
}
for (final File file : fileList)
{
if (file.isDirectory())
{
retval.addAll(getResourcesFromDirectory(file, pattern));
}
else
{
try
{
final String fileName = file.getCanonicalPath();
final boolean accept = pattern.matcher(fileName).matches();
if (accept)
{
retval.add(fileName);
}
}
catch (final IOException e)
{
throw new Error(e);
}
}
}
return retval;
}
/**
* Return a List of all the classes under a package. Test-classes are excluded from the result.
* @param packageRoot String package name
* @param excludeInterfaces boolean; if true; interfaces are excluded from the result
* @return Collection<Class<?>>; the classes under the package
*/
public static Collection> classList(final String packageRoot, final boolean excludeInterfaces)
{
Collection classList = ClassList.getResources(Pattern.compile(".*[^-]classes." + packageRoot + ".*\\.class"));
Collection> result = new ArrayList>();
for (String className : classList)
{
int pos = className.indexOf("\\org\\");
if (pos >= 0)
{
className = className.substring(pos + 1);
}
className = className.replaceAll("\\\\", ".");
pos = className.lastIndexOf(".class");
if (pos >= 0)
{
className = className.substring(0, pos);
}
if (className.endsWith("package-info"))
{
continue; // Not a real class
}
// System.out.println("Checking class \"" + className + "\"");
try
{
Class> c = Class.forName(className);
// System.out.println("modifiers: " + Modifier.toString(c.getModifiers()));
boolean exclude = false;
if (excludeInterfaces)
{
for (String modifierString : Modifier.toString(c.getModifiers()).split(" "))
{
if (modifierString.equals("interface"))
{
// System.out.println(className + " is an interface");
exclude = true;
continue;
}
}
}
if (!exclude)
{
result.add(c);
}
}
catch (ClassNotFoundException exception)
{
exception.printStackTrace();
}
}
return result;
}
/**
* Determine if a class is an anonymous inner class.
* @param c Class; the class to check
* @return boolean; true if c is an anonymous inner class; false otherwise
*/
public static boolean isAnonymousInnerClass(final Class> c)
{
String className = c.getName();
int pos = className.lastIndexOf("$");
if (pos > 0)
{
while (++pos < className.length())
{
if (!Character.isDigit(className.charAt(pos)))
{
break;
}
}
if (pos >= className.length())
{
return true;
}
}
return false;
}
/**
* Report if a class has non-static fields.
* @param c Class<?>; the class
* @return boolean; true if the class has non-static fields
*/
public static boolean hasNonStaticFields(final Class> c)
{
for (Field f : c.getDeclaredFields())
{
//System.out.println("field " + f.getName() + ": " + Modifier.toString(f.getModifiers()));
if (!Modifier.isStatic(f.getModifiers()))
{
return true;
}
}
if (c.equals(Object.class))
{
return false;
}
return hasNonStaticFields(c.getSuperclass());
}
/**
* List the resources that match args[0], or a fixed pattern to demonstrate the use of this class.
* @param args args[0] is the pattern to match, or list all resources matching a built-in pattern if there are no args
*/
public static void main(final String[] args)
{
Pattern pattern;
if (args.length < 1)
{
pattern = Pattern.compile(".*[^-]classes.org.opentrafficsim.*\\.class");
}
else
{
pattern = Pattern.compile(args[0]);
}
final Collection list = ClassList.getResources(pattern);
for (final String name : list)
{
System.out.println(name);
}
}
}