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