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