1 28 29 package org.jruby.javasupport.proxy; 30 31 import java.lang.reflect.Constructor ; 32 import java.lang.reflect.Field ; 33 import java.lang.reflect.InvocationTargetException ; 34 import java.lang.reflect.Method ; 35 import java.lang.reflect.Modifier ; 36 import java.lang.reflect.UndeclaredThrowableException ; 37 import java.security.AccessController ; 38 import java.security.PrivilegedAction ; 39 import java.util.Arrays ; 40 import java.util.HashMap ; 41 import java.util.HashSet ; 42 import java.util.Iterator ; 43 import java.util.Map ; 44 import java.util.Set ; 45 46 import org.objectweb.asm.ClassVisitor; 47 import org.objectweb.asm.ClassWriter; 48 import org.objectweb.asm.FieldVisitor; 49 import org.objectweb.asm.Label; 50 import org.objectweb.asm.Opcodes; 51 import org.objectweb.asm.Type; 52 import org.objectweb.asm.commons.GeneratorAdapter; 53 54 public class JavaProxyClassFactory { 55 56 private static final Type JAVA_LANG_CLASS_TYPE = Type.getType(Class .class); 57 58 private static final Type[] EMPTY_TYPE_ARR = new Type[0]; 59 60 private static final org.objectweb.asm.commons.Method HELPER_GET_PROXY_CLASS_METHOD = org.objectweb.asm.commons.Method 61 .getMethod(JavaProxyClass.class.getName() 62 + " initProxyClass(java.lang.Class)"); 63 64 private static final org.objectweb.asm.commons.Method CLASS_FORNAME_METHOD = org.objectweb.asm.commons.Method 65 .getMethod("java.lang.Class forName(java.lang.String)"); 66 67 private static final String INVOCATION_HANDLER_FIELD_NAME = "__handler"; 68 69 private static final String PROXY_CLASS_FIELD_NAME = "__proxy_class"; 70 71 private static final Class [] EMPTY_CLASS_ARR = new Class [0]; 72 73 private static final Type INVOCATION_HANDLER_TYPE = Type 74 .getType(JavaProxyInvocationHandler.class); 75 76 private static final Type PROXY_METHOD_TYPE = Type 77 .getType(JavaProxyMethod.class); 78 79 private static final Type PROXY_CLASS_TYPE = Type 80 .getType(JavaProxyClass.class); 81 82 private static final org.objectweb.asm.commons.Method INVOCATION_HANDLER_INVOKE_METHOD = org.objectweb.asm.commons.Method 83 .getMethod("java.lang.Object invoke(java.lang.Object, " 84 + PROXY_METHOD_TYPE.getClassName() 85 + ", java.lang.Object[])"); 86 87 private static final Type PROXY_HELPER_TYPE = Type 88 .getType(InternalJavaProxyHelper.class); 89 90 private static final org.objectweb.asm.commons.Method PROXY_HELPER_GET_METHOD = org.objectweb.asm.commons.Method 91 .getMethod(PROXY_METHOD_TYPE.getClassName() + " initProxyMethod(" 92 + JavaProxyClass.class.getName() 93 + ",java.lang.String,java.lang.String,boolean)"); 94 95 private static final Type JAVA_PROXY_TYPE = Type 96 .getType(InternalJavaProxy.class); 97 98 private static int counter; 99 100 private static Map proxies = new HashMap (); 101 102 private static Method defineClass_method; 103 104 static JavaProxyClass newProxyClass(ClassLoader loader, 105 String targetClassName, Class superClass, Class [] interfaces) 106 throws InvocationTargetException { 107 if (loader == null) { 108 loader = JavaProxyClassFactory.class.getClassLoader(); 109 } 110 111 if (superClass == null) { 112 superClass = Object .class; 113 } 114 115 if (interfaces == null) { 116 interfaces = EMPTY_CLASS_ARR; 117 } 118 119 if (targetClassName == null) { 120 String pkg = packageName(superClass); 121 String fullName = superClass.getName(); 122 int ix = fullName.lastIndexOf('.'); 123 String cName = fullName; 124 if(ix != -1) { 125 cName = fullName.substring(ix+1); 126 } 127 if (pkg.startsWith("java.") || pkg.startsWith("javax.")) { 128 pkg = packageName(JavaProxyClassFactory.class) + ".gen"; 129 } 130 if(ix == -1) { 131 targetClassName = cName + "$Proxy" + counter++; 132 } else { 133 targetClassName = pkg + "." + cName + "$Proxy" 134 + counter++; 135 } 136 } 137 138 Set key = new HashSet (); 139 key.add(superClass); 140 for (int i = 0; i < interfaces.length; i++) { 141 key.add(interfaces[i]); 142 } 143 144 JavaProxyClass proxyClass = (JavaProxyClass) proxies.get(key); 145 if (proxyClass == null) { 146 147 validateArgs(targetClassName, superClass); 148 149 Map methods = new HashMap (); 150 collectMethods(superClass, interfaces, methods); 151 152 Type selfType = Type.getType("L" 153 + toInternalClassName(targetClassName) + ";"); 154 proxyClass = generate(loader, targetClassName, superClass, 155 interfaces, methods, selfType); 156 157 proxies.put(key, proxyClass); 158 } 159 160 return proxyClass; 161 } 162 163 private static JavaProxyClass generate(final ClassLoader loader, 164 final String targetClassName, final Class superClass, 165 final Class [] interfaces, final Map methods, final Type selfType) { 166 ClassWriter cw = beginProxyClass(targetClassName, superClass, 167 interfaces); 168 169 GeneratorAdapter clazzInit = createClassInitializer(selfType, cw); 170 171 generateConstructors(superClass, selfType, cw); 172 173 generateGetProxyClass(selfType, cw); 174 175 generateGetInvocationHandler(selfType, cw); 176 177 generateProxyMethods(superClass, methods, selfType, cw, clazzInit); 178 179 clazzInit.returnValue(); 181 clazzInit.endMethod(); 182 183 cw.visitEnd(); 185 186 byte[] data = cw.toByteArray(); 187 188 194 195 Class clazz = invokeDefineClass(loader, selfType.getClassName(), data); 196 197 try { 199 Field proxy_class = clazz.getDeclaredField(PROXY_CLASS_FIELD_NAME); 200 proxy_class.setAccessible(true); 201 return (JavaProxyClass) proxy_class.get(clazz); 202 } catch (Exception ex) { 203 InternalError ie = new InternalError (); 204 ie.initCause(ex); 205 throw ie; 206 } 207 } 208 209 static { 210 AccessController.doPrivileged(new PrivilegedAction () { 211 public Object run() { 212 try { 213 defineClass_method = ClassLoader .class.getDeclaredMethod( 214 "defineClass", new Class [] { String .class, 215 byte[].class, int.class, int.class }); 216 } catch (Exception e) { 217 e.printStackTrace(); 219 return null; 220 } 221 defineClass_method.setAccessible(true); 222 return null; 223 } 224 }); 225 } 226 227 private static Class invokeDefineClass(ClassLoader loader, 228 String className, byte[] data) { 229 try { 230 return (Class ) defineClass_method 231 .invoke(loader, new Object [] { className, data, 232 new Integer (0), new Integer (data.length) }); 233 } catch (IllegalArgumentException e) { 234 e.printStackTrace(); 236 return null; 237 } catch (IllegalAccessException e) { 238 e.printStackTrace(); 240 return null; 241 } catch (InvocationTargetException e) { 242 e.printStackTrace(); 244 return null; 245 } 246 } 247 248 private static ClassWriter beginProxyClass(final String targetClassName, 249 final Class superClass, final Class [] interfaces) { 250 251 ClassWriter cw = new ClassWriter(true); 252 253 int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC; 254 String name = toInternalClassName(targetClassName); 255 String signature = null; 256 String supername = toInternalClassName(superClass); 257 String [] interfaceNames = new String [interfaces.length + 1]; 258 for (int i = 0; i < interfaces.length; i++) { 259 interfaceNames[i] = toInternalClassName(interfaces[i]); 260 } 261 interfaceNames[interfaces.length] = toInternalClassName(InternalJavaProxy.class); 262 263 cw.visit(Opcodes.V1_3, access, name, signature, supername, 265 interfaceNames); 266 267 cw.visitField(Opcodes.ACC_PRIVATE, INVOCATION_HANDLER_FIELD_NAME, 268 INVOCATION_HANDLER_TYPE.getDescriptor(), null, null).visitEnd(); 269 270 cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, 271 PROXY_CLASS_FIELD_NAME, PROXY_CLASS_TYPE.getDescriptor(), null, 272 null).visitEnd(); 273 274 return cw; 275 } 276 277 private static void generateProxyMethods(Class superClass, Map methods, 278 Type selfType, ClassVisitor cw, GeneratorAdapter clazzInit) { 279 Iterator it = methods.values().iterator(); 280 while (it.hasNext()) { 281 MethodData md = (MethodData) it.next(); 282 Type superClassType = Type.getType(superClass); 283 generateProxyMethod(selfType, superClassType, cw, clazzInit, md); 284 } 285 } 286 287 private static void generateGetInvocationHandler(Type selfType, 288 ClassVisitor cw) { 289 GeneratorAdapter gh = new GeneratorAdapter(Opcodes.ACC_PUBLIC, 291 new org.objectweb.asm.commons.Method("___getInvocationHandler", 292 INVOCATION_HANDLER_TYPE, EMPTY_TYPE_ARR), null, 293 EMPTY_TYPE_ARR, cw); 294 295 gh.loadThis(); 296 gh.getField(selfType, INVOCATION_HANDLER_FIELD_NAME, 297 INVOCATION_HANDLER_TYPE); 298 gh.returnValue(); 299 gh.endMethod(); 300 } 301 302 private static void generateGetProxyClass(Type selfType, ClassVisitor cw) { 303 GeneratorAdapter gpc = new GeneratorAdapter(Opcodes.ACC_PUBLIC, 305 new org.objectweb.asm.commons.Method("___getProxyClass", 306 PROXY_CLASS_TYPE, EMPTY_TYPE_ARR), null, 307 EMPTY_TYPE_ARR, cw); 308 gpc.getStatic(selfType, PROXY_CLASS_FIELD_NAME, PROXY_CLASS_TYPE); 309 gpc.returnValue(); 310 gpc.endMethod(); 311 } 312 313 private static void generateConstructors(Class superClass, Type selfType, 314 ClassVisitor cw) { 315 Constructor [] cons = superClass.getConstructors(); 316 for (int i = 0; i < cons.length; i++) { 317 Constructor constructor = cons[i]; 318 319 int acc = constructor.getModifiers(); 320 if (Modifier.isProtected(acc) || Modifier.isPublic(acc)) { 321 } else if (!Modifier.isPrivate(acc) 323 && packageName(constructor.getDeclaringClass()).equals( 324 packageName(selfType.getClassName()))) { 325 } else { 327 continue; 329 } 330 331 generateConstructor(selfType, constructor, cw); 332 } 333 } 334 335 private static GeneratorAdapter createClassInitializer(Type selfType, 336 ClassVisitor cw) { 337 GeneratorAdapter clazzInit; 338 clazzInit = new GeneratorAdapter(Opcodes.ACC_PRIVATE 339 | Opcodes.ACC_STATIC, new org.objectweb.asm.commons.Method( 340 "<clinit>", Type.VOID_TYPE, EMPTY_TYPE_ARR), null, 341 EMPTY_TYPE_ARR, cw); 342 343 clazzInit.visitLdcInsn(selfType.getClassName()); 344 clazzInit.invokeStatic(JAVA_LANG_CLASS_TYPE, CLASS_FORNAME_METHOD); 345 clazzInit 346 .invokeStatic(PROXY_HELPER_TYPE, HELPER_GET_PROXY_CLASS_METHOD); 347 clazzInit.dup(); 348 clazzInit.putStatic(selfType, PROXY_CLASS_FIELD_NAME, PROXY_CLASS_TYPE); 349 return clazzInit; 350 } 351 352 private static void generateProxyMethod(Type selfType, Type superType, 353 ClassVisitor cw, GeneratorAdapter clazzInit, MethodData md) { 354 if (!md.generateProxyMethod()) { 355 return; 356 } 357 358 org.objectweb.asm.commons.Method m = md.getMethod(); 359 Type[] ex = toType(md.getExceptions()); 360 361 String field_name = "__mth$" + md.getName() + md.scrabmledSignature(); 362 363 FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE 365 | Opcodes.ACC_STATIC, field_name, PROXY_METHOD_TYPE 366 .getDescriptor(), null, null); 367 fv.visitEnd(); 368 369 clazzInit.dup(); 370 clazzInit.push(m.getName()); 371 clazzInit.push(m.getDescriptor()); 372 clazzInit.push(md.isImplemented()); 373 clazzInit.invokeStatic(PROXY_HELPER_TYPE, PROXY_HELPER_GET_METHOD); 374 clazzInit.putStatic(selfType, field_name, PROXY_METHOD_TYPE); 375 376 org.objectweb.asm.commons.Method sm = new org.objectweb.asm.commons.Method( 377 "__super$" + m.getName(), m.getReturnType(), m 378 .getArgumentTypes()); 379 380 GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC, m, null, 384 ex, cw); 385 386 ga.loadThis(); 387 ga.getField(selfType, INVOCATION_HANDLER_FIELD_NAME, 388 INVOCATION_HANDLER_TYPE); 389 390 393 if (md.isImplemented()) { 394 ga.dup(); 395 Label ok = ga.newLabel(); 396 ga.ifNonNull(ok); 397 398 ga.loadThis(); 399 ga.loadArgs(); 400 ga.invokeConstructor(superType, m); 401 ga.returnValue(); 402 ga.mark(ok); 403 } 404 405 ga.loadThis(); 406 ga.getStatic(selfType, field_name, PROXY_METHOD_TYPE); 407 408 if (m.getArgumentTypes().length == 0) { 409 ga.getStatic(JAVA_PROXY_TYPE, "NO_ARGS", Type 411 .getType(Object [].class)); 412 } else { 413 ga.loadArgArray(); 415 } 416 417 Label before = ga.mark(); 418 419 ga.invokeInterface(INVOCATION_HANDLER_TYPE, 420 INVOCATION_HANDLER_INVOKE_METHOD); 421 422 Label after = ga.mark(); 423 424 ga.unbox(m.getReturnType()); 425 ga.returnValue(); 426 427 Label rethrow = ga.mark(); 429 ga.visitInsn(Opcodes.ATHROW); 430 431 for (int i = 0; i < ex.length; i++) { 432 ga.visitTryCatchBlock(before, after, rethrow, ex[i] 433 .getInternalName()); 434 } 435 436 ga.visitTryCatchBlock(before, after, rethrow, "java/lang/Error"); 437 ga.visitTryCatchBlock(before, after, rethrow, 438 "java/lang/RuntimeException"); 439 440 Type thr = Type.getType(Throwable .class); 441 Label handler = ga.mark(); 442 Type udt = Type.getType(UndeclaredThrowableException .class); 443 int loc = ga.newLocal(thr); 444 ga.storeLocal(loc, thr); 445 ga.newInstance(udt); 446 ga.dup(); 447 ga.loadLocal(loc, thr); 448 ga.invokeConstructor(udt, org.objectweb.asm.commons.Method 449 .getMethod("void <init>(java.lang.Throwable)")); 450 ga.throwException(); 451 452 ga.visitTryCatchBlock(before, after, handler, "java/lang/Throwable"); 453 454 ga.endMethod(); 455 456 if (md.isImplemented()) { 460 461 GeneratorAdapter ga2 = new GeneratorAdapter(Opcodes.ACC_PUBLIC, sm, 462 null, ex, cw); 463 464 ga2.loadThis(); 465 ga2.loadArgs(); 466 ga2.invokeConstructor(superType, m); 467 ga2.returnValue(); 468 ga2.endMethod(); 469 } 470 } 471 472 private static Class [] generateConstructor(Type selfType, 473 Constructor constructor, ClassVisitor cw) { 474 475 Class [] superConstructorParameterTypes = constructor 476 .getParameterTypes(); 477 Class [] newConstructorParameterTypes = new Class [superConstructorParameterTypes.length + 1]; 478 System.arraycopy(superConstructorParameterTypes, 0, 479 newConstructorParameterTypes, 0, 480 superConstructorParameterTypes.length); 481 newConstructorParameterTypes[superConstructorParameterTypes.length] = JavaProxyInvocationHandler.class; 482 483 int access = Opcodes.ACC_PUBLIC; 484 String name1 = "<init>"; 485 String signature = null; 486 Class [] superConstructorExceptions = constructor.getExceptionTypes(); 487 488 org.objectweb.asm.commons.Method super_m = new org.objectweb.asm.commons.Method( 489 name1, Type.VOID_TYPE, toType(superConstructorParameterTypes)); 490 org.objectweb.asm.commons.Method m = new org.objectweb.asm.commons.Method( 491 name1, Type.VOID_TYPE, toType(newConstructorParameterTypes)); 492 493 GeneratorAdapter ga = new GeneratorAdapter(access, m, signature, 494 toType(superConstructorExceptions), cw); 495 496 ga.loadThis(); 497 ga.loadArgs(0, superConstructorParameterTypes.length); 498 ga.invokeConstructor(Type.getType(constructor.getDeclaringClass()), 499 super_m); 500 501 ga.loadThis(); 502 ga.loadArg(superConstructorParameterTypes.length); 503 ga.putField(selfType, INVOCATION_HANDLER_FIELD_NAME, 504 INVOCATION_HANDLER_TYPE); 505 506 ga.returnValue(); 508 ga.endMethod(); 509 return newConstructorParameterTypes; 510 } 511 512 private static String toInternalClassName(Class clazz) { 513 return toInternalClassName(clazz.getName()); 514 } 515 516 private static String toInternalClassName(String name) { 517 return name.replace('.', '/'); 518 } 519 520 private static Type[] toType(Class [] parameterTypes) { 521 Type[] result = new Type[parameterTypes.length]; 522 for (int i = 0; i < result.length; i++) { 523 result[i] = Type.getType(parameterTypes[i]); 524 } 525 return result; 526 } 527 528 private static void collectMethods(Class superClass, Class [] interfaces, 529 Map methods) { 530 HashSet allClasses = new HashSet (); 531 addClass(allClasses, methods, superClass); 532 addInterfaces(allClasses, methods, interfaces); 533 } 534 535 static class MethodData { 536 Set methods = new HashSet (); 537 538 final Method mostSpecificMethod; 539 540 boolean hasPublicDecl = false; 541 542 MethodData(Method method) { 543 this.mostSpecificMethod = method; 544 hasPublicDecl = method.getDeclaringClass().isInterface() 545 || Modifier.isPublic(method.getModifiers()); 546 } 547 548 public String scrabmledSignature() { 549 StringBuffer sb = new StringBuffer (); 550 Class [] parms = getParameterTypes(); 551 for (int i = 0; i < parms.length; i++) { 552 sb.append('$'); 553 String name = parms[i].getName(); 554 name = name.replace('[', '1'); 555 name = name.replace('.', '_'); 556 name = name.replace(';', '2'); 557 sb.append(name); 558 } 559 return sb.toString(); 560 } 561 562 public Class getDeclaringClass() { 563 return mostSpecificMethod.getDeclaringClass(); 564 } 565 566 public org.objectweb.asm.commons.Method getMethod() { 567 return new org.objectweb.asm.commons.Method(getName(), Type 568 .getType(getReturnType()), getType(getParameterTypes())); 569 } 570 571 private Type[] getType(Class [] parameterTypes) { 572 Type[] result = new Type[parameterTypes.length]; 573 for (int i = 0; i < parameterTypes.length; i++) { 574 result[i] = Type.getType(parameterTypes[i]); 575 } 576 return result; 577 } 578 579 private String getName() { 580 return mostSpecificMethod.getName(); 581 } 582 583 private Class [] getParameterTypes() { 584 return mostSpecificMethod.getParameterTypes(); 585 } 586 587 public Class [] getExceptions() { 588 589 Set all = new HashSet (); 590 591 Iterator it = methods.iterator(); 592 while (it.hasNext()) { 593 Method m = (Method ) it.next(); 594 Class [] ex = m.getExceptionTypes(); 595 for (int i = 0; i < ex.length; i++) { 596 Class exx = ex[i]; 597 598 if (all.contains(exx)) { 599 continue; 600 } 601 602 boolean add = true; 603 Iterator it2 = all.iterator(); 604 while (it2.hasNext()) { 605 Class de = (Class ) it2.next(); 606 607 if (de.isAssignableFrom(exx)) { 608 add = false; 609 break; 610 } else if (exx.isAssignableFrom(de)) { 611 it2.remove(); 612 add = true; 613 } 614 615 } 616 617 if (add) { 618 all.add(exx); 619 } 620 } 621 } 622 623 return (Class []) all.toArray(new Class [all.size()]); 624 } 625 626 public boolean generateProxyMethod() { 627 return !isFinal() && hasPublicDecl; 628 } 629 630 public void add(Method method) { 631 methods.add(method); 632 hasPublicDecl |= Modifier.isPublic(method.getModifiers()); 633 } 634 635 Class getReturnType() { 636 return mostSpecificMethod.getReturnType(); 637 } 638 639 boolean isFinal() { 640 if (mostSpecificMethod.getDeclaringClass().isInterface()) { 641 return false; 642 } 643 644 int mod = mostSpecificMethod.getModifiers(); 645 return Modifier.isFinal(mod); 646 } 647 648 boolean isImplemented() { 649 if (mostSpecificMethod.getDeclaringClass().isInterface()) { 650 return false; 651 } 652 653 int mod = mostSpecificMethod.getModifiers(); 654 return !Modifier.isAbstract(mod); 655 } 656 } 657 658 static class MethodKey { 659 private String name; 660 661 private Class [] arguments; 662 663 MethodKey(Method m) { 664 this.name = m.getName(); 665 this.arguments = m.getParameterTypes(); 666 } 667 668 public boolean equals(Object obj) { 669 if (obj instanceof MethodKey) { 670 MethodKey key = (MethodKey) obj; 671 672 return name.equals(key.name) 673 && Arrays.equals(arguments, key.arguments); 674 } 675 676 return false; 677 } 678 679 public int hashCode() { 680 return name.hashCode(); 681 } 682 } 683 684 private static void addInterfaces(Set allClasses, Map methods, 685 Class [] interfaces) { 686 for (int i = 0; i < interfaces.length; i++) { 687 addInterface(allClasses, methods, interfaces[i]); 688 } 689 } 690 691 private static void addInterface(Set allClasses, Map methods, 692 Class interfaze) { 693 if (allClasses.add(interfaze)) { 694 addMethods(methods, interfaze); 695 addInterfaces(allClasses, methods, interfaze.getInterfaces()); 696 } 697 } 698 699 private static void addMethods(Map methods, Class classOrInterface) { 700 Method [] mths = classOrInterface.getDeclaredMethods(); 701 for (int i = 0; i < mths.length; i++) { 702 addMethod(methods, mths[i]); 703 } 704 } 705 706 private static void addMethod(Map methods, Method method) { 707 708 int acc = method.getModifiers(); 709 if (Modifier.isStatic(acc) || Modifier.isPrivate(acc)) { 710 return; 711 } 712 713 MethodKey mk = new MethodKey(method); 714 MethodData md = (MethodData) methods.get(mk); 715 if (md == null) { 716 md = new MethodData(method); 717 methods.put(mk, md); 718 } 719 md.add(method); 720 } 721 722 private static void addClass(Set allClasses, Map methods, Class clazz) { 723 if (allClasses.add(clazz)) { 724 addMethods(methods, clazz); 725 Class superClass = clazz.getSuperclass(); 726 if (superClass != null) { 727 addClass(allClasses, methods, superClass); 728 } 729 730 addInterfaces(allClasses, methods, clazz.getInterfaces()); 731 } 732 } 733 734 private static void validateArgs(String targetClassName, Class superClass) { 735 736 if (Modifier.isFinal(superClass.getModifiers())) { 737 throw new IllegalArgumentException ("cannot extend final class"); 738 } 739 740 String targetPackage = packageName(targetClassName); 741 742 String pkg = targetPackage.replace('.', '/'); 743 if (pkg.startsWith("java")) { 744 throw new IllegalArgumentException ("cannor add classes to package " 745 + pkg); 746 } 747 748 Package p = Package.getPackage(pkg); 749 if (p != null) { 750 if (p.isSealed()) { 751 throw new IllegalArgumentException ("package " + p 752 + " is sealed"); 753 } 754 } 755 } 756 757 private static String packageName(Class clazz) { 758 String clazzName = clazz.getName(); 759 return packageName(clazzName); 760 } 761 762 private static String packageName(String clazzName) { 763 int idx = clazzName.lastIndexOf('.'); 764 if (idx == -1) { 765 return ""; 766 } else { 767 return clazzName.substring(0, idx); 768 } 769 } 770 771 } 772 | Popular Tags |