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