001// Copyright 2011 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.internal.plastic; 016 017import java.lang.reflect.Method; 018import java.util.HashMap; 019import java.util.Map; 020 021import org.apache.tapestry5.internal.plastic.InstructionBuilderState.LVInfo; 022import org.apache.tapestry5.internal.plastic.asm.Label; 023import org.apache.tapestry5.internal.plastic.asm.MethodVisitor; 024import org.apache.tapestry5.internal.plastic.asm.Opcodes; 025import org.apache.tapestry5.internal.plastic.asm.Type; 026import org.apache.tapestry5.plastic.Condition; 027import org.apache.tapestry5.plastic.InstructionBuilder; 028import org.apache.tapestry5.plastic.InstructionBuilderCallback; 029import org.apache.tapestry5.plastic.LocalVariable; 030import org.apache.tapestry5.plastic.LocalVariableCallback; 031import org.apache.tapestry5.plastic.MethodDescription; 032import org.apache.tapestry5.plastic.PlasticField; 033import org.apache.tapestry5.plastic.PlasticMethod; 034import org.apache.tapestry5.plastic.PlasticUtils; 035import org.apache.tapestry5.plastic.SwitchCallback; 036import org.apache.tapestry5.plastic.TryCatchCallback; 037import org.apache.tapestry5.plastic.WhenCallback; 038import org.apache.tapestry5.plastic.WhileCallback; 039 040@SuppressWarnings("rawtypes") 041public class InstructionBuilderImpl extends Lockable implements Opcodes, InstructionBuilder 042{ 043 private static final int[] DUPE_OPCODES = new int[] 044 {DUP, DUP_X1, DUP_X2}; 045 046 /** 047 * Maps from condition to opcode to jump to the false code block. 048 */ 049 private static final Map<Condition, Integer> conditionToOpcode = new HashMap<>(); 050 051 static 052 { 053 Map<Condition, Integer> m = conditionToOpcode; 054 055 m.put(Condition.NULL, IFNONNULL); 056 m.put(Condition.NON_NULL, IFNULL); 057 m.put(Condition.ZERO, IFNE); 058 m.put(Condition.NON_ZERO, IFEQ); 059 m.put(Condition.EQUAL, IF_ICMPNE); 060 m.put(Condition.NOT_EQUAL, IF_ICMPEQ); 061 m.put(Condition.LESS_THAN, IF_ICMPGE); 062 m.put(Condition.GREATER, IF_ICMPLE); 063 } 064 065 private static final Map<String, Integer> typeToSpecialComparisonOpcode = new HashMap<>(); 066 067 static 068 { 069 Map<String, Integer> m = typeToSpecialComparisonOpcode; 070 071 m.put("long", LCMP); 072 m.put("float", FCMPL); 073 m.put("double", DCMPL); 074 } 075 076 private static final Map<Object, Integer> constantOpcodes = new HashMap<>(); 077 078 static 079 { 080 Map<Object, Integer> m = constantOpcodes; 081 082 m.put(Integer.valueOf(-1), ICONST_M1); 083 m.put(Integer.valueOf(0), ICONST_0); 084 m.put(Integer.valueOf(1), ICONST_1); 085 m.put(Integer.valueOf(2), ICONST_2); 086 m.put(Integer.valueOf(3), ICONST_3); 087 m.put(Integer.valueOf(4), ICONST_4); 088 m.put(Integer.valueOf(5), ICONST_5); 089 090 m.put(Long.valueOf(0), LCONST_0); 091 m.put(Long.valueOf(1), LCONST_1); 092 093 m.put(Float.valueOf(0), FCONST_0); 094 m.put(Float.valueOf(1), FCONST_1); 095 m.put(Float.valueOf(2), FCONST_2); 096 097 m.put(Double.valueOf(0), DCONST_0); 098 m.put(Double.valueOf(1), DCONST_1); 099 100 m.put(null, ACONST_NULL); 101 } 102 103 protected final InstructionBuilderState state; 104 105 protected final MethodVisitor v; 106 107 protected final NameCache cache; 108 109 InstructionBuilderImpl(MethodDescription description, MethodVisitor visitor, NameCache cache) 110 { 111 InstructionBuilderState state = new InstructionBuilderState(description, visitor, cache); 112 this.state = state; 113 114 // These are conveniences for values stored inside the state. In fact, 115 // these fields predate the InstructionBuilderState type. 116 117 this.v = state.visitor; 118 this.cache = state.nameCache; 119 } 120 121 @Override 122 public InstructionBuilder returnDefaultValue() 123 { 124 check(); 125 126 PrimitiveType type = PrimitiveType.getByName(state.description.returnType); 127 128 if (type == null) 129 { 130 v.visitInsn(ACONST_NULL); 131 v.visitInsn(ARETURN); 132 } else 133 { 134 switch (type) 135 { 136 case VOID: 137 break; 138 139 case LONG: 140 v.visitInsn(LCONST_0); 141 break; 142 143 case FLOAT: 144 v.visitInsn(FCONST_0); 145 break; 146 147 case DOUBLE: 148 v.visitInsn(DCONST_0); 149 break; 150 151 default: 152 v.visitInsn(ICONST_0); 153 break; 154 } 155 156 v.visitInsn(type.returnOpcode); 157 } 158 159 return this; 160 } 161 162 @Override 163 public InstructionBuilder loadThis() 164 { 165 check(); 166 167 v.visitVarInsn(ALOAD, 0); 168 169 return this; 170 } 171 172 @Override 173 public InstructionBuilder loadNull() 174 { 175 check(); 176 177 v.visitInsn(ACONST_NULL); 178 179 return this; 180 } 181 182 @Override 183 public InstructionBuilder loadArgument(int index) 184 { 185 check(); 186 187 PrimitiveType type = PrimitiveType.getByName(state.description.argumentTypes[index]); 188 189 int opcode = type == null ? ALOAD : type.loadOpcode; 190 191 v.visitVarInsn(state.argumentLoadOpcode[index], state.argumentIndex[index]); 192 193 return this; 194 } 195 196 @Override 197 public InstructionBuilder loadArguments() 198 { 199 check(); 200 201 for (int i = 0; i < state.description.argumentTypes.length; i++) 202 { 203 loadArgument(i); 204 } 205 206 return this; 207 } 208 209 @Override 210 public InstructionBuilder invokeSpecial(String containingClassName, MethodDescription description) 211 { 212 check(); 213 214 doInvoke(INVOKESPECIAL, containingClassName, description, false); 215 216 return this; 217 } 218 219 @Override 220 public InstructionBuilder invokeVirtual(PlasticMethod method) 221 { 222 check(); 223 224 assert method != null; 225 226 MethodDescription description = method.getDescription(); 227 228 return invokeVirtual(method.getPlasticClass().getClassName(), description.returnType, description.methodName, 229 description.argumentTypes); 230 } 231 232 @Override 233 public InstructionBuilder invokeVirtual(String className, String returnType, String methodName, 234 String... argumentTypes) 235 { 236 check(); 237 238 doInvoke(INVOKEVIRTUAL, className, returnType, methodName, false, argumentTypes); 239 240 return this; 241 } 242 243 @Override 244 public InstructionBuilder invokeInterface(String interfaceName, String returnType, String methodName, 245 String... argumentTypes) 246 { 247 check(); 248 249 doInvoke(INVOKEINTERFACE, interfaceName, returnType, methodName, true, argumentTypes); 250 251 return this; 252 } 253 254 private void doInvoke(int opcode, String className, String returnType, String methodName, boolean isInterface, 255 String... argumentTypes) 256 { 257 v.visitMethodInsn(opcode, cache.toInternalName(className), methodName, 258 cache.toMethodDescriptor(returnType, argumentTypes), isInterface); 259 } 260 261 @Override 262 public InstructionBuilder invokeStatic(Class clazz, Class returnType, String methodName, Class... argumentTypes) 263 { 264 doInvoke(INVOKESTATIC, clazz, returnType, methodName, false, argumentTypes); 265 266 return this; 267 } 268 269 private void doInvoke(int opcode, Class clazz, Class returnType, String methodName, boolean isInterface, 270 Class... argumentTypes) 271 { 272 doInvoke(opcode, clazz.getName(), cache.toTypeName(returnType), methodName, isInterface, 273 PlasticUtils.toTypeNames(argumentTypes)); 274 } 275 276 @Override 277 public InstructionBuilder invoke(Method method) 278 { 279 check(); 280 281 return invoke(method.getDeclaringClass(), method.getReturnType(), method.getName(), method.getParameterTypes()); 282 } 283 284 @Override 285 public InstructionBuilder invoke(Class clazz, Class returnType, String methodName, Class... argumentTypes) 286 { 287 check(); 288 289 doInvoke(clazz.isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL, clazz, returnType, methodName, 290 clazz.isInterface(), argumentTypes); 291 292 return this; 293 } 294 295 private void doInvoke(int opcode, String containingClassName, MethodDescription description, boolean isInterface) 296 { 297 v.visitMethodInsn(opcode, cache.toInternalName(containingClassName), description.methodName, 298 cache.toDesc(description), isInterface); 299 } 300 301 @Override 302 public InstructionBuilder returnResult() 303 { 304 check(); 305 306 PrimitiveType type = PrimitiveType.getByName(state.description.returnType); 307 308 int opcode = type == null ? ARETURN : type.returnOpcode; 309 310 v.visitInsn(opcode); 311 312 return this; 313 } 314 315 @Override 316 public InstructionBuilder boxPrimitive(String typeName) 317 { 318 check(); 319 320 PrimitiveType type = PrimitiveType.getByName(typeName); 321 322 if (type != null && type != PrimitiveType.VOID) 323 { 324 v.visitMethodInsn(INVOKESTATIC, type.wrapperInternalName, "valueOf", type.valueOfMethodDescriptor, 325 false); 326 } 327 328 return this; 329 } 330 331 @Override 332 public InstructionBuilder unboxPrimitive(String typeName) 333 { 334 check(); 335 336 PrimitiveType type = PrimitiveType.getByName(typeName); 337 338 if (type != null) 339 { 340 doUnbox(type); 341 } 342 343 return this; 344 } 345 346 private void doUnbox(PrimitiveType type) 347 { 348 v.visitMethodInsn(INVOKEVIRTUAL, type.wrapperInternalName, type.toValueMethodName, type.toValueMethodDescriptor, 349 false); 350 } 351 352 @Override 353 public InstructionBuilder getField(String className, String fieldName, String typeName) 354 { 355 check(); 356 357 v.visitFieldInsn(GETFIELD, cache.toInternalName(className), fieldName, cache.toDesc(typeName)); 358 359 return this; 360 } 361 362 @Override 363 public InstructionBuilder getStaticField(String className, String fieldName, String typeName) 364 { 365 check(); 366 367 v.visitFieldInsn(GETSTATIC, cache.toInternalName(className), fieldName, cache.toDesc(typeName)); 368 369 return this; 370 } 371 372 @Override 373 public InstructionBuilder getStaticField(String className, String fieldName, Class fieldType) 374 { 375 check(); 376 377 return getStaticField(className, fieldName, cache.toTypeName(fieldType)); 378 } 379 380 @Override 381 public InstructionBuilder putStaticField(String className, String fieldName, Class fieldType) 382 { 383 check(); 384 385 return putStaticField(className, fieldName, cache.toTypeName(fieldType)); 386 } 387 388 @Override 389 public InstructionBuilder putStaticField(String className, String fieldName, String typeName) 390 { 391 check(); 392 393 v.visitFieldInsn(PUTSTATIC, cache.toInternalName(className), fieldName, cache.toDesc(typeName)); 394 395 return this; 396 } 397 398 @Override 399 public InstructionBuilder getField(PlasticField field) 400 { 401 check(); 402 403 return getField(field.getPlasticClass().getClassName(), field.getName(), field.getTypeName()); 404 } 405 406 @Override 407 public InstructionBuilder putField(String className, String fieldName, String typeName) 408 { 409 check(); 410 411 v.visitFieldInsn(PUTFIELD, cache.toInternalName(className), fieldName, cache.toDesc(typeName)); 412 413 return this; 414 } 415 416 @Override 417 public InstructionBuilder putField(String className, String fieldName, Class fieldType) 418 { 419 check(); 420 421 return putField(className, fieldName, cache.toTypeName(fieldType)); 422 } 423 424 @Override 425 public InstructionBuilder getField(String className, String fieldName, Class fieldType) 426 { 427 check(); 428 429 return getField(className, fieldName, cache.toTypeName(fieldType)); 430 } 431 432 @Override 433 public InstructionBuilder loadArrayElement(int index, String elementType) 434 { 435 check(); 436 437 loadConstant(index); 438 439 PrimitiveType type = PrimitiveType.getByName(elementType); 440 441 if (type == null) 442 { 443 v.visitInsn(AALOAD); 444 } else 445 { 446 throw new RuntimeException("Access to non-object arrays is not yet supported."); 447 } 448 449 return this; 450 } 451 452 @Override 453 public InstructionBuilder loadArrayElement() 454 { 455 check(); 456 457 v.visitInsn(AALOAD); 458 459 return this; 460 } 461 462 @Override 463 public InstructionBuilder checkcast(String className) 464 { 465 check(); 466 467 // Found out the hard way that array names are handled differently; you cast to the descriptor, not the internal 468 // name. 469 470 String internalName = className.contains("[") ? cache.toDesc(className) : cache.toInternalName(className); 471 472 v.visitTypeInsn(CHECKCAST, internalName); 473 474 return this; 475 } 476 477 @Override 478 public InstructionBuilder checkcast(Class clazz) 479 { 480 check(); 481 482 return checkcast(cache.toTypeName(clazz)); 483 } 484 485 @Override 486 public InstructionBuilder instanceOf(String className) 487 { 488 check(); 489 490 // Found out the hard way that array names are handled differently; you cast to the descriptor, not the internal 491 // name. 492 493 String internalName = className.contains("[") ? cache.toDesc(className) : cache.toInternalName(className); 494 495 v.visitTypeInsn(INSTANCEOF, internalName); 496 497 return this; 498 } 499 500 @Override 501 public InstructionBuilder instanceOf(Class clazz) 502 { 503 check(); 504 505 return instanceOf(cache.toTypeName(clazz)); 506 } 507 508 @Override 509 public InstructionBuilder startTryCatch(TryCatchCallback callback) 510 { 511 check(); 512 513 new TryCatchBlockImpl(this, state).doCallback(callback); 514 515 return this; 516 } 517 518 @Override 519 public InstructionBuilder newInstance(String className) 520 { 521 check(); 522 523 v.visitTypeInsn(NEW, cache.toInternalName(className)); 524 525 return this; 526 } 527 528 @Override 529 public InstructionBuilder newInstance(Class clazz) 530 { 531 check(); 532 533 return newInstance(clazz.getName()); 534 } 535 536 @Override 537 public InstructionBuilder invokeConstructor(String className, String... argumentTypes) 538 { 539 check(); 540 541 doInvoke(INVOKESPECIAL, className, "void", "<init>", false, argumentTypes); 542 543 return this; 544 } 545 546 @Override 547 public InstructionBuilder invokeConstructor(Class clazz, Class... argumentTypes) 548 { 549 check(); 550 551 return invokeConstructor(clazz.getName(), PlasticUtils.toTypeNames(argumentTypes)); 552 } 553 554 @Override 555 public InstructionBuilder dupe(int depth) 556 { 557 check(); 558 559 if (depth < 0 || depth >= DUPE_OPCODES.length) 560 throw new IllegalArgumentException(String.format( 561 "Dupe depth %d is invalid; values from 0 to %d are allowed.", depth, DUPE_OPCODES.length - 1)); 562 563 v.visitInsn(DUPE_OPCODES[depth]); 564 565 return this; 566 } 567 568 @Override 569 public InstructionBuilder dupe() 570 { 571 check(); 572 573 v.visitInsn(DUP); 574 575 return this; 576 } 577 578 @Override 579 public InstructionBuilder pop() 580 { 581 check(); 582 583 v.visitInsn(POP); 584 585 return this; 586 } 587 588 @Override 589 public InstructionBuilder swap() 590 { 591 check(); 592 593 v.visitInsn(SWAP); 594 595 return this; 596 } 597 598 @Override 599 public InstructionBuilder loadConstant(Object constant) 600 { 601 check(); 602 603 Integer opcode = constantOpcodes.get(constant); 604 605 if (opcode != null) 606 v.visitInsn(opcode); 607 else 608 v.visitLdcInsn(constant); 609 610 return this; 611 } 612 613 @Override 614 public InstructionBuilder loadTypeConstant(String typeName) 615 { 616 check(); 617 618 Type type = Type.getType(cache.toDesc(typeName)); 619 620 v.visitLdcInsn(type); 621 622 return this; 623 } 624 625 @Override 626 public InstructionBuilder loadTypeConstant(Class clazz) 627 { 628 check(); 629 630 Type type = Type.getType(clazz); 631 632 v.visitLdcInsn(type); 633 634 return this; 635 } 636 637 @Override 638 public InstructionBuilder castOrUnbox(String typeName) 639 { 640 check(); 641 642 PrimitiveType type = PrimitiveType.getByName(typeName); 643 644 if (type == null) 645 return checkcast(typeName); 646 647 v.visitTypeInsn(CHECKCAST, type.wrapperInternalName); 648 doUnbox(type); 649 650 return this; 651 } 652 653 @Override 654 public InstructionBuilder throwException(String className, String message) 655 { 656 check(); 657 658 newInstance(className).dupe().loadConstant(message); 659 660 invokeConstructor(className, "java.lang.String"); 661 662 v.visitInsn(ATHROW); 663 664 return this; 665 } 666 667 @Override 668 public InstructionBuilder throwException(Class<? extends Throwable> exceptionType, String message) 669 { 670 check(); 671 672 return throwException(cache.toTypeName(exceptionType), message); 673 } 674 675 @Override 676 public InstructionBuilder throwException() 677 { 678 check(); 679 680 v.visitInsn(ATHROW); 681 682 return this; 683 } 684 685 @Override 686 public InstructionBuilder startSwitch(int min, int max, SwitchCallback callback) 687 { 688 check(); 689 690 assert callback != null; 691 692 new SwitchBlockImpl(this, state, min, max).doCallback(callback); 693 694 return this; 695 } 696 697 @Override 698 public InstructionBuilder startVariable(String type, final LocalVariableCallback callback) 699 { 700 check(); 701 702 final LocalVariable var = state.startVariable(type); 703 704 callback.doBuild(var, this); 705 706 state.stopVariable(var); 707 708 return this; 709 } 710 711 @Override 712 public InstructionBuilder storeVariable(LocalVariable var) 713 { 714 check(); 715 716 state.store(var); 717 718 return this; 719 } 720 721 @Override 722 public InstructionBuilder loadVariable(LocalVariable var) 723 { 724 check(); 725 726 state.load(var); 727 728 return this; 729 } 730 731 @Override 732 public InstructionBuilder when(Condition condition, final InstructionBuilderCallback ifTrue) 733 { 734 check(); 735 736 assert ifTrue != null; 737 738 // This is nice for code coverage but could be more efficient, possibly generate 739 // more efficient bytecode, if it talked to the v directly. 740 741 return when(condition, new WhenCallback() 742 { 743 @Override 744 public void ifTrue(InstructionBuilder builder) 745 { 746 ifTrue.doBuild(builder); 747 } 748 749 @Override 750 public void ifFalse(InstructionBuilder builder) 751 { 752 } 753 }); 754 } 755 756 @Override 757 public InstructionBuilder when(Condition condition, final WhenCallback callback) 758 { 759 check(); 760 761 assert condition != null; 762 assert callback != null; 763 764 Label ifFalseLabel = new Label(); 765 Label endIfLabel = new Label(); 766 767 v.visitJumpInsn(conditionToOpcode.get(condition), ifFalseLabel); 768 769 callback.ifTrue(this); 770 771 v.visitJumpInsn(GOTO, endIfLabel); 772 773 v.visitLabel(ifFalseLabel); 774 775 callback.ifFalse(this); 776 777 v.visitLabel(endIfLabel); 778 779 return this; 780 } 781 782 @Override 783 public InstructionBuilder doWhile(Condition condition, final WhileCallback callback) 784 { 785 check(); 786 787 assert condition != null; 788 assert callback != null; 789 790 Label doCheck = state.newLabel(); 791 792 Label exitLoop = new Label(); 793 794 callback.buildTest(this); 795 796 v.visitJumpInsn(conditionToOpcode.get(condition), exitLoop); 797 798 callback.buildBody(this); 799 800 v.visitJumpInsn(GOTO, doCheck); 801 802 v.visitLabel(exitLoop); 803 804 return this; 805 } 806 807 @Override 808 public InstructionBuilder increment(LocalVariable variable) 809 { 810 check(); 811 812 LVInfo info = state.locals.get(variable); 813 814 v.visitIincInsn(info.offset, 1); 815 816 return this; 817 } 818 819 @Override 820 public InstructionBuilder arrayLength() 821 { 822 check(); 823 824 v.visitInsn(ARRAYLENGTH); 825 826 return this; 827 } 828 829 @Override 830 public InstructionBuilder iterateArray(final InstructionBuilderCallback callback) 831 { 832 startVariable("int", new LocalVariableCallback() 833 { 834 @Override 835 public void doBuild(final LocalVariable indexVariable, InstructionBuilder builder) 836 { 837 builder.loadConstant(0).storeVariable(indexVariable); 838 839 builder.doWhile(Condition.LESS_THAN, new WhileCallback() 840 { 841 @Override 842 public void buildTest(InstructionBuilder builder) 843 { 844 builder.dupe().arrayLength(); 845 builder.loadVariable(indexVariable).swap(); 846 } 847 848 @Override 849 public void buildBody(InstructionBuilder builder) 850 { 851 builder.dupe().loadVariable(indexVariable).loadArrayElement(); 852 853 callback.doBuild(builder); 854 855 builder.increment(indexVariable); 856 } 857 }); 858 } 859 }); 860 861 return this; 862 } 863 864 @Override 865 public InstructionBuilder dupeWide() 866 { 867 check(); 868 869 v.visitInsn(DUP2); 870 871 return this; 872 } 873 874 @Override 875 public InstructionBuilder popWide() 876 { 877 check(); 878 879 v.visitInsn(POP2); 880 881 return this; 882 } 883 884 @Override 885 public InstructionBuilder compareSpecial(String typeName) 886 { 887 check(); 888 889 Integer opcode = typeToSpecialComparisonOpcode.get(typeName); 890 891 if (opcode == null) 892 throw new IllegalArgumentException(String.format("Not a special primitive type: '%s'.", typeName)); 893 894 v.visitInsn(opcode); 895 896 return this; 897 } 898 899 void doCallback(InstructionBuilderCallback callback) 900 { 901 check(); 902 903 if (callback != null) 904 callback.doBuild(this); 905 906 lock(); 907 } 908}