Commit 5225abe9413b4aa801488bc01834bc3aedf386e7
1 parent
38127d17
some tweaks to HandlerList and added more javadoc
Showing
1 changed file
with
165 additions
and
54 deletions
java/common/com/mumfrey/liteloader/core/event/HandlerList.java
| ... | ... | @@ -82,9 +82,8 @@ public class HandlerList<T> extends LinkedList<T> |
| 82 | 82 | */ |
| 83 | 83 | protected void bake() |
| 84 | 84 | { |
| 85 | - HandlerListClassLoader<T> classLoader = new HandlerListClassLoader<T>(this.type, this.size()); | |
| 86 | - this.bakedHandler = classLoader.newHandler(); | |
| 87 | - this.bakedHandler.populate(this); | |
| 85 | + HandlerListClassLoader<T> classLoader = new HandlerListClassLoader<T>(this.type); | |
| 86 | + this.bakedHandler = classLoader.newHandler(this); | |
| 88 | 87 | } |
| 89 | 88 | |
| 90 | 89 | /** |
| ... | ... | @@ -370,7 +369,22 @@ public class HandlerList<T> extends LinkedList<T> |
| 370 | 369 | { |
| 371 | 370 | public abstract T get(); |
| 372 | 371 | |
| 373 | - public abstract void populate(List<T> listeners); | |
| 372 | + public abstract BakedHandlerList<T> populate(List<T> listeners); | |
| 373 | + } | |
| 374 | + | |
| 375 | + /** | |
| 376 | + * Exception to throw when failing to bake a handler list | |
| 377 | + * | |
| 378 | + * @author Adam Mummery-Smith | |
| 379 | + */ | |
| 380 | + static class BakingFailedException extends RuntimeException | |
| 381 | + { | |
| 382 | + private static final long serialVersionUID = 1L; | |
| 383 | + | |
| 384 | + public BakingFailedException(Throwable cause) | |
| 385 | + { | |
| 386 | + super("An unexpected error occurred while baking the handler list", cause); | |
| 387 | + } | |
| 374 | 388 | } |
| 375 | 389 | |
| 376 | 390 | /** |
| ... | ... | @@ -381,66 +395,118 @@ public class HandlerList<T> extends LinkedList<T> |
| 381 | 395 | */ |
| 382 | 396 | static class HandlerListClassLoader<T> extends URLClassLoader |
| 383 | 397 | { |
| 398 | + private static final String HANDLER_VAR_PREFIX = "handler$"; | |
| 399 | + | |
| 384 | 400 | /** |
| 385 | 401 | * Unique index number, just to ensure no name clashes |
| 386 | 402 | */ |
| 387 | 403 | private static int handlerIndex; |
| 388 | 404 | |
| 389 | - private int lineNumber = 1; | |
| 390 | - | |
| 405 | + /** | |
| 406 | + * Interface type which this classloader is generating handler for | |
| 407 | + */ | |
| 391 | 408 | private final Class<T> type; |
| 392 | 409 | |
| 410 | + /** | |
| 411 | + * Calculated class ref for the class type so that we don't have to keep calling getName().replace('.', '/') | |
| 412 | + */ | |
| 393 | 413 | private final String typeRef; |
| 394 | 414 | |
| 415 | + /** | |
| 416 | + * Size of the handler list | |
| 417 | + */ | |
| 395 | 418 | private int size; |
| 396 | - | |
| 419 | + | |
| 397 | 420 | /** |
| 398 | 421 | * @param type |
| 399 | 422 | * @param size |
| 400 | 423 | */ |
| 401 | - HandlerListClassLoader(Class<T> type, int size) | |
| 424 | + HandlerListClassLoader(Class<T> type) | |
| 402 | 425 | { |
| 403 | 426 | super(new URL[0], Launch.classLoader); |
| 404 | 427 | this.type = type; |
| 405 | 428 | this.typeRef = type.getName().replace('.', '/'); |
| 406 | - this.size = size; | |
| 407 | 429 | } |
| 408 | 430 | |
| 409 | 431 | /** |
| 410 | 432 | * Create and return a new baked handler list |
| 411 | 433 | */ |
| 412 | 434 | @SuppressWarnings("unchecked") |
| 413 | - public BakedHandlerList<T> newHandler() | |
| 435 | + public BakedHandlerList<T> newHandler(HandlerList<T> list) | |
| 436 | + { | |
| 437 | + this.size = list.size(); | |
| 438 | + | |
| 439 | + Class<BakedHandlerList<T>> handlerClass = null; | |
| 440 | + | |
| 441 | + try | |
| 442 | + { | |
| 443 | + // Inflect the class name and attempt to generate the class | |
| 444 | + String className = HandlerListClassLoader.getNextClassName(Obf.HandlerList.name, this.type.getSimpleName()); | |
| 445 | + handlerClass = (Class<BakedHandlerList<T>>)this.loadClass(className); | |
| 446 | + } | |
| 447 | + catch (ClassNotFoundException ex) | |
| 448 | + { | |
| 449 | + throw new BakingFailedException(ex); | |
| 450 | + } | |
| 451 | + | |
| 452 | + try | |
| 453 | + { | |
| 454 | + // Create an instance of the class, populate the entries from the supplied list and return it | |
| 455 | + BakedHandlerList<T> handlerList = this.createInstance(handlerClass); | |
| 456 | + return handlerList.populate(list); | |
| 457 | + } | |
| 458 | + catch (InstantiationException ex) | |
| 459 | + { | |
| 460 | + throw new BakingFailedException(ex); | |
| 461 | + } | |
| 462 | + } | |
| 463 | + | |
| 464 | + /** | |
| 465 | + * Create an instance of the baked class | |
| 466 | + * | |
| 467 | + * @param handlerClass Baked HandlerList class | |
| 468 | + * @return new instance of the Baked HandlerList class | |
| 469 | + * @throws InstantiationException if the handler can't be created for some reason | |
| 470 | + */ | |
| 471 | + private BakedHandlerList<T> createInstance(Class<BakedHandlerList<T>> handlerClass) throws InstantiationException | |
| 414 | 472 | { |
| 415 | 473 | try |
| 416 | 474 | { |
| 417 | - String className = this.getNextClassName(); | |
| 418 | - Class<BakedHandlerList<T>> handlerClass = (Class<BakedHandlerList<T>>)this.loadClass(className); | |
| 419 | 475 | Constructor<BakedHandlerList<T>> ctor = handlerClass.getDeclaredConstructor(); |
| 420 | 476 | ctor.setAccessible(true); |
| 421 | 477 | return ctor.newInstance(); |
| 422 | 478 | } |
| 423 | 479 | catch (Exception ex) |
| 424 | 480 | { |
| 425 | - throw new RuntimeException(ex); | |
| 481 | + InstantiationException ie = new InstantiationException("Error instantiating class " + handlerClass); | |
| 482 | + ie.setStackTrace(ex.getStackTrace()); | |
| 483 | + throw ie; | |
| 426 | 484 | } |
| 427 | 485 | } |
| 428 | 486 | |
| 487 | + /* (non-Javadoc) | |
| 488 | + * @see java.net.URLClassLoader#findClass(java.lang.String) | |
| 489 | + */ | |
| 429 | 490 | @Override |
| 430 | 491 | protected Class<?> findClass(String name) throws ClassNotFoundException |
| 431 | 492 | { |
| 432 | 493 | try |
| 433 | 494 | { |
| 495 | + // Read the basic class template | |
| 434 | 496 | byte[] bytes = Launch.classLoader.getClassBytes(Obf.BakedHandlerList.name); |
| 435 | 497 | ClassReader classReader = new ClassReader(bytes); |
| 436 | 498 | ClassNode classNode = new ClassNode(); |
| 437 | 499 | classReader.accept(classNode, ClassReader.EXPAND_FRAMES); |
| 438 | 500 | |
| 501 | + // Apply all transformations to the class, injects our custom code | |
| 439 | 502 | this.transform(name, classNode); |
| 440 | 503 | |
| 504 | + // Write the class | |
| 441 | 505 | ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); |
| 442 | 506 | classNode.accept(classWriter); |
| 443 | 507 | bytes = classWriter.toByteArray(); |
| 508 | + | |
| 509 | + // Delegate to ClassLoader's usual behaviour to load the class we just generated | |
| 444 | 510 | return this.defineClass(name, bytes, 0, bytes.length); |
| 445 | 511 | } |
| 446 | 512 | catch (Throwable th) |
| ... | ... | @@ -450,30 +516,49 @@ public class HandlerList<T> extends LinkedList<T> |
| 450 | 516 | } |
| 451 | 517 | } |
| 452 | 518 | |
| 453 | - private void transform(String name, ClassNode classNode) | |
| 519 | + /** | |
| 520 | + * Perform all class bytecode transformations | |
| 521 | + * | |
| 522 | + * @param name | |
| 523 | + * @param classNode | |
| 524 | + * @throws IOException | |
| 525 | + */ | |
| 526 | + private void transform(String name, ClassNode classNode) throws IOException | |
| 454 | 527 | { |
| 455 | 528 | LiteLoaderLogger.info("Baking listener list for %s with %d listeners", this.type.getSimpleName(), this.size); |
| 456 | 529 | LiteLoaderLogger.debug("Generating: %s", name); |
| 457 | 530 | |
| 458 | 531 | this.populateClass(name, classNode); |
| 459 | 532 | this.transformMethods(name, classNode); |
| 460 | - this.injectInterfaceMethods(name, classNode); | |
| 533 | + this.injectInterfaceMethods(classNode, this.type.getName()); | |
| 461 | 534 | } |
| 462 | 535 | |
| 536 | + /** | |
| 537 | + * Populate the class node itself | |
| 538 | + * | |
| 539 | + * @param name | |
| 540 | + * @param classNode | |
| 541 | + */ | |
| 463 | 542 | private void populateClass(String name, ClassNode classNode) |
| 464 | 543 | { |
| 465 | 544 | classNode.access = classNode.access & ~Opcodes.ACC_ABSTRACT; |
| 466 | 545 | classNode.name = name.replace('.', '/'); |
| 467 | 546 | classNode.superName = Obf.BakedHandlerList.ref; |
| 468 | - classNode.interfaces.add(this.type.getName().replace('.', '/')); | |
| 469 | - classNode.sourceFile = "Dynamic"; | |
| 547 | + classNode.interfaces.add(this.typeRef); | |
| 548 | + classNode.sourceFile = name.substring(name.lastIndexOf('.') + 1) + ".java"; | |
| 470 | 549 | |
| 471 | - for (int i = 0; i < this.size; i++) | |
| 550 | + for (int handlerIndex = 0; handlerIndex < this.size; handlerIndex++) | |
| 472 | 551 | { |
| 473 | - classNode.fields.add(new FieldNode(Opcodes.ACC_PRIVATE, "handler$" + i, "L" + this.typeRef + ";", null, null)); | |
| 552 | + classNode.fields.add(new FieldNode(Opcodes.ACC_PRIVATE, HandlerListClassLoader.HANDLER_VAR_PREFIX + handlerIndex, "L" + this.typeRef + ";", null, null)); | |
| 474 | 553 | } |
| 475 | 554 | } |
| 476 | 555 | |
| 556 | + /** | |
| 557 | + * Transform existing methods in the template class | |
| 558 | + * | |
| 559 | + * @param name | |
| 560 | + * @param classNode | |
| 561 | + */ | |
| 477 | 562 | private void transformMethods(String name, ClassNode classNode) |
| 478 | 563 | { |
| 479 | 564 | for (Iterator<MethodNode> methodIterator = classNode.methods.iterator(); methodIterator.hasNext();) |
| ... | ... | @@ -494,6 +579,12 @@ public class HandlerList<T> extends LinkedList<T> |
| 494 | 579 | } |
| 495 | 580 | } |
| 496 | 581 | |
| 582 | + /** | |
| 583 | + * Transform the constructor | |
| 584 | + * | |
| 585 | + * @param classNode | |
| 586 | + * @param method | |
| 587 | + */ | |
| 497 | 588 | private void processCtor(ClassNode classNode, MethodNode method) |
| 498 | 589 | { |
| 499 | 590 | for (Iterator<AbstractInsnNode> iter = method.instructions.iterator(); iter.hasNext();) |
| ... | ... | @@ -510,6 +601,12 @@ public class HandlerList<T> extends LinkedList<T> |
| 510 | 601 | } |
| 511 | 602 | } |
| 512 | 603 | |
| 604 | + /** | |
| 605 | + * Transform .get() | |
| 606 | + * | |
| 607 | + * @param classNode | |
| 608 | + * @param method | |
| 609 | + */ | |
| 513 | 610 | private void processGet(ClassNode classNode, MethodNode method) |
| 514 | 611 | { |
| 515 | 612 | method.access = method.access & ~Opcodes.ACC_ABSTRACT; |
| ... | ... | @@ -519,40 +616,41 @@ public class HandlerList<T> extends LinkedList<T> |
| 519 | 616 | method.instructions.add(new InsnNode(Opcodes.ARETURN)); |
| 520 | 617 | } |
| 521 | 618 | |
| 619 | + /** | |
| 620 | + * Transform .processPopulate() | |
| 621 | + * | |
| 622 | + * @param classNode | |
| 623 | + * @param method | |
| 624 | + */ | |
| 522 | 625 | private void processPopulate(ClassNode classNode, MethodNode method) |
| 523 | 626 | { |
| 524 | 627 | method.access = method.access & ~Opcodes.ACC_ABSTRACT; |
| 525 | 628 | method.instructions.clear(); |
| 526 | 629 | |
| 527 | - for (int i = 0; i < this.size; i++) | |
| 630 | + for (int handlerIndex = 0; handlerIndex < this.size; handlerIndex++) | |
| 528 | 631 | { |
| 529 | 632 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); |
| 530 | 633 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); |
| 531 | - method.instructions.add(new IntInsnNode(Opcodes.BIPUSH, i)); | |
| 634 | + method.instructions.add(new IntInsnNode(Opcodes.BIPUSH, handlerIndex)); | |
| 532 | 635 | method.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;", true)); |
| 533 | 636 | method.instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, this.typeRef)); |
| 534 | - method.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, "handler$" + i, "L" + this.typeRef + ";")); | |
| 637 | + method.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, HandlerListClassLoader.HANDLER_VAR_PREFIX + handlerIndex, "L" + this.typeRef + ";")); | |
| 535 | 638 | } |
| 536 | 639 | |
| 537 | - method.instructions.add(new InsnNode(Opcodes.RETURN)); | |
| 538 | - } | |
| 539 | - | |
| 540 | - private void injectInterfaceMethods(String name, ClassNode classNode) | |
| 541 | - { | |
| 542 | - try | |
| 543 | - { | |
| 544 | - String interfaceName = this.type.getName(); | |
| 545 | - this.injectInterfaceMethods(classNode, interfaceName); | |
| 546 | - } | |
| 547 | - catch (IOException ex) | |
| 548 | - { | |
| 549 | - ex.printStackTrace(); | |
| 550 | - } | |
| 640 | + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); | |
| 641 | + method.instructions.add(new InsnNode(Opcodes.ARETURN)); | |
| 551 | 642 | } |
| 552 | 643 | |
| 644 | + /** | |
| 645 | + * Recurse down the interface inheritance hierarchy and inject methods to handle each interface | |
| 646 | + * | |
| 647 | + * @param classNode | |
| 648 | + * @param interfaceName | |
| 649 | + * @throws IOException | |
| 650 | + */ | |
| 553 | 651 | private void injectInterfaceMethods(ClassNode classNode, String interfaceName) throws IOException |
| 554 | 652 | { |
| 555 | - ClassReader interfaceReader = new ClassReader(this.getInterfaceBytes(interfaceName)); | |
| 653 | + ClassReader interfaceReader = new ClassReader(HandlerListClassLoader.getInterfaceBytes(interfaceName)); | |
| 556 | 654 | ClassNode interfaceNode = new ClassNode(); |
| 557 | 655 | interfaceReader.accept(interfaceNode, 0); |
| 558 | 656 | |
| ... | ... | @@ -568,6 +666,12 @@ public class HandlerList<T> extends LinkedList<T> |
| 568 | 666 | } |
| 569 | 667 | } |
| 570 | 668 | |
| 669 | + /** | |
| 670 | + * Inject the supplied interface method into the target class an populate it with method calls to the list members | |
| 671 | + * | |
| 672 | + * @param classNode | |
| 673 | + * @param method | |
| 674 | + */ | |
| 571 | 675 | private void populateInterfaceMethod(ClassNode classNode, MethodNode method) |
| 572 | 676 | { |
| 573 | 677 | Type returnType = Type.getReturnType(method.desc); |
| ... | ... | @@ -577,13 +681,13 @@ public class HandlerList<T> extends LinkedList<T> |
| 577 | 681 | Type[] args = Type.getArgumentTypes(method.desc); |
| 578 | 682 | method.access = Opcodes.ACC_PUBLIC; |
| 579 | 683 | |
| 580 | - for (int i = 0; i < this.size; i++) | |
| 684 | + for (int handlerIndex = 0; handlerIndex < this.size; handlerIndex++) | |
| 581 | 685 | { |
| 582 | 686 | LabelNode lineNumberLabel = new LabelNode(new Label()); |
| 583 | 687 | method.instructions.add(lineNumberLabel); |
| 584 | - method.instructions.add(new LineNumberNode(++this.lineNumber, lineNumberLabel)); | |
| 688 | + method.instructions.add(new LineNumberNode(100 + handlerIndex, lineNumberLabel)); | |
| 585 | 689 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); |
| 586 | - method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, "handler$" + i, "L" + this.typeRef + ";")); | |
| 690 | + method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, HandlerListClassLoader.HANDLER_VAR_PREFIX + handlerIndex, "L" + this.typeRef + ";")); | |
| 587 | 691 | this.invokeInterfaceMethod(method, args); |
| 588 | 692 | } |
| 589 | 693 | |
| ... | ... | @@ -591,6 +695,12 @@ public class HandlerList<T> extends LinkedList<T> |
| 591 | 695 | } |
| 592 | 696 | } |
| 593 | 697 | |
| 698 | + /** | |
| 699 | + * Inject instructions into the supplied method to invoke the same method on the supplied interface | |
| 700 | + * | |
| 701 | + * @param method | |
| 702 | + * @param args | |
| 703 | + */ | |
| 594 | 704 | private void invokeInterfaceMethod(MethodNode method, Type[] args) |
| 595 | 705 | { |
| 596 | 706 | int argNumber = 1; |
| ... | ... | @@ -603,27 +713,28 @@ public class HandlerList<T> extends LinkedList<T> |
| 603 | 713 | method.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, this.typeRef, method.name, method.desc, true)); |
| 604 | 714 | } |
| 605 | 715 | |
| 606 | - private String getNextClassName() | |
| 716 | + /** | |
| 717 | + * @param baseName | |
| 718 | + * @param typeName | |
| 719 | + * @return | |
| 720 | + */ | |
| 721 | + private static String getNextClassName(String baseName, String typeName) | |
| 607 | 722 | { |
| 608 | - return String.format("%s$%s$%s", Obf.HandlerList.name, this.type.getSimpleName(), HandlerListClassLoader.handlerIndex++); | |
| 723 | + return String.format("%s$%s%d", baseName, typeName, HandlerListClassLoader.handlerIndex++); | |
| 609 | 724 | } |
| 610 | 725 | |
| 611 | - private byte[] getInterfaceBytes(String name) throws IOException | |
| 726 | + /** | |
| 727 | + * @param name | |
| 728 | + * @return | |
| 729 | + * @throws IOException | |
| 730 | + */ | |
| 731 | + private static byte[] getInterfaceBytes(String name) throws IOException | |
| 612 | 732 | { |
| 613 | 733 | byte[] bytes = Launch.classLoader.getClassBytes(name); |
| 614 | 734 | |
| 615 | - final List<IClassTransformer> transformers = Launch.classLoader.getTransformers(); | |
| 616 | - | |
| 617 | - for (final IClassTransformer transformer : transformers) | |
| 735 | + for (final IClassTransformer transformer : Launch.classLoader.getTransformers()) | |
| 618 | 736 | { |
| 619 | - try | |
| 620 | - { | |
| 621 | - bytes = transformer.transform(name, name, bytes); | |
| 622 | - } | |
| 623 | - catch (Exception ex) | |
| 624 | - { | |
| 625 | - ex.printStackTrace(); | |
| 626 | - } | |
| 737 | + bytes = transformer.transform(name, name, bytes); | |
| 627 | 738 | } |
| 628 | 739 | |
| 629 | 740 | return bytes; | ... | ... |