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; | ... | ... |