/**
*
* Copyright
* 2009-2015 Jayway Products AB
* 2016-2017 Föreningen Sambruk
*
* Licensed under AGPL, Version 3.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/agpl.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package se.streamsource.streamflow.util;
import org.qi4j.api.specification.Specification;
import org.qi4j.api.util.Function;
import org.qi4j.api.util.Iterables;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import static org.qi4j.api.util.Iterables.*;
/**
* Scan classpath for classes that matches given criteria. Useful for automated assemblies with lots of similar classes
*/
public class ClassScanner
{
/**
* Get all classes from the package of the given class, and recursively in subpackages.
* <p/>
* This only works if the seed class is loaded from a file: URL. Jar files are possible as well.
*
* @param seedClass
* @return
*/
public static Iterable<Class> getClasses(final Class seedClass)
{
URL location = seedClass.getProtectionDomain().getCodeSource().getLocation();
if (!location.getProtocol().equals("file"))
throw new IllegalArgumentException("Can only enumerate classes from file system locations. URL is:" + location);
// JDK: Location is directory, Tomcat: Location is actual class file
String tmpFileName = null;
try
{
tmpFileName = URLDecoder.decode(location.getPath(), "UTF-8");
if( tmpFileName.endsWith( ".class" ))
{
tmpFileName = tmpFileName.substring( 0, tmpFileName.length() - (seedClass.getName().length() + 6) );
}
} catch (UnsupportedEncodingException e)
{
// Ignore
}
final File file = new File(tmpFileName);
if (file.getName().endsWith(".jar"))
{
try
{
JarFile jarFile = new JarFile(file);
Iterable<JarEntry> entries = Iterables.iterable(jarFile.entries());
try
{
return Iterables.addAll(new ArrayList<Class>(), filter(new NonAbstractClass(),
map(new Function<JarEntry, Class>()
{
public Class map(JarEntry jarEntry)
{
String name = jarEntry.getName();
name = name.substring(0, name.length() - 6);
name = name.replace('/', '.');
try
{
return seedClass.getClassLoader().loadClass(name);
} catch (ClassNotFoundException e)
{
return null;
}
}
}
, filter(new Specification<JarEntry>()
{
public boolean satisfiedBy(JarEntry jarEntry)
{
return jarEntry.getName().endsWith(".class");
}
}, entries))));
} finally
{
jarFile.close();
}
} catch (IOException e)
{
throw new IllegalArgumentException("Could not open jar file " + file, e);
}
} else
{
final File path = new File(file, seedClass.getPackage().getName().replace('.', File.separatorChar));
Iterable<File> files = getFiles(path, new Specification<File>()
{
public boolean satisfiedBy(File file)
{
return file.getName().endsWith(".class");
}
});
return filter(new NonAbstractClass(),
map(new Function<File, Class>()
{
public Class map(File f)
{
String fileName = f.getAbsolutePath().substring(file.toString().length() + 1);
fileName = fileName.replace(File.separatorChar, '.').substring(0, fileName.length() - 6);
try
{
return seedClass.getClassLoader().loadClass(fileName);
} catch (Throwable e)
{
return null;
}
}
}, files));
}
}
/**
* Useful specification for filtering classes based on a regular expression matching the class names.
* <p/>
* Example: matches(".*Model") -> match only class names that end with Model
* <p/>
* Example:
*
* @param regex
* @return
*/
public static Specification<Class> matches(String regex)
{
final Pattern pattern = Pattern.compile(regex);
return new Specification<Class>()
{
public boolean satisfiedBy(Class aClass)
{
return pattern.matcher(aClass.getName()).matches();
}
};
}
private static Iterable<File> getFiles(File directory, final Specification<File> filter)
{
return flatten(filter(filter, iterable(directory.listFiles())),
flatten(map(new Function<File, Iterable<File>>()
{
public Iterable<File> map(File file)
{
return getFiles(file, filter);
}
}, filter(new Specification<File>()
{
public boolean satisfiedBy(File file)
{
return file.isDirectory();
}
}, iterable(directory.listFiles())))));
}
private static class NonAbstractClass
implements Specification<Class>
{
public boolean satisfiedBy(Class item)
{
return item.isInterface() || !Modifier.isAbstract(item.getModifiers());
}
}
}