All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xapi.dev.scanner.impl.ClasspathScannerDefault Maven / Gradle / Ivy

Go to download

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;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy