001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020
021 package org.apache.xbean.finder;
022
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.lang.annotation.Annotation;
026 import java.lang.reflect.AnnotatedElement;
027 import java.lang.reflect.Constructor;
028 import java.lang.reflect.Field;
029 import java.lang.reflect.Method;
030 import java.net.URL;
031 import java.util.ArrayList;
032 import java.util.Collections;
033 import java.util.HashMap;
034 import java.util.List;
035 import java.util.Map;
036
037 import org.objectweb.asm.AnnotationVisitor;
038 import org.objectweb.asm.ClassReader;
039 import org.objectweb.asm.FieldVisitor;
040 import org.objectweb.asm.MethodVisitor;
041 import org.objectweb.asm.commons.EmptyVisitor;
042 import org.objectweb.asm.signature.SignatureReader;
043 import org.objectweb.asm.signature.SignatureVisitor;
044
045 /**
046 * @version $Rev: 924423 $ $Date: 2010-03-17 15:06:14 -0400 (Wed, 17 Mar 2010) $
047 */
048 public abstract class AbstractFinder {
049 private final Map<String, List<Info>> annotated = new HashMap<String, List<Info>>();
050 protected final List<ClassInfo> classInfos = new ArrayList<ClassInfo>();
051 private final List<String> classesNotLoaded = new ArrayList<String>();
052 private final int ASM_FLAGS = ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES;
053
054 protected abstract URL getResource(String className);
055
056 protected abstract Class<?> loadClass(String fixedName) throws ClassNotFoundException;
057
058 public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
059 List<Info> infos = annotated.get(annotation.getName());
060 return infos != null && !infos.isEmpty();
061 }
062
063 /**
064 * Returns a list of classes that could not be loaded in last invoked findAnnotated* method.
065 * <p/>
066 * The list will only contain entries of classes whose byte code matched the requirements
067 * of last invoked find* method, but were unable to be loaded and included in the results.
068 * <p/>
069 * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the
070 * results from the last findAnnotated* method call.
071 * <p/>
072 * This method is not thread safe.
073 * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call.
074 */
075 public List<String> getClassesNotLoaded() {
076 return Collections.unmodifiableList(classesNotLoaded);
077 }
078
079 public List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation) {
080 classesNotLoaded.clear();
081 List<Package> packages = new ArrayList<Package>();
082 List<Info> infos = getAnnotationInfos(annotation.getName());
083 for (Info info : infos) {
084 if (info instanceof PackageInfo) {
085 PackageInfo packageInfo = (PackageInfo) info;
086 try {
087 Package pkg = packageInfo.get();
088 // double check via proper reflection
089 if (pkg.isAnnotationPresent(annotation)) {
090 packages.add(pkg);
091 }
092 } catch (ClassNotFoundException e) {
093 classesNotLoaded.add(packageInfo.getName());
094 }
095 }
096 }
097 return packages;
098 }
099
100 public List<Class> findAnnotatedClasses(Class<? extends Annotation> annotation) {
101 classesNotLoaded.clear();
102 List<Class> classes = new ArrayList<Class>();
103 List<Info> infos = getAnnotationInfos(annotation.getName());
104 for (Info info : infos) {
105 if (info instanceof ClassInfo) {
106 ClassInfo classInfo = (ClassInfo) info;
107 try {
108 Class clazz = classInfo.get();
109 // double check via proper reflection
110 if (clazz.isAnnotationPresent(annotation)) {
111 classes.add(clazz);
112 }
113 } catch (ClassNotFoundException e) {
114 classesNotLoaded.add(classInfo.getName());
115 }
116 }
117 }
118 return classes;
119 }
120
121 /**
122 * Naive implementation - works extremelly slow O(n^3)
123 *
124 * @param annotation
125 * @return list of directly or indirectly (inherited) annotated classes
126 */
127 public List<Class> findInheritedAnnotatedClasses(Class<? extends Annotation> annotation) {
128 classesNotLoaded.clear();
129 List<Class> classes = new ArrayList<Class>();
130 List<Info> infos = getAnnotationInfos(annotation.getName());
131 for (Info info : infos) {
132 try {
133 if(info instanceof ClassInfo){
134 classes.add(((ClassInfo) info).get());
135 }
136 } catch (ClassNotFoundException cnfe) {
137 // TODO: ignored, but a log message would be appropriate
138 }
139 }
140 boolean annClassFound;
141 List<ClassInfo> tempClassInfos = new ArrayList<ClassInfo>(classInfos);
142 do {
143 annClassFound = false;
144 for (int pos = 0; pos < tempClassInfos.size(); pos++) {
145 ClassInfo classInfo = tempClassInfos.get(pos);
146 try {
147 // check whether any superclass is annotated
148 String superType = classInfo.getSuperType();
149 for (Class clazz : classes) {
150 if (superType.equals(clazz.getName())) {
151 classes.add(classInfo.get());
152 tempClassInfos.remove(pos);
153 annClassFound = true;
154 break;
155 }
156 }
157 // check whether any interface is annotated
158 List<String> interfces = classInfo.getInterfaces();
159 for (String interfce: interfces) {
160 for (Class clazz : classes) {
161 if (interfce.replaceFirst("<.*>","").equals(clazz.getName())) {
162 classes.add(classInfo.get());
163 tempClassInfos.remove(pos);
164 annClassFound = true;
165 break;
166 }
167 }
168 }
169 } catch (ClassNotFoundException e) {
170 classesNotLoaded.add(classInfo.getName());
171 } catch (NoClassDefFoundError e) {
172 classesNotLoaded.add(classInfo.getName());
173 }
174 }
175 } while (annClassFound);
176 return classes;
177 }
178
179 public List<Method> findAnnotatedMethods(Class<? extends Annotation> annotation) {
180 classesNotLoaded.clear();
181 List<ClassInfo> seen = new ArrayList<ClassInfo>();
182 List<Method> methods = new ArrayList<Method>();
183 List<Info> infos = getAnnotationInfos(annotation.getName());
184 for (Info info : infos) {
185 if (info instanceof MethodInfo && !info.getName().equals("<init>")) {
186 MethodInfo methodInfo = (MethodInfo) info;
187 ClassInfo classInfo = methodInfo.getDeclaringClass();
188
189 if (seen.contains(classInfo)) continue;
190
191 seen.add(classInfo);
192
193 try {
194 Class clazz = classInfo.get();
195 for (Method method : clazz.getDeclaredMethods()) {
196 if (method.isAnnotationPresent(annotation)) {
197 methods.add(method);
198 }
199 }
200 } catch (ClassNotFoundException e) {
201 classesNotLoaded.add(classInfo.getName());
202 }
203 }
204 }
205 return methods;
206 }
207
208 public List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation) {
209 classesNotLoaded.clear();
210 List<ClassInfo> seen = new ArrayList<ClassInfo>();
211 List<Constructor> constructors = new ArrayList<Constructor>();
212 List<Info> infos = getAnnotationInfos(annotation.getName());
213 for (Info info : infos) {
214 if (info instanceof MethodInfo && info.getName().equals("<init>")) {
215 MethodInfo methodInfo = (MethodInfo) info;
216 ClassInfo classInfo = methodInfo.getDeclaringClass();
217
218 if (seen.contains(classInfo)) continue;
219
220 seen.add(classInfo);
221
222 try {
223 Class clazz = classInfo.get();
224 for (Constructor constructor : clazz.getConstructors()) {
225 if (constructor.isAnnotationPresent(annotation)) {
226 constructors.add(constructor);
227 }
228 }
229 } catch (ClassNotFoundException e) {
230 classesNotLoaded.add(classInfo.getName());
231 }
232 }
233 }
234 return constructors;
235 }
236
237 public List<Field> findAnnotatedFields(Class<? extends Annotation> annotation) {
238 classesNotLoaded.clear();
239 List<ClassInfo> seen = new ArrayList<ClassInfo>();
240 List<Field> fields = new ArrayList<Field>();
241 List<Info> infos = getAnnotationInfos(annotation.getName());
242 for (Info info : infos) {
243 if (info instanceof FieldInfo) {
244 FieldInfo fieldInfo = (FieldInfo) info;
245 ClassInfo classInfo = fieldInfo.getDeclaringClass();
246
247 if (seen.contains(classInfo)) continue;
248
249 seen.add(classInfo);
250
251 try {
252 Class clazz = classInfo.get();
253 for (Field field : clazz.getDeclaredFields()) {
254 if (field.isAnnotationPresent(annotation)) {
255 fields.add(field);
256 }
257 }
258 } catch (ClassNotFoundException e) {
259 classesNotLoaded.add(classInfo.getName());
260 }
261 }
262 }
263 return fields;
264 }
265
266 public List<Class> findClassesInPackage(String packageName, boolean recursive) {
267 classesNotLoaded.clear();
268 List<Class> classes = new ArrayList<Class>();
269 for (ClassInfo classInfo : classInfos) {
270 try {
271 if (recursive && classInfo.getPackageName().startsWith(packageName)){
272 classes.add(classInfo.get());
273 } else if (classInfo.getPackageName().equals(packageName)){
274 classes.add(classInfo.get());
275 }
276 } catch (ClassNotFoundException e) {
277 classesNotLoaded.add(classInfo.getName());
278 }
279 }
280 return classes;
281 }
282
283 protected List<Info> getAnnotationInfos(String name) {
284 List<Info> infos = annotated.get(name);
285 if (infos == null) {
286 infos = new ArrayList<Info>();
287 annotated.put(name, infos);
288 }
289 return infos;
290 }
291
292 protected void readClassDef(InputStream in) throws IOException {
293 ClassReader classReader = new ClassReader(in);
294 classReader.accept(new InfoBuildingVisitor(), ASM_FLAGS);
295 }
296
297 public class Annotatable {
298 private final List<ClassFinder.AnnotationInfo> annotations = new ArrayList<ClassFinder.AnnotationInfo>();
299
300 public Annotatable(AnnotatedElement element) {
301 for (Annotation annotation : element.getAnnotations()) {
302 annotations.add(new ClassFinder.AnnotationInfo(annotation.annotationType().getName()));
303 }
304 }
305
306 public Annotatable() {
307 }
308
309 public List<ClassFinder.AnnotationInfo> getAnnotations() {
310 return annotations;
311 }
312
313 }
314
315 public static interface Info {
316 String getName();
317
318 List<ClassFinder.AnnotationInfo> getAnnotations();
319 }
320
321 public class PackageInfo extends Annotatable implements Info {
322 private final String name;
323 private final ClassFinder.ClassInfo info;
324 private final Package pkg;
325
326 public PackageInfo(Package pkg){
327 super(pkg);
328 this.pkg = pkg;
329 this.name = pkg.getName();
330 this.info = null;
331 }
332
333 public PackageInfo(String name) {
334 info = new ClassFinder.ClassInfo(name, null);
335 this.name = name;
336 this.pkg = null;
337 }
338
339 public String getName() {
340 return name;
341 }
342
343 public Package get() throws ClassNotFoundException {
344 return (pkg != null)?pkg:info.get().getPackage();
345 }
346 }
347
348 public class ClassInfo extends Annotatable implements Info {
349 private String name;
350 private final List<ClassFinder.MethodInfo> methods = new ArrayList<ClassFinder.MethodInfo>();
351 private final List<ClassFinder.MethodInfo> constructors = new ArrayList<ClassFinder.MethodInfo>();
352 private String superType;
353 private final List<String> interfaces = new ArrayList<String>();
354 private final List<ClassFinder.FieldInfo> fields = new ArrayList<ClassFinder.FieldInfo>();
355 private Class<?> clazz;
356 private ClassNotFoundException notFound;
357
358 public ClassInfo(Class clazz) {
359 super(clazz);
360 this.clazz = clazz;
361 this.name = clazz.getName();
362 Class superclass = clazz.getSuperclass();
363 this.superType = superclass != null ? superclass.getName(): null;
364 }
365
366 public ClassInfo(String name, String superType) {
367 this.name = name;
368 this.superType = superType;
369 }
370
371 public String getPackageName(){
372 return name.substring(0,name.lastIndexOf("."));
373 }
374
375 public List<ClassFinder.MethodInfo> getConstructors() {
376 return constructors;
377 }
378
379 public List<String> getInterfaces() {
380 return interfaces;
381 }
382
383 public List<ClassFinder.FieldInfo> getFields() {
384 return fields;
385 }
386
387 public List<ClassFinder.MethodInfo> getMethods() {
388 return methods;
389 }
390
391 public String getName() {
392 return name;
393 }
394
395 public String getSuperType() {
396 return superType;
397 }
398
399 public Class get() throws ClassNotFoundException {
400 if (clazz != null) return clazz;
401 if (notFound != null) throw notFound;
402 try {
403 String fixedName = name.replaceFirst("<.*>", "");
404 this.clazz = loadClass(fixedName);
405 return clazz;
406 } catch (ClassNotFoundException notFound) {
407 classesNotLoaded.add(name);
408 this.notFound = notFound;
409 throw notFound;
410 }
411 }
412
413
414 public String toString() {
415 return name;
416 }
417 }
418
419 public class MethodInfo extends Annotatable implements Info {
420 private final ClassInfo declaringClass;
421 private final String returnType;
422 private final String name;
423 private final List<List<ClassFinder.AnnotationInfo>> parameterAnnotations = new ArrayList<List<ClassFinder.AnnotationInfo>>();
424
425 public MethodInfo(ClassInfo info, Constructor constructor){
426 super(constructor);
427 this.declaringClass = info;
428 this.name = "<init>";
429 this.returnType = Void.TYPE.getName();
430 }
431
432 public MethodInfo(ClassInfo info, Method method){
433 super(method);
434 this.declaringClass = info;
435 this.name = method.getName();
436 this.returnType = method.getReturnType().getName();
437 }
438
439 public MethodInfo(ClassInfo declarignClass, String name, String returnType) {
440 this.declaringClass = declarignClass;
441 this.name = name;
442 this.returnType = returnType;
443 }
444
445 public List<List<ClassFinder.AnnotationInfo>> getParameterAnnotations() {
446 return parameterAnnotations;
447 }
448
449 public List<ClassFinder.AnnotationInfo> getParameterAnnotations(int index) {
450 if (index >= parameterAnnotations.size()) {
451 for (int i = parameterAnnotations.size(); i <= index; i++) {
452 List<ClassFinder.AnnotationInfo> annotationInfos = new ArrayList<ClassFinder.AnnotationInfo>();
453 parameterAnnotations.add(i, annotationInfos);
454 }
455 }
456 return parameterAnnotations.get(index);
457 }
458
459 public String getName() {
460 return name;
461 }
462
463 public ClassInfo getDeclaringClass() {
464 return declaringClass;
465 }
466
467 public String getReturnType() {
468 return returnType;
469 }
470
471 public String toString() {
472 return declaringClass + "@" + name;
473 }
474 }
475
476 public class FieldInfo extends Annotatable implements Info {
477 private final String name;
478 private final String type;
479 private final ClassInfo declaringClass;
480
481 public FieldInfo(ClassInfo info, Field field){
482 super(field);
483 this.declaringClass = info;
484 this.name = field.getName();
485 this.type = field.getType().getName();
486 }
487
488 public FieldInfo(ClassInfo declaringClass, String name, String type) {
489 this.declaringClass = declaringClass;
490 this.name = name;
491 this.type = type;
492 }
493
494 public String getName() {
495 return name;
496 }
497
498 public ClassInfo getDeclaringClass() {
499 return declaringClass;
500 }
501
502 public String getType() {
503 return type;
504 }
505
506 public String toString() {
507 return declaringClass + "#" + name;
508 }
509 }
510
511 public class AnnotationInfo extends Annotatable implements Info {
512 private final String name;
513
514 public AnnotationInfo(Annotation annotation){
515 this(annotation.getClass().getName());
516 }
517
518 public AnnotationInfo(Class<? extends Annotation> annotation) {
519 this.name = annotation.getName().intern();
520 }
521
522 public AnnotationInfo(String name) {
523 name = name.replaceAll("^L|;$", "");
524 name = name.replace('/', '.');
525 this.name = name.intern();
526 }
527
528 public String getName() {
529 return name;
530 }
531
532 public String toString() {
533 return name;
534 }
535 }
536
537 public class InfoBuildingVisitor extends EmptyVisitor {
538 private Info info;
539
540 public InfoBuildingVisitor() {
541 }
542
543 public InfoBuildingVisitor(Info info) {
544 this.info = info;
545 }
546
547 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
548 if (name.endsWith("package-info")) {
549 info = new PackageInfo(javaName(name));
550 } else {
551 ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName));
552
553 if (signature == null) {
554 for (String interfce : interfaces) {
555 classInfo.getInterfaces().add(javaName(interfce));
556 }
557 } else {
558 // the class uses generics
559 new SignatureReader(signature).accept(new ClassFinder.GenericAwareInfoBuildingVisitor(ClassFinder.GenericAwareInfoBuildingVisitor.TYPE.CLASS, classInfo));
560 }
561 info = classInfo;
562 classInfos.add(classInfo);
563 }
564 }
565
566 private String javaName(String name) {
567 return (name == null)? null:name.replace('/', '.');
568 }
569
570 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
571 AnnotationInfo annotationInfo = new AnnotationInfo(desc);
572 info.getAnnotations().add(annotationInfo);
573 getAnnotationInfos(annotationInfo.getName()).add(info);
574 return new ClassFinder.InfoBuildingVisitor(annotationInfo);
575 }
576
577 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
578 ClassInfo classInfo = ((ClassInfo) info);
579 FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc);
580 classInfo.getFields().add(fieldInfo);
581 return new ClassFinder.InfoBuildingVisitor(fieldInfo);
582 }
583
584 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
585 ClassInfo classInfo = ((ClassInfo) info);
586 MethodInfo methodInfo = new MethodInfo(classInfo, name, desc);
587 classInfo.getMethods().add(methodInfo);
588 return new ClassFinder.InfoBuildingVisitor(methodInfo);
589 }
590
591 public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) {
592 MethodInfo methodInfo = ((MethodInfo) info);
593 List<AnnotationInfo> annotationInfos = methodInfo.getParameterAnnotations(param);
594 AnnotationInfo annotationInfo = new AnnotationInfo(desc);
595 annotationInfos.add(annotationInfo);
596 return new ClassFinder.InfoBuildingVisitor(annotationInfo);
597 }
598 }
599
600 public static class GenericAwareInfoBuildingVisitor implements SignatureVisitor {
601
602 public enum TYPE {
603 CLASS
604 }
605
606 public enum STATE {
607 BEGIN, END, SUPERCLASS, INTERFACE, FORMAL_TYPE_PARAM
608 }
609
610 private Info info;
611 private ClassFinder.GenericAwareInfoBuildingVisitor.TYPE type;
612 private ClassFinder.GenericAwareInfoBuildingVisitor.STATE state;
613
614 private static boolean debug = false;
615
616 public GenericAwareInfoBuildingVisitor() {
617 }
618
619 public GenericAwareInfoBuildingVisitor(ClassFinder.GenericAwareInfoBuildingVisitor.TYPE type, Info info) {
620 this.type = type;
621 this.info = info;
622 this.state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.BEGIN;
623 }
624
625 public void visitFormalTypeParameter(String s) {
626 if (debug) System.out.println(" s=" + s);
627 switch (state) {
628 case BEGIN:
629 ((ClassInfo) info).name += "<" + s;
630 }
631 state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.FORMAL_TYPE_PARAM;
632 }
633
634 public SignatureVisitor visitClassBound() {
635 if (debug) System.out.println(" visitClassBound()");
636 return this;
637 }
638
639 public SignatureVisitor visitInterfaceBound() {
640 if (debug) System.out.println(" visitInterfaceBound()");
641 return this;
642 }
643
644 public SignatureVisitor visitSuperclass() {
645 if (debug) System.out.println(" visitSuperclass()");
646 state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.SUPERCLASS;
647 return this;
648 }
649
650 public SignatureVisitor visitInterface() {
651 if (debug) System.out.println(" visitInterface()");
652 ((ClassInfo) info).getInterfaces().add("");
653 state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.INTERFACE;
654 return this;
655 }
656
657 public SignatureVisitor visitParameterType() {
658 if (debug) System.out.println(" visitParameterType()");
659 return this;
660 }
661
662 public SignatureVisitor visitReturnType() {
663 if (debug) System.out.println(" visitReturnType()");
664 return this;
665 }
666
667 public SignatureVisitor visitExceptionType() {
668 if (debug) System.out.println(" visitExceptionType()");
669 return this;
670 }
671
672 public void visitBaseType(char c) {
673 if (debug) System.out.println(" visitBaseType(" + c + ")");
674 }
675
676 public void visitTypeVariable(String s) {
677 if (debug) System.out.println(" visitTypeVariable(" + s + ")");
678 }
679
680 public SignatureVisitor visitArrayType() {
681 if (debug) System.out.println(" visitArrayType()");
682 return this;
683 }
684
685 public void visitClassType(String s) {
686 if (debug) System.out.println(" visitClassType(" + s + ")");
687 switch (state) {
688 case INTERFACE:
689 List<String> interfces = ((ClassInfo) info).getInterfaces();
690 int idx = interfces.size() - 1;
691 String interfce = interfces.get(idx);
692 if (interfce.length() == 0) {
693 interfce = javaName(s);
694 } else {
695 interfce += javaName(s);
696 }
697 interfces.set(idx, interfce);
698 break;
699 case SUPERCLASS:
700 if (!s.equals("java/lang/Object")) {
701 ((ClassInfo) info).superType = javaName(s);
702 }
703 }
704 }
705
706 public void visitInnerClassType(String s) {
707 if (debug) System.out.println(" visitInnerClassType(" + s + ")");
708 }
709
710 public void visitTypeArgument() {
711 if (debug) System.out.println(" visitTypeArgument()");
712 switch (state) {
713 case INTERFACE:
714 List<String> interfces = ((ClassInfo) info).getInterfaces();
715 int idx = interfces.size() - 1;
716 String interfce = interfces.get(idx);
717 interfce += "<";
718 interfces.set(idx, interfce);
719 }
720 }
721
722 public SignatureVisitor visitTypeArgument(char c) {
723 if (debug) System.out.println(" visitTypeArgument(" + c + ")");
724 switch (state) {
725 case INTERFACE:
726 List<String> interfces = ((ClassInfo) info).getInterfaces();
727 int idx = interfces.size() - 1;
728 String interfce = interfces.get(idx);
729 interfce += "<";
730 interfces.set(idx, interfce);
731 }
732 return this;
733 }
734
735 public void visitEnd() {
736 if (debug) System.out.println(" visitEnd()");
737 switch (state) {
738 case INTERFACE:
739 List<String> interfces = ((ClassInfo) info).getInterfaces();
740 int idx = interfces.size() - 1;
741 String interfce = interfces.get(idx);
742 interfce += ">";
743 interfces.set(idx, interfce);
744 break;
745 case FORMAL_TYPE_PARAM:
746 String name = ((ClassInfo) info).name;
747 if (name.contains("<")) {
748 ((ClassInfo) info).name += ">";
749 }
750 }
751 state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.END;
752 }
753
754 private String javaName(String name) {
755 return (name == null)? null:name.replace('/', '.');
756 }
757
758 }
759 }