View Javadoc

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