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 20:06:14 +0100 (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 }