xapi.dev.scanner.impl.ClasspathScannerDefault Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xapi-dev Show documentation
Show all versions of xapi-dev Show documentation
Everything needed to run a comprehensive dev environment.
Just type X_ and pick a service from autocomplete;
new dev modules will be added as they are built.
The only dev service not included in the uber jar is xapi-dev-maven,
as it includes all runtime dependencies of maven, adding ~4 seconds to build time,
and 6 megabytes to the final output jar size (without xapi-dev-maven, it's ~1MB).
The newest version!
package xapi.dev.scanner.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import xapi.annotation.inject.InstanceDefault;
import xapi.collect.api.Fifo;
import xapi.collect.impl.SimpleFifo;
import xapi.dev.resource.impl.ByteCodeResource;
import xapi.dev.resource.impl.FileBackedResource;
import xapi.dev.resource.impl.JarBackedResource;
import xapi.dev.resource.impl.SourceCodeResource;
import xapi.dev.resource.impl.StringDataResource;
import xapi.dev.scanner.api.ClasspathScanner;
import xapi.except.ThreadsafeUncaughtExceptionHandler;
import xapi.util.X_Debug;
import xapi.util.X_Namespace;
import xapi.util.X_Properties;
import xapi.util.X_Util;
@InstanceDefault(implFor = ClasspathScanner.class)
public class ClasspathScannerDefault implements ClasspathScanner {
final Set pkgs;
final Set> annotations;
final Set resourceMatchers;
final Set bytecodeMatchers;
final Set sourceMatchers;
final Set activeJars;
public ClasspathScannerDefault() {
pkgs = new HashSet();
annotations = new HashSet>();
resourceMatchers = new HashSet();
bytecodeMatchers = new HashSet();
sourceMatchers = new HashSet();
activeJars = new HashSet();
}
protected class ScanRunner implements Runnable {
private final URL classpath;
private final ClasspathResourceMap map;
private final int priority;
private final Iterable pathRoot;
private Thread creatorThread;
public ScanRunner(URL classpath, Iterable pkgs,
ClasspathResourceMap map, int priority) {
this.classpath = classpath;
this.map = map;
this.priority = priority;
this.pathRoot = pkgs;
creatorThread = Thread.currentThread();
}
@Override
public void run() {
Thread.currentThread().setUncaughtExceptionHandler(new ThreadsafeUncaughtExceptionHandler(creatorThread));
creatorThread = null;
// determine if we should run in jar mode or file mode
File file;
String path = classpath.toExternalForm();
boolean jarUrl = path.startsWith("jar:");
if (jarUrl) {
path = path.substring("jar:".length());
}
boolean fileUrl = path.startsWith("file:");
if (fileUrl) {
path = path.substring("file:".length());
}
boolean jarFile = path.contains(".jar!");
if (jarFile) {
path = path.substring(0, path.indexOf(".jar!") + ".jar".length());
} else {
jarFile = path.endsWith(".jar");
}
if (!(file = new java.io.File(path)).exists()) {
path = new File(path).toURI().toString();
if ((file = new java.io.File(path)).exists()) {
// should be impossible since we get these urls from classloader
throw X_Util.rethrow(new FileNotFoundException());
}
}
try {
if (classpath.getProtocol().equals("jar")) {
scan(((JarURLConnection)classpath.openConnection()).getJarFile());
return;
}
assert classpath.getProtocol().equals("file") : "ScanRunner only handles url and file protocols";
if (jarFile) {
scan(new JarFile(file));
} else {
// For files, we need to strip everything up to the package we are
// scanning
String fileRoot = file.getCanonicalPath().replace('\\', '/');
int delta = 0;
if (!fileRoot.endsWith("/")) {
delta = -1;
fileRoot += "/";
}
for (String pkg : pathRoot) {
if (fileRoot.replace('/', '.').endsWith(pkg.endsWith(".")?pkg:pkg+".")) {
scan(file, fileRoot.substring(0, fileRoot.length() - pkg.length() + delta));
break;
}
}
}
} catch (Exception e) {
Thread t = Thread.currentThread();
t.getUncaughtExceptionHandler().uncaughtException(t, e);
}
}
private final void scan(File file, String pathRoot) throws IOException {
if (file.isDirectory()) {
scan(file.listFiles(), pathRoot);
} else {
addFile(file, pathRoot);
}
}
private void scan(File[] listFiles, String pathRoot) throws IOException {
for (File file : listFiles) {
scan(file, pathRoot);
}
}
private final void scan(JarFile jarFile) {
if (activeJars.add(jarFile.getName())) {
Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry next = entries.nextElement();
addEntry(jarFile, next);
}
}
}
protected void addFile(File file, String pathRoot) throws IOException {
String name = file.getCanonicalPath().substring(pathRoot.length());
if (name.startsWith(File.separator)) {
name = name.substring(1);
}
if (name.endsWith(".class")) {
if (map.includeBytecode(name)) {
map.addBytecode(name, new ByteCodeResource(
new FileBackedResource(name, file, priority)));
}
} else if (name.endsWith(".java")) {
if (map.includeSourcecode(name)) {
map.addSourcecode(name, new SourceCodeResource(
new FileBackedResource(name, file, priority)));
}
} else {
if (map.includeResource(name)) {
map.addResource(name, new StringDataResource(
new FileBackedResource(name, file, priority)));
}
}
}
protected void addEntry(JarFile file, JarEntry entry) {
String name = entry.getName();
for (String pkg : pkgs) {
if (name.startsWith(pkg)) {
if (name.endsWith(".class")) {
if (map.includeBytecode(name)) {
map.addBytecode(name, new ByteCodeResource(
new JarBackedResource(file, entry, priority)));
}
} else if (name.endsWith(".java")) {
if (map.includeSourcecode(name)) {
map.addSourcecode(name, new SourceCodeResource(
new JarBackedResource(file, entry, priority)));
}
} else {
if (map.includeResource(name)) {
map.addResource(name, new StringDataResource(
new JarBackedResource(file, entry, priority)));
}
}
return;
}
}
}
}
@Override
public ClasspathScanner scanAnnotation(Class annotation) {
annotations.add(annotation);
return this;
}
@Override
public ClasspathScanner scanAnnotations(@SuppressWarnings("unchecked")
Class ... annotations) {
for (Class annotation : annotations) {
this.annotations.add(annotation);
}
return this;
}
@Override
public ClasspathResourceMap scan(ClassLoader loaders) {
ExecutorService executor = newExecutor();
try {
ClasspathResourceMap map = scan(loaders, executor).call();
synchronized (map) {
return map;
}
} catch (Exception e) {
throw X_Debug.rethrow(e);
}
}
@Override
public ExecutorService newExecutor() {
return Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()*3/2);
}
@Override
public synchronized Callable scan(ClassLoader loadFrom, ExecutorService executor) {
// perform the actual scan
Map> classPaths = new LinkedHashMap>();
if (pkgs.size() == 0 || (pkgs.size() == 1 && "".equals(pkgs.iterator().next()))) {
for (String pkg : X_Properties.getProperty(X_Namespace.PROPERTY_RUNTIME_SCANPATH,
",META-INF,com,org,net,xapi,java").split(",\\s*")) {
pkgs.add(pkg);
}
}
for (String pkg : pkgs) {
final Enumeration resources;
try {
resources = loadFrom.getResources(
pkg.equals(".*")//||pkg.equals("")
?"":pkg.replace('.', '/')
);
} catch (Exception e) {
e.printStackTrace();
continue;
}
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
String file = resource.toExternalForm();
if (file.contains("gwt-dev.jar")) {
continue;
}
Fifo fifo = classPaths.get(resource);
if (fifo == null) {
fifo = new SimpleFifo();
fifo.give(pkg);
classPaths.put(resource, fifo);
} else {
fifo.remove(pkg);
fifo.give(pkg);
}
}
}
int pos = 0;
final ClasspathResourceMap map = new ClasspathResourceMap(executor,
annotations, bytecodeMatchers, resourceMatchers, sourceMatchers);
map.setClasspath(classPaths.keySet());
final Fifo> jobs = new SimpleFifo>();
class Finisher implements Callable{
@Override
public ClasspathResourceMap call() throws Exception {
while (!jobs.isEmpty()) {
// drain the work pool
Iterator> iter = jobs.forEach().iterator();
while (iter.hasNext()) {
if (iter.next().isDone()) {
iter.remove();
}
}
try {
Thread.sleep(0, 500);
} catch (InterruptedException e) {
throw X_Debug.rethrow(e);
}
}
return map;
}
}
for (URL url : classPaths.keySet()) {
Fifo packages = classPaths.get(url);
ScanRunner scanner = newScanRunner(url, map, executor, packages.forEach(), pos);
jobs.give(executor.submit(scanner));
}
return new Finisher();
}
private ScanRunner newScanRunner(URL classPath, ClasspathResourceMap map, ExecutorService executor,
Iterable pkgs, int priority) {
return new ScanRunner(classPath, pkgs, map, priority);
}
@Override
public ClasspathScanner scanPackage(String pkg) {
pkgs.add(pkg);
return this;
}
@Override
public ClasspathScanner scanPackages(String ... pkgs) {
for (String pkg : pkgs) {
this.pkgs.add(pkg);
}
return this;
}
@Override
public ClasspathScanner matchClassFile(String regex) {
bytecodeMatchers.add(Pattern.compile(regex));
return this;
}
@Override
public ClasspathScanner matchClassFiles(String ... regexes) {
for (String regex : regexes) {
bytecodeMatchers.add(Pattern.compile(regex));
}
return this;
}
@Override
public ClasspathScanner matchResource(String regex) {
resourceMatchers.add(Pattern.compile(regex));
return this;
}
@Override
public ClasspathScanner matchResources(String ... regexes) {
for (String regex : regexes) {
resourceMatchers.add(Pattern.compile(regex));
}
return this;
}
@Override
public ClasspathScanner matchSourceFile(String regex) {
sourceMatchers.add(Pattern.compile(regex));
return this;
}
@Override
public ClasspathScanner matchSourceFiles(String ... regexes) {
for (String regex : regexes) {
sourceMatchers.add(Pattern.compile(regex));
}
return this;
}
}