1 package org.apache.onami.autobind.scanner.asm;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import static java.lang.Runtime.getRuntime;
21 import static java.lang.String.format;
22 import static java.lang.System.getProperty;
23 import static java.util.Collections.synchronizedSet;
24 import static java.util.concurrent.Executors.newFixedThreadPool;
25 import static java.util.logging.Level.FINE;
26 import static java.util.logging.Level.INFO;
27 import static java.util.logging.Level.SEVERE;
28 import static java.util.logging.Level.WARNING;
29 import static java.util.logging.Logger.getLogger;
30 import static java.util.regex.Pattern.compile;
31 import static org.apache.onami.autobind.scanner.asm.AnnotationCollector.ASM_FLAGS;
32
33 import java.io.BufferedInputStream;
34 import java.io.File;
35 import java.io.FileInputStream;
36 import java.io.FileNotFoundException;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.net.JarURLConnection;
40 import java.net.URI;
41 import java.net.URISyntaxException;
42 import java.net.URL;
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.Enumeration;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Set;
49 import java.util.concurrent.ArrayBlockingQueue;
50 import java.util.concurrent.BlockingQueue;
51 import java.util.concurrent.ExecutionException;
52 import java.util.concurrent.ExecutorService;
53 import java.util.concurrent.Future;
54 import java.util.jar.JarEntry;
55 import java.util.jar.JarFile;
56 import java.util.logging.Level;
57 import java.util.logging.Logger;
58 import java.util.regex.Pattern;
59
60 import javax.inject.Inject;
61 import javax.inject.Named;
62
63 import org.apache.onami.autobind.scanner.ClasspathScanner;
64 import org.apache.onami.autobind.scanner.PackageFilter;
65 import org.apache.onami.autobind.scanner.features.ScannerFeature;
66 import org.objectweb.asm.ClassReader;
67
68
69
70
71
72 public class ASMClasspathScanner
73 implements ClasspathScanner
74 {
75
76 private static final String LINE_SEPARATOR = getProperty( "line.separator" );
77
78 private final Logger _logger = getLogger( getClass().getName() );
79
80 @Inject
81 @Named( "classpath" )
82 private URL[] classPath;
83
84 private List<Pattern> patterns = new ArrayList<Pattern>();
85
86 private final Set<String> visited;
87
88 private final BlockingQueue<AnnotationCollector> collectors;
89
90 @Inject
91 public ASMClasspathScanner( Set<ScannerFeature> listeners, @Named( "packages" ) PackageFilter... filter )
92 {
93 int cores = getRuntime().availableProcessors();
94 this.collectors = new ArrayBlockingQueue<AnnotationCollector>( cores );
95
96 for ( int i = 0; i < cores; i++ )
97 {
98 try
99 {
100 collectors.put( new AnnotationCollector() );
101 }
102 catch ( InterruptedException e )
103 {
104
105 }
106 }
107 for ( PackageFilter p : filter )
108 {
109 includePackage( p );
110 }
111
112 for ( ScannerFeature listener : listeners )
113 {
114 addFeature( listener );
115 }
116 visited = synchronizedSet( new HashSet<String>() );
117 }
118
119 @Override
120 public void addFeature( ScannerFeature feature )
121 {
122 for ( AnnotationCollector collector : collectors )
123 {
124 collector.addScannerFeature( feature );
125 }
126 }
127
128 @Override
129 public void removeFeature( ScannerFeature feature )
130 {
131 for ( AnnotationCollector collector : collectors )
132 {
133 collector.addScannerFeature( feature );
134 }
135 }
136
137 @Override
138 public List<ScannerFeature> getFeatures()
139 {
140 List<ScannerFeature> features;
141 try
142 {
143 AnnotationCollector collector = collectors.take();
144 features = collector.getScannerFeatures();
145 collectors.put( collector );
146 }
147 catch ( InterruptedException e )
148 {
149
150 features = Collections.emptyList();
151 }
152 return features;
153 }
154
155 @Override
156 public void includePackage( final PackageFilter filter )
157 {
158 String packageName = filter.getPackage();
159 String pattern = ".*" + packageName.replace( ".", "/" );
160
161 if ( filter.deep() )
162 {
163 pattern = pattern + "/(?:\\w|/)*([A-Z](?:\\w|\\$)+)\\.class$";
164 }
165 else
166 {
167 pattern = pattern + "/([A-Z](?:\\w|\\$)+)\\.class$";
168 }
169
170 if ( _logger.isLoggable( FINE ) )
171 {
172 _logger.fine( format( "Including Package for scanning: %s generating Pattern: %s", packageName, pattern ) );
173 }
174 patterns.add( compile( pattern ) );
175 }
176
177 @Override
178 public void excludePackage( final PackageFilter filter )
179 {
180
181 }
182
183 public void scan()
184 throws IOException
185 {
186 ExecutorService pool = newFixedThreadPool( getRuntime().availableProcessors() );
187
188 if ( _logger.isLoggable( INFO ) )
189 {
190 StringBuilder builder = new StringBuilder();
191 builder.append( "Using Root-Path for Classpath scanning:" ).append( LINE_SEPARATOR );
192 for ( URL url : classPath )
193 {
194 builder.append( url.toString() ).append( LINE_SEPARATOR );
195 }
196 _logger.log( INFO, builder.toString() );
197 }
198
199 List<Future<?>> futures = new ArrayList<Future<?>>();
200 for ( final URL url : classPath )
201 {
202 Future<?> task = pool.submit( new Runnable()
203 {
204 @Override
205 public void run()
206 {
207 try
208 {
209 if ( url.toString().startsWith( "jar:" ) )
210 {
211 visitJar( url );
212 return;
213 }
214 URI uri;
215 File entry;
216 try
217 {
218 uri = url.toURI();
219 entry = new File( uri );
220 if ( !entry.exists() )
221 {
222 if ( _logger.isLoggable( FINE ) )
223 {
224 _logger.log( FINE, format( "Skipping Entry %s, because it doesn't exists.", entry ) );
225 }
226 return;
227 }
228 }
229 catch ( URISyntaxException e )
230 {
231
232 _logger.log( WARNING, format( "Using invalid URL for Classpath Scanning: %s", url ), e );
233 return;
234 }
235 catch ( Throwable e )
236 {
237
238 _logger.log( SEVERE, format( "Using invalid URL for Classpath Scanning: ", url ), e );
239 return;
240 }
241
242 if ( entry.isDirectory() )
243 {
244 visitFolder( entry );
245 }
246 else
247 {
248 String path = uri.toString();
249 if ( matches( path ) )
250 {
251 if ( !visited.contains( entry.getAbsolutePath() ) )
252 {
253 visitClass( new FileInputStream( entry ) );
254 visited.add( entry.getAbsolutePath() );
255 }
256 }
257 else if ( path.endsWith( ".jar" ) )
258 {
259 visitJar( entry );
260 }
261 }
262 }
263 catch ( FileNotFoundException e )
264 {
265 if ( _logger.isLoggable( FINE ) )
266 {
267 _logger.log( FINE, format( "Skipping Entry %s, because it doesn't exists.", url ), e );
268 }
269 }
270 catch ( IOException e )
271 {
272 if ( _logger.isLoggable( FINE ) )
273 {
274 _logger.log( FINE, format( "Skipping Entry %s, because it couldn't be scanned.", url ), e );
275 }
276 }
277 catch ( Throwable e )
278 {
279 _logger.log( WARNING, format( "Skipping Entry %s, because it couldn't be scanned.", url ), e );
280 }
281 }
282 } );
283 futures.add( task );
284 }
285 for ( Future<?> future : futures )
286 {
287 try
288 {
289 future.get();
290 }
291 catch ( InterruptedException e )
292 {
293 throw new RuntimeException( e );
294 }
295 catch ( ExecutionException e )
296 {
297 _logger.log( SEVERE, e.getMessage(), e );
298 }
299 }
300 pool.shutdown();
301 destroy();
302 }
303
304 public void destroy()
305 {
306 classPath = null;
307 collectors.clear();
308 patterns.clear();
309 patterns = null;
310 visited.clear();
311 }
312
313 private void visitFolder( File folder )
314 throws IOException
315 {
316 if ( _logger.isLoggable( FINE ) )
317 {
318 _logger.log( FINE, format( "Scanning Folder: %s...", folder.getAbsolutePath() ) );
319 }
320
321 File[] files = folder.listFiles();
322 for ( File file : files )
323 {
324 if ( file.isDirectory() )
325 {
326 visitFolder( file );
327 }
328 else
329 {
330 String path = file.toURI().toString();
331 if ( matches( path ) )
332 {
333 if ( !visited.contains( file.getAbsolutePath() ) )
334 {
335 visitClass( new FileInputStream( file ) );
336 visited.add( file.getAbsolutePath() );
337 }
338 }
339 else if ( path.endsWith( ".jar" ) )
340 {
341 visitJar( file );
342 }
343 }
344 }
345 }
346
347 private void visitJar( URL url )
348 throws IOException
349 {
350 if ( _logger.isLoggable( FINE ) )
351 {
352 _logger.log( FINE, format( "Scanning JAR-File: %s", url ) );
353 }
354
355 JarURLConnection conn = (JarURLConnection) url.openConnection();
356 _visitJar( conn.getJarFile() );
357 }
358
359 private void visitJar( File file )
360 throws IOException
361 {
362 if ( _logger.isLoggable( FINE ) )
363 {
364 _logger.log( FINE, format( "Scanning JAR-File: %s", file.getAbsolutePath() ) );
365 }
366
367 JarFile jarFile = new JarFile( file );
368 _visitJar( jarFile );
369 }
370
371 private void _visitJar( JarFile jarFile )
372 throws IOException
373 {
374 Enumeration<JarEntry> jarEntries = jarFile.entries();
375 for ( JarEntry jarEntry = null; jarEntries.hasMoreElements(); )
376 {
377 jarEntry = jarEntries.nextElement();
378 String name = jarEntry.getName();
379
380 if ( !jarEntry.isDirectory() && matches( name ) )
381 {
382 if ( !visited.contains( name ) )
383 {
384 visitClass( jarFile.getInputStream( jarEntry ) );
385 visited.add( name );
386 }
387 }
388 }
389 }
390
391 private void visitClass( InputStream in )
392 throws IOException
393 {
394 ClassReader reader = new ClassReader( new BufferedInputStream( in ) );
395 try
396 {
397 AnnotationCollector collector = collectors.take();
398 reader.accept( collector, ASM_FLAGS );
399 collectors.put( collector );
400 }
401 catch ( InterruptedException e )
402 {
403
404 }
405 }
406
407 private boolean matches( String name )
408 {
409 boolean returned = false;
410 try
411 {
412 for ( Pattern pattern : patterns )
413 {
414 if ( pattern.matcher( name ).matches() )
415 {
416 return ( returned = true );
417 }
418 }
419 return returned;
420 }
421 finally
422 {
423 if ( _logger.isLoggable( Level.FINE ) )
424 {
425 _logger.log( FINE, format( "%s.matches(..) - \"%s\" -> %s",
426 getClass().getSimpleName(), name, returned ) );
427 }
428 }
429 }
430
431 }