/** * Copyright (C) 2010 Daniel Manzke <daniel.manzke@googlemail.com> * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 de.devsurf.injection.guice.scanner.asm; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; 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.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; import org.objectweb.asm.ClassReader; import de.devsurf.injection.guice.scanner.ClasspathScanner; import de.devsurf.injection.guice.scanner.PackageFilter; import de.devsurf.injection.guice.scanner.features.ScannerFeature; /** * This Implementation only uses the ASM-API to read all recognized classes. It * doesn't depend on any further 3rd-Party libraries. * * @author Daniel Manzke * */ public class ASMClasspathScanner implements ClasspathScanner { public static String LINE_SEPARATOR = System.getProperty("line.separator"); private Logger _logger = Logger.getLogger(ASMClasspathScanner.class.getName()); @Inject @Named("classpath") private URL[] classPath; private List<Pattern> patterns = new ArrayList<Pattern>(); private int count; private Set<String> visited; private BlockingQueue<AnnotationCollector> collectors; @Inject public ASMClasspathScanner(Set<ScannerFeature> listeners, @Named("packages") PackageFilter... filter) { int cores = Runtime.getRuntime().availableProcessors(); this.collectors = new ArrayBlockingQueue<AnnotationCollector>(cores); for(int i=0;i<cores;i++){ try { collectors.put(new AnnotationCollector()); } catch (InterruptedException e) { // ignore } } for (PackageFilter p : filter) { includePackage(p); } for (ScannerFeature listener : listeners) { addFeature(listener); } visited = Collections.synchronizedSet(new HashSet<String>()); } @Override public void addFeature(ScannerFeature feature) { for(AnnotationCollector collector : collectors){ collector.addScannerFeature(feature); } } @Override public void removeFeature(ScannerFeature feature) { for(AnnotationCollector collector : collectors){ collector.addScannerFeature(feature); } } @Override public List<ScannerFeature> getFeatures() { List<ScannerFeature> features; try { AnnotationCollector collector = collectors.take(); features = collector.getScannerFeatures(); collectors.put(collector); } catch (InterruptedException e) { // ignore features = Collections.emptyList(); } return features; } @Override public void includePackage(final PackageFilter filter) { String packageName = filter.getPackage(); String pattern = ".*" + packageName.replace(".", "/"); if (filter.deep()) { pattern = pattern + "/(?:\\w|/)*([A-Z](?:\\w|\\$)+)\\.class$"; } else { pattern = pattern + "/([A-Z](?:\\w|\\$)+)\\.class$"; } if (_logger.isLoggable(Level.FINE)) { _logger.fine("Including Package for scanning: " + packageName + " generating Pattern: " + pattern); } patterns.add(Pattern.compile(pattern)); } @Override public void excludePackage(final PackageFilter filter) { // TODO Could use Predicate of Google } public void scan() throws IOException { ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); if (_logger.isLoggable(Level.INFO)) { StringBuilder builder = new StringBuilder(); builder.append("Using Root-Path for Classpath scanning:").append(LINE_SEPARATOR); for (URL url : classPath) { builder.append(url.toString()).append(LINE_SEPARATOR); } _logger.log(Level.INFO, builder.toString()); } List<Future<?>> futures = new ArrayList<Future<?>>(); for (final URL url : classPath) { Future<?> task = pool.submit(new Runnable() { @Override public void run() { try { if (url.toString().startsWith("jar:")) { visitJar(url); return; } URI uri; File entry; try { uri = url.toURI(); entry = new File(uri); if (!entry.exists()) { _logger.log(Level.FINE, "Skipping Entry " + entry + ", because it doesn't exists."); return; } } catch (URISyntaxException e) { // ignore _logger.log(Level.WARNING, "Using invalid URL for Classpath Scanning: " + url, e); return; } catch (Throwable e) { // ignore _logger.log(Level.SEVERE, "Using invalid URL for Classpath Scanning: " + url, e); return; } if (entry.isDirectory()) { visitFolder(entry); } else { String path = uri.toString(); if (matches(path)) { if (!visited.contains(entry.getAbsolutePath())) { visitClass(new FileInputStream(entry)); visited.add(entry.getAbsolutePath()); } } else if (path.endsWith(".jar")) { visitJar(entry); } } } catch (FileNotFoundException e) { _logger.log(Level.FINE, "Skipping Entry " + url + ", because it doesn't exists.",e); } catch (IOException e) { _logger.log(Level.FINE, "Skipping Entry " + url + ", because it couldn't be scanned.",e); } catch (Throwable e) { _logger.log(Level.WARNING, "Skipping Entry " + url + ", because it couldn't be scanned.",e); } } }); futures.add(task); } for (Future<?> future : futures) { try { future.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { _logger.log(Level.SEVERE, e.getMessage(), e); } } pool.shutdown(); destroy(); } public void destroy(){ classPath = null; collectors.clear(); collectors = null; patterns.clear(); patterns = null; visited.clear(); visited = null; } private void visitFolder(File folder) throws IOException { _logger.log(Level.FINE, "Scanning Folder: " + folder.getAbsolutePath()); File[] files = folder.listFiles(); for (File file : files) { if (file.isDirectory()) { visitFolder(file); } else { String path = file.toURI().toString(); if (matches(path)) { if (!visited.contains(file.getAbsolutePath())) { visitClass(new FileInputStream(file)); visited.add(file.getAbsolutePath()); } } else if (path.endsWith(".jar")) { visitJar(file); } } } } private void visitJar(URL url) throws IOException { if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Scanning JAR-File: " + url); } JarURLConnection conn = (JarURLConnection) url.openConnection(); _visitJar(conn.getJarFile()); } private void visitJar(File file) throws IOException { if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Scanning JAR-File: " + file.getAbsolutePath()); } JarFile jarFile = new JarFile(file); _visitJar(jarFile); } private void _visitJar(JarFile jarFile) throws IOException { Enumeration<JarEntry> jarEntries = jarFile.entries(); for (JarEntry jarEntry = null; jarEntries.hasMoreElements();) { count++; jarEntry = jarEntries.nextElement(); String name = jarEntry.getName(); if (!jarEntry.isDirectory() && matches(name)) { if (!visited.contains(name)) { visitClass(jarFile.getInputStream(jarEntry)); visited.add(name); } } } } private void visitClass(InputStream in) throws IOException { count++; ClassReader reader = new ClassReader(new BufferedInputStream(in)); try { AnnotationCollector collector = collectors.take(); reader.accept(collector, AnnotationCollector.ASM_FLAGS); collectors.put(collector); } catch (InterruptedException e) { // ignore } } private boolean matches(String name) { boolean returned = false; try{ for (Pattern pattern : patterns) { if (pattern.matcher(name).matches()) { return (returned = true); } } return returned; }finally{ if(_logger.isLoggable(Level.FINE)){ _logger.log(Level.FINE, ASMClasspathScanner.class.getSimpleName()+".matches(..) - \""+name+"\" -> "+returned); } } } public static void main(String[] args) { String p = "((?:\\w|/)+)/([a-zA-Z_\\$][\\w\\$]*)*\\.class$"; List<String> ps = Arrays.asList("com/saperion/test/Impl$Test.class", "com/saperion/test/Impl.class", "C:/programme/com/saperion/test/Impl$Test.class", "C:/programme/com/saperion/test/Impl.class", "C:/programme/myjar.jar!com/saperion/test/Impl$Test.class", "C:/programme/myjar.jar!com/saperion/test/Impl.class"); Pattern pattern = Pattern.compile(p, Pattern.COMMENTS); for (String s : ps) { Matcher matcher = pattern.matcher(s); if (matcher.matches()) { System.out.println("Num groups: " + matcher.groupCount()); System.out.println("Package: " + matcher.group(1)); System.out.println("Class: " + matcher.group(2)); } else { System.err.println("Input does not match pattern."); } } String file = "F:\\git\\twiddns\\target\\classes\\de\\devsurf\\twiddns\\test\\Publisher.class".replace("\\", "/"); String packageName = "de.devsurf.twiddns"; String patternStr = ".*" + packageName.replace(".", "/"); patternStr = patternStr + "/(?:\\w|/)*([A-Z](?:\\w|\\$)+)\\.class$"; pattern = Pattern.compile(patternStr); Matcher matcher = pattern.matcher(file); System.out.println(matcher.matches()); System.out.println(":D"); } }