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