View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.annotations;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.URI;
25  import java.net.URL;
26  import java.net.URLClassLoader;
27  import java.util.Arrays;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Set;
31  import java.util.jar.JarEntry;
32  import java.util.jar.JarInputStream;
33  
34  import org.eclipse.jetty.util.ConcurrentHashSet;
35  import org.eclipse.jetty.util.Loader;
36  import org.eclipse.jetty.util.MultiException;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  import org.eclipse.jetty.util.resource.Resource;
40  import org.eclipse.jetty.webapp.JarScanner;
41  import org.objectweb.asm.AnnotationVisitor;
42  import org.objectweb.asm.ClassReader;
43  import org.objectweb.asm.ClassVisitor;
44  import org.objectweb.asm.FieldVisitor;
45  import org.objectweb.asm.MethodVisitor;
46  import org.objectweb.asm.Opcodes;
47  
48  
49  /**
50   * AnnotationParser
51   *
52   * Use asm to scan classes for annotations. A SAX-style parsing is done.
53   * Handlers are registered which will be called back when various types of
54   * entity are encountered, eg a class, a method, a field. 
55   * 
56   * Handlers are not called back in any particular order and are assumed
57   * to be order-independent.
58   * 
59   * As a registered Handler will be called back for each annotation discovered
60   * on a class, a method, a field, the Handler should test to see if the annotation
61   * is one that it is interested in.
62   * 
63   * For the servlet spec, we are only interested in annotations on classes, methods and fields,
64   * so the callbacks for handling finding a class, a method a field are themselves
65   * not fully implemented.
66   */
67  public class AnnotationParser
68  {
69      private static final Logger LOG = Log.getLogger(AnnotationParser.class);
70  
71      protected Set<String> _parsedClassNames = new ConcurrentHashSet<String>();
72     
73  
74      /**
75       * Convert internal name to simple name
76       * 
77       * @param name
78       * @return
79       */
80      public static String normalize (String name)
81      {
82          if (name==null)
83              return null;
84  
85          if (name.startsWith("L") && name.endsWith(";"))
86              name = name.substring(1, name.length()-1);
87  
88          if (name.endsWith(".class"))
89              name = name.substring(0, name.length()-".class".length());
90  
91          return name.replace('/', '.');
92      }
93      
94      /**
95       * Convert internal names to simple names.
96       * 
97       * @param list
98       * @return
99       */
100     public static String[] normalize (String[] list)
101     {
102         if (list == null)
103             return null;       
104         String[] normalList = new String[list.length];
105         int i=0;
106         for (String s : list)
107             normalList[i++] = normalize(s);
108         return normalList;
109     }
110 
111     
112     /**
113      * ClassInfo
114      * 
115      * Immutable information gathered by parsing class header.
116      * 
117      */
118     public class ClassInfo 
119     {
120         final Resource _containingResource;
121         final String _className;
122         final int _version;
123         final int _access;
124         final String _signature;
125         final String _superName; 
126         final String[] _interfaces;
127         
128         public ClassInfo(Resource resource, String className, int version, int access, String signature, String superName, String[] interfaces)
129         {
130             super();
131             _containingResource = resource;
132             _className = className;
133             _version = version;
134             _access = access;
135             _signature = signature;
136             _superName = superName;
137             _interfaces = interfaces;
138         }
139 
140         public String getClassName()
141         {
142             return _className;
143         }
144 
145         public int getVersion()
146         {
147             return _version;
148         }
149 
150         public int getAccess()
151         {
152             return _access;
153         }
154 
155         public String getSignature()
156         {
157             return _signature;
158         }
159 
160         public String getSuperName()
161         {
162             return _superName;
163         }
164 
165         public String[] getInterfaces()
166         {
167             return _interfaces;
168         }
169 
170         public Resource getContainingResource()
171         {
172             return _containingResource;
173         }
174     }
175 
176     
177     /**
178      * MethodInfo
179      * 
180      * Immutable information gathered by parsing a method on a class.
181      */
182     public class MethodInfo
183     {
184         final ClassInfo _classInfo;
185         final String _methodName; 
186         final int _access;
187         final String _desc; 
188         final String _signature;
189         final String[] _exceptions;
190         
191         public MethodInfo(ClassInfo classInfo, String methodName, int access, String desc, String signature, String[] exceptions)
192         {
193             super();
194             _classInfo = classInfo;
195             _methodName = methodName;
196             _access = access;
197             _desc = desc;
198             _signature = signature;
199             _exceptions = exceptions;
200         }
201 
202         public ClassInfo getClassInfo()
203         {
204             return _classInfo;
205         }
206 
207         public String getMethodName()
208         {
209             return _methodName;
210         }
211 
212         public int getAccess()
213         {
214             return _access;
215         }
216 
217         public String getDesc()
218         {
219             return _desc;
220         }
221 
222         public String getSignature()
223         {
224             return _signature;
225         }
226 
227         public String[] getExceptions()
228         {
229             return _exceptions;
230         }
231     }
232     
233     
234     
235     /**
236      * FieldInfo
237      *
238      * Immutable information gathered by parsing a field on a class.
239      * 
240      */
241     public class FieldInfo
242     {
243         final ClassInfo _classInfo;
244         final String _fieldName;
245         final int _access;
246         final String _fieldType;
247         final String _signature;
248         final Object _value;
249         
250         public FieldInfo(ClassInfo classInfo, String fieldName, int access, String fieldType, String signature, Object value)
251         {
252             super();
253             _classInfo = classInfo;
254             _fieldName = fieldName;
255             _access = access;
256             _fieldType = fieldType;
257             _signature = signature;
258             _value = value;
259         }
260 
261         public ClassInfo getClassInfo()
262         {
263             return _classInfo;
264         }
265 
266         public String getFieldName()
267         {
268             return _fieldName;
269         }
270 
271         public int getAccess()
272         {
273             return _access;
274         }
275 
276         public String getFieldType()
277         {
278             return _fieldType;
279         }
280 
281         public String getSignature()
282         {
283             return _signature;
284         }
285 
286         public Object getValue()
287         {
288             return _value;
289         }
290     }
291     
292     
293     /**
294      * Handler
295      *
296      * Signature for all handlers that respond to parsing class files.
297      */
298     public static interface Handler
299     {
300         public void handle(ClassInfo classInfo);
301         public void handle(MethodInfo methodInfo);
302         public void handle (FieldInfo fieldInfo);
303         public void handle (ClassInfo info, String annotationName);
304         public void handle (MethodInfo info, String annotationName);
305         public void handle (FieldInfo info, String annotationName);
306     }
307     
308     
309     
310     /**
311      * AbstractHandler
312      *
313      * Convenience base class to provide no-ops for all Handler methods.
314      * 
315      */
316     public static abstract class AbstractHandler implements Handler
317     {
318 
319         @Override
320         public void handle(ClassInfo classInfo)
321         {
322            //no-op
323         }
324 
325         @Override
326         public void handle(MethodInfo methodInfo)
327         {
328             // no-op           
329         }
330 
331         @Override
332         public void handle(FieldInfo fieldInfo)
333         {
334             // no-op 
335         }
336 
337         @Override
338         public void handle(ClassInfo info, String annotationName)
339         {
340             // no-op 
341         }
342 
343         @Override
344         public void handle(MethodInfo info, String annotationName)
345         {
346             // no-op            
347         }
348 
349         @Override
350         public void handle(FieldInfo info, String annotationName)
351         {
352            // no-op
353         }        
354     }
355 
356     
357     
358     /**
359      * MyMethodVisitor
360      * 
361      * ASM Visitor for parsing a method. We are only interested in the annotations on methods.
362      */
363     public class MyMethodVisitor extends MethodVisitor
364     {
365         final MethodInfo _mi;
366         final Set<? extends Handler> _handlers;
367         
368         public MyMethodVisitor(final Set<? extends Handler> handlers,
369                                final ClassInfo classInfo,
370                                final int access,
371                                final String name,
372                                final String methodDesc,
373                                final String signature,
374                                final String[] exceptions)
375         {
376             super(Opcodes.ASM4);
377             _handlers = handlers;
378             _mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions);
379         }
380 
381         
382         /**
383          * We are only interested in finding the annotations on methods.
384          * 
385          * @see org.objectweb.asm.MethodVisitor#visitAnnotation(java.lang.String, boolean)
386          */
387         @Override
388         public AnnotationVisitor visitAnnotation(String desc, boolean visible)
389         {
390             String annotationName = normalize(desc);
391             for (Handler h:_handlers)
392                 h.handle(_mi, annotationName);
393             return null;
394         }
395     }
396 
397 
398     
399     /**
400      * MyFieldVisitor
401      * 
402      * An ASM visitor for parsing Fields. 
403      * We are only interested in visiting annotations on Fields.
404      *
405      */
406     public class MyFieldVisitor extends FieldVisitor
407     {   
408         final FieldInfo _fieldInfo;
409         final Set<? extends Handler> _handlers;
410         
411     
412         public MyFieldVisitor(final Set<? extends Handler> handlers,
413                               final ClassInfo classInfo,
414                               final int access,
415                               final String fieldName,
416                               final String fieldType,
417                               final String signature,
418                               final Object value)
419         {
420             super(Opcodes.ASM4);
421             _handlers = handlers;
422             _fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value);
423         }
424 
425 
426         /**
427          * Parse an annotation found on a Field.
428          * 
429          * @see org.objectweb.asm.FieldVisitor#visitAnnotation(java.lang.String, boolean)
430          */
431         @Override
432         public AnnotationVisitor visitAnnotation(String desc, boolean visible)
433         {
434             String annotationName = normalize(desc);
435             for (Handler h : _handlers)
436                h.handle(_fieldInfo, annotationName);
437 
438             return null;
439         }
440     }
441 
442   
443 
444 
445     /**
446      * MyClassVisitor
447      *
448      * ASM visitor for a class.
449      */
450     public class MyClassVisitor extends ClassVisitor
451     {
452 
453         final Resource _containingResource;
454         final Set<? extends Handler> _handlers;
455         ClassInfo _ci;
456         
457         public MyClassVisitor(Set<? extends Handler> handlers, Resource containingResource)
458         {
459             super(Opcodes.ASM4);
460             _handlers = handlers;
461             _containingResource = containingResource;
462         }
463 
464 
465         @Override
466         public void visit (final int version,
467                            final int access,
468                            final String name,
469                            final String signature,
470                            final String superName,
471                            final String[] interfaces)
472         {           
473             _ci = new ClassInfo(_containingResource, normalize(name), version, access, signature, normalize(superName), normalize(interfaces));
474             
475             _parsedClassNames.add(_ci.getClassName());                 
476 
477             for (Handler h:_handlers)
478                h.handle(_ci);
479         }
480         
481 
482         /**
483          * Visit an annotation on a Class
484          * 
485          * @see org.objectweb.asm.ClassVisitor#visitAnnotation(java.lang.String, boolean)
486          */
487         @Override
488         public AnnotationVisitor visitAnnotation (String desc, boolean visible)
489         {
490             String annotationName = normalize(desc);
491             for (Handler h : _handlers)
492                 h.handle(_ci, annotationName);
493 
494             return null;
495         }
496 
497 
498         /**
499          * Visit a method to extract its annotations
500          * 
501          * @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
502          */
503         @Override
504         public MethodVisitor visitMethod (final int access,
505                                           final String name,
506                                           final String methodDesc,
507                                           final String signature,
508                                           final String[] exceptions)
509         {
510 
511             return new MyMethodVisitor(_handlers, _ci, access, name, methodDesc, signature, exceptions);
512         }
513 
514         /**
515          * Visit a field to extract its annotations
516          * 
517          * @see org.objectweb.asm.ClassVisitor#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object)
518          */
519         @Override
520         public FieldVisitor visitField (final int access,
521                                         final String fieldName,
522                                         final String fieldType,
523                                         final String signature,
524                                         final Object value)
525         {
526             return new MyFieldVisitor(_handlers, _ci, access, fieldName, fieldType, signature, value);
527         }
528     }
529 
530  
531 
532     /**
533      * True if the class has already been processed, false otherwise
534      * @param className
535      */
536     public boolean isParsed (String className)
537     {
538         return _parsedClassNames.contains(className);
539     }
540 
541     
542     
543     /**
544      * Parse a given class
545      * 
546      * @param className
547      * @param resolver
548      * @throws Exception
549      */
550     public void parse (Set<? extends Handler> handlers, String className, ClassNameResolver resolver)
551     throws Exception
552     {
553         if (className == null)
554             return;
555 
556         if (!resolver.isExcluded(className))
557         {
558             if (!isParsed(className) || resolver.shouldOverride(className))
559             {
560                 className = className.replace('.', '/')+".class";
561                 URL resource = Loader.getResource(this.getClass(), className);
562                 if (resource!= null)
563                 {
564                     Resource r = Resource.newResource(resource);
565                     scanClass(handlers, null, r.getInputStream());
566                 }
567             }
568         }
569     }
570 
571     
572     
573     /**
574      * Parse the given class, optionally walking its inheritance hierarchy
575      * 
576      * @param clazz
577      * @param resolver
578      * @param visitSuperClasses
579      * @throws Exception
580      */
581     public void parse (Set<? extends Handler> handlers, Class<?> clazz, ClassNameResolver resolver, boolean visitSuperClasses)
582     throws Exception
583     {
584         Class<?> cz = clazz;
585         while (cz != null)
586         {
587             if (!resolver.isExcluded(cz.getName()))
588             {
589                 if (!isParsed(cz.getName()) || resolver.shouldOverride(cz.getName()))
590                 {
591                     String nameAsResource = cz.getName().replace('.', '/')+".class";
592                     URL resource = Loader.getResource(this.getClass(), nameAsResource);
593                     if (resource!= null)
594                     {
595                         Resource r = Resource.newResource(resource);
596                         scanClass(handlers, null, r.getInputStream());
597                     }
598                 }
599             }
600 
601             if (visitSuperClasses)
602                 cz = cz.getSuperclass();
603             else
604                 cz = null;
605         }
606     }
607 
608     
609     
610     /**
611      * Parse the given classes
612      * 
613      * @param classNames
614      * @param resolver
615      * @throws Exception
616      */
617     public void parse (Set<? extends Handler> handlers, String[] classNames, ClassNameResolver resolver)
618     throws Exception
619     {
620         if (classNames == null)
621             return;
622 
623         parse(handlers, Arrays.asList(classNames), resolver);
624     }
625 
626     
627     /**
628      * Parse the given classes
629      * 
630      * @param classNames
631      * @param resolver
632      * @throws Exception
633      */
634     public void parse (Set<? extends Handler> handlers, List<String> classNames, ClassNameResolver resolver)
635     throws Exception
636     {
637         MultiException me = new MultiException();
638         
639         for (String s:classNames)
640         {
641             try
642             {
643                 if ((resolver == null) || (!resolver.isExcluded(s) &&  (!isParsed(s) || resolver.shouldOverride(s))))
644                 {
645                     s = s.replace('.', '/')+".class";
646                     URL resource = Loader.getResource(this.getClass(), s);
647                     if (resource!= null)
648                     {
649                         Resource r = Resource.newResource(resource);
650                         scanClass(handlers, null, r.getInputStream());
651                     }
652                 }
653             }
654             catch (Exception e)
655             {
656                 me.add(new RuntimeException("Error scanning class "+s, e));
657             }
658         }
659         me.ifExceptionThrow();
660     }
661 
662     
663     /**
664      * Parse all classes in a directory
665      * 
666      * @param dir
667      * @param resolver
668      * @throws Exception
669      */
670     protected void parseDir (Set<? extends Handler> handlers, Resource dir, ClassNameResolver resolver)
671     throws Exception
672     {
673         //skip dirs whose name start with . (ie hidden)
674         if (!dir.isDirectory() || !dir.exists() || dir.getName().startsWith("."))
675             return;
676 
677         if (LOG.isDebugEnabled()) {LOG.debug("Scanning dir {}", dir);};
678 
679         MultiException me = new MultiException();
680         
681         String[] files=dir.list();
682         for (int f=0;files!=null && f<files.length;f++)
683         {
684             Resource res = dir.addPath(files[f]);
685             if (res.isDirectory())
686                 parseDir(handlers, res, resolver);
687             else
688             {
689                 //we've already verified the directories, so just verify the class file name
690                 File file = res.getFile();
691                 if (isValidClassFileName((file==null?null:file.getName())))
692                 {
693                     try
694                     {
695                         String name = res.getName();
696                         if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
697                         {
698                             Resource r = Resource.newResource(res.getURL());
699                             if (LOG.isDebugEnabled()) {LOG.debug("Scanning class {}", r);};
700                             scanClass(handlers, dir, r.getInputStream());
701                         }
702                     }                  
703                     catch (Exception ex)
704                     {
705                         me.add(new RuntimeException("Error scanning file "+files[f],ex));
706                     }
707                 }
708                 else
709                 {
710                    if (LOG.isDebugEnabled()) LOG.debug("Skipping scan on invalid file {}", res);
711                 }
712             }
713         }
714 
715         me.ifExceptionThrow();
716     }
717 
718 
719     /**
720      * Parse classes in the supplied classloader. 
721      * Only class files in jar files will be scanned.
722      * 
723      * @param loader
724      * @param visitParents
725      * @param nullInclusive
726      * @param resolver
727      * @throws Exception
728      */
729     public void parse (final Set<? extends Handler> handlers, ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
730     throws Exception
731     {
732         if (loader==null)
733             return;
734 
735         if (!(loader instanceof URLClassLoader))
736             return; //can't extract classes?
737 
738         final MultiException me = new MultiException();
739         
740         JarScanner scanner = new JarScanner()
741         {
742             @Override
743             public void processEntry(URI jarUri, JarEntry entry)
744             {
745                 try
746                 {
747                     parseJarEntry(handlers, Resource.newResource(jarUri), entry, resolver);
748                 }
749                 catch (Exception e)
750                 {
751                     me.add(new RuntimeException("Error parsing entry "+entry.getName()+" from jar "+ jarUri, e));
752                 }
753             }
754 
755         };
756 
757         scanner.scan(null, loader, nullInclusive, visitParents);
758         me.ifExceptionThrow();
759     }
760 
761 
762     /**
763      * Parse classes in the supplied uris.
764      * 
765      * @param uris
766      * @param resolver
767      * @throws Exception
768      */
769     public void parse (final Set<? extends Handler> handlers, final URI[] uris, final ClassNameResolver resolver)
770     throws Exception
771     {
772         if (uris==null)
773             return;
774 
775         MultiException me = new MultiException();
776         
777         for (URI uri:uris)
778         {
779             try
780             {
781                 parse(handlers, uri, resolver);
782             }
783             catch (Exception e)
784             {
785                 me.add(new RuntimeException("Problem parsing classes from "+ uri, e));
786             }
787         }
788         me.ifExceptionThrow();
789     }
790 
791     /**
792      * Parse a particular uri
793      * @param uri
794      * @param resolver
795      * @throws Exception
796      */
797     public void parse (final Set<? extends Handler> handlers, URI uri, final ClassNameResolver resolver)
798     throws Exception
799     {
800         if (uri == null)
801             return;
802 
803         parse (handlers, Resource.newResource(uri), resolver);
804     }
805 
806     
807     /**
808      * Parse a resource
809      * @param r
810      * @param resolver
811      * @throws Exception
812      */
813     public void parse (final Set<? extends Handler> handlers, Resource r, final ClassNameResolver resolver)
814     throws Exception
815     {
816         if (r == null)
817             return;
818         
819         if (r.exists() && r.isDirectory())
820         {
821             parseDir(handlers, r, resolver);
822             return;
823         }
824 
825         String fullname = r.toString();
826         if (fullname.endsWith(".jar"))
827         {
828             parseJar(handlers, r, resolver);
829             return;
830         }
831 
832         if (fullname.endsWith(".class"))
833         {
834             scanClass(handlers, null, r.getInputStream());
835             return;
836         }
837         
838         if (LOG.isDebugEnabled()) LOG.warn("Resource not scannable for classes: {}", r);
839     }
840 
841     
842   
843 
844     /**
845      * Parse a resource that is a jar file.
846      * 
847      * @param jarResource
848      * @param resolver
849      * @throws Exception
850      */
851     protected void parseJar (Set<? extends Handler> handlers, Resource jarResource,  final ClassNameResolver resolver)
852     throws Exception
853     {
854         if (jarResource == null)
855             return;
856        
857         if (jarResource.toString().endsWith(".jar"))
858         {
859             if (LOG.isDebugEnabled()) {LOG.debug("Scanning jar {}", jarResource);};
860 
861             //treat it as a jar that we need to open and scan all entries from  
862             //TODO alternative impl
863             /*
864             long start = System.nanoTime();
865             Collection<Resource> resources = Resource.newResource("jar:"+jarResource+"!/").getAllResources();
866             System.err.println(jarResource+String.valueOf(resources.size())+" resources listed in "+ ((TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS))));
867             for (Resource r:resources)
868             {
869                 //skip directories
870                 if (r.isDirectory())
871                     continue;
872 
873                 String name = r.getName();
874                 name = name.substring(name.indexOf("!/")+2);
875 
876                 //check file is a valid class file name
877                 if (isValidClassFileName(name) && isValidClassFilePath(name))
878                 {
879                     String shortName =  name.replace('/', '.').substring(0,name.length()-6);
880 
881                     if ((resolver == null)
882                             ||
883                         (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
884                     {
885                         if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", r);};
886                         scanClass(handlers, jarResource, r.getInputStream());
887                     }
888                 }
889             }
890             */
891 
892            InputStream in = jarResource.getInputStream();
893             if (in==null)
894                 return;
895 
896             MultiException me = new MultiException();
897             
898             JarInputStream jar_in = new JarInputStream(in);
899             try
900             { 
901                 JarEntry entry = jar_in.getNextJarEntry();
902                 while (entry!=null)
903                 {      
904                     try
905                     {
906                         parseJarEntry(handlers, jarResource, entry, resolver);                        
907                     }
908                     catch (Exception e)
909                     {
910                         me.add(new RuntimeException("Error scanning entry "+entry.getName()+" from jar "+jarResource, e));
911                     }
912                     entry = jar_in.getNextJarEntry();
913                 }
914             }
915             finally
916             {
917                 jar_in.close();
918             }
919             me.ifExceptionThrow();
920         }        
921     }
922 
923     /**
924      * Parse a single entry in a jar file
925      * @param jar
926      * @param entry
927      * @param resolver
928      * @throws Exception
929      */
930     protected void parseJarEntry (Set<? extends Handler> handlers, Resource jar, JarEntry entry, final ClassNameResolver resolver)
931     throws Exception
932     {
933         if (jar == null || entry == null)
934             return;
935 
936         //skip directories
937         if (entry.isDirectory())
938             return;
939 
940         String name = entry.getName();
941 
942         //check file is a valid class file name
943         if (isValidClassFileName(name) && isValidClassFilePath(name))
944         {
945             String shortName =  name.replace('/', '.').substring(0,name.length()-6);
946 
947             if ((resolver == null)
948                     ||
949                 (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
950             {
951                 Resource clazz = Resource.newResource("jar:"+jar.getURI()+"!/"+name);
952                 if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
953                 scanClass(handlers, jar, clazz.getInputStream());
954             }
955         }
956     }
957     
958     
959 
960     /**
961      * Use ASM on a class
962      * 
963      * @param containingResource the dir or jar that the class is contained within, can be null if not known
964      * @param is
965      * @throws IOException
966      */
967     protected void scanClass (Set<? extends Handler> handlers, Resource containingResource, InputStream is)
968     throws IOException
969     {
970         ClassReader reader = new ClassReader(is);
971         reader.accept(new MyClassVisitor(handlers, containingResource), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
972     }
973     
974     /**
975      * Check that the given path represents a valid class file name.
976      * The check is fairly cursory, checking that:
977      * <ul>
978      * <li> the name ends with .class</li>
979      * <li> it isn't a dot file or in a hidden directory </li>
980      * <li> the name of the class at least begins with a valid identifier for a class name </li>
981      * </ul>
982      * @param name
983      * @return
984      */
985     private boolean isValidClassFileName (String name)
986     {
987         //no name cannot be valid
988         if (name == null || name.length()==0)
989             return false;
990 
991         //skip anything that is not a class file
992         if (!name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
993         {
994             if (LOG.isDebugEnabled()) LOG.debug("Not a class: {}",name);
995             return false;
996         }
997 
998         //skip any classfiles that are not a valid java identifier
999         int c0 = 0;      
1000         int ldir = name.lastIndexOf('/', name.length()-6);
1001         c0 = (ldir > -1 ? ldir+1 : c0);
1002         if (!Character.isJavaIdentifierStart(name.charAt(c0)))
1003         {
1004             if (LOG.isDebugEnabled()) LOG.debug("Not a java identifier: {}"+name);
1005             return false;
1006         }
1007    
1008         return true;
1009     }
1010     
1011     
1012     /**
1013      * Check that the given path does not contain hidden directories
1014      *
1015      * @param path
1016      * @return
1017      */
1018     private boolean isValidClassFilePath (String path)
1019     {
1020         //no path is not valid
1021         if (path == null || path.length()==0)
1022             return false;
1023 
1024         //skip any classfiles that are in a hidden directory
1025         if (path.startsWith(".") || path.contains("/."))
1026         {
1027             if (LOG.isDebugEnabled()) LOG.debug("Contains hidden dirs: {}"+path);
1028             return false;
1029         }
1030 
1031         return true;
1032     }
1033 }
1034