View Javadoc

1   package org.apache.onami.autobind.scanner.asm;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *  http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
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   * This Implementation only uses the ASM-API to read all recognized classes. It
70   * doesn't depend on any further 3rd-Party libraries.
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                 // ignore
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             // ignore
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         // TODO Could use Predicate of Google
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                             // ignore
232                             _logger.log( WARNING, format( "Using invalid URL for Classpath Scanning: %s", url ), e );
233                             return;
234                         }
235                         catch ( Throwable e )
236                         {
237                             // ignore
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             // ignore
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 }