/*
* Copyright (c) 2002-2009 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j 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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.shell.impl;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Well, since Class#getExtendingClasses doesn't exist, use this instead.
* @author Mattias Persson
*/
public class ClassLister
{
private static final String CLASS_NAME_ENDING = ".class";
/**
* @param <T> the class type.
* @param superClass the class which the resulting classes must implement
* or extend.
* @param lookInThesePackages an optional collection of which java packages
* to search in. If null is specified then all packages are searched.
* @return all classes (in the class path) which extends or implements
* a certain class.
*/
public static <T> Collection<Class<? extends T>>
listClassesExtendingOrImplementing( Class<? extends T> superClass,
Collection<String> lookInThesePackages )
{
String classPath = System.getProperty( "java.class.path" );
StringTokenizer tokenizer = new StringTokenizer( classPath,
File.pathSeparator );
Collection<Class<? extends T>> classes =
new HashSet<Class<? extends T>>();
while ( tokenizer.hasMoreTokens() )
{
collectClasses( classes, superClass, lookInThesePackages,
tokenizer.nextToken() );
}
return Collections.unmodifiableCollection( classes );
}
private static <T> void collectClasses(
Collection<Class<? extends T>> classes, Class<? extends T> superClass,
Collection<String> lookInThesePackages, String classPathToken )
{
File directory = new File( classPathToken );
if ( !directory.exists() )
{
return;
}
try
{
if ( directory.isDirectory() )
{
collectFromDirectory( classes, superClass, lookInThesePackages,
"", directory );
}
else
{
JarFile jarFile = new JarFile( directory );
Enumeration<JarEntry> entries = jarFile.entries();
while ( entries.hasMoreElements() )
{
JarEntry entry = entries.nextElement();
String entryName = fixJarEntryClassName( entry.getName() );
tryCollectClass( classes, superClass, lookInThesePackages,
entryName );
}
}
}
catch ( IOException e )
{
throw new RuntimeException( "Error collecting classes from " +
classPathToken, e );
}
}
private static <T> void collectFromDirectory(
Collection<Class<? extends T>> classes, Class<? extends T> superClass,
Collection<String> lookInThesePackages, String prefix, File directory )
{
// Should we even bother walking through these directories?
// Check with lookInThesePackages if there's a package matching this.
boolean botherToBrowseFurtherDown = false;
if ( lookInThesePackages != null )
{
for ( String packageName : lookInThesePackages )
{
if ( packageName.startsWith( prefix ) )
{
botherToBrowseFurtherDown = true;
break;
}
}
}
else
{
botherToBrowseFurtherDown = true;
}
if ( !botherToBrowseFurtherDown )
{
return;
}
File[] files = directory.listFiles();
if ( files == null )
{
return;
}
for ( File file : files )
{
if ( file.isDirectory() )
{
collectFromDirectory( classes, superClass, lookInThesePackages,
addToPrefix( prefix, file.getName() ), file );
}
else
{
String className = addToPrefix( prefix, file.getName() );
className = trimFromClassEnding( className );
tryCollectClass( classes, superClass, lookInThesePackages,
className );
}
}
}
private static String addToPrefix( String prefix, String toAdd )
{
prefix = prefix == null ? "" : prefix;
if ( prefix.length() > 0 )
{
prefix += ".";
}
prefix += toAdd;
return prefix;
}
private static String trimFromClassEnding( String className )
{
if ( className.endsWith( CLASS_NAME_ENDING ) )
{
className = className.substring( 0,
className.length() - CLASS_NAME_ENDING.length() );
}
return className;
}
private static String fixJarEntryClassName( String entryName )
{
entryName = entryName.replace( File.separatorChar, '.' );
entryName = entryName.replace( '/', '.' );
return trimFromClassEnding( entryName );
}
private static <T> void tryCollectClass(
Collection<Class<? extends T>> classes, Class<? extends T> superClass,
Collection<String> lookInThesePackages, String className )
{
try
{
if ( !classNameIsInPackage( className, lookInThesePackages ) )
{
return;
}
Class<? extends T> cls = Class.forName( className ).asSubclass(
superClass );
if ( cls.isInterface() ||
Modifier.isAbstract( cls.getModifiers() ) )
{
return;
}
if ( superClass.isAssignableFrom( cls ) )
{
classes.add( cls );
}
}
catch ( Throwable e )
{
// Ok
}
}
private static boolean classNameIsInPackage( String className,
Collection<String> lookInThesePackages )
{
if ( lookInThesePackages == null )
{
return true;
}
String packageName = packageForClassName( className );
return lookInThesePackages.contains( packageName );
}
private static String packageForClassName( String className )
{
int index = className.lastIndexOf( '.' );
if ( index == -1 )
{
return className;
}
return className.substring( 0, index );
}
}