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<>(); 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<>(); 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<>(); 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, false); 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, false, 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, true, argumentTypes); 238 239 return this; 240 } 241 242 private void doInvoke(int opcode, String className, String returnType, String methodName, boolean isInterface, 243 String... argumentTypes) 244 { 245 v.visitMethodInsn(opcode, cache.toInternalName(className), methodName, 246 cache.toMethodDescriptor(returnType, argumentTypes), isInterface); 247 } 248 249 @Override 250 public InstructionBuilder invokeStatic(Class clazz, Class returnType, String methodName, Class... argumentTypes) 251 { 252 doInvoke(INVOKESTATIC, clazz, returnType, methodName, false, argumentTypes); 253 254 return this; 255 } 256 257 private void doInvoke(int opcode, Class clazz, Class returnType, String methodName, boolean isInterface, 258 Class... argumentTypes) 259 { 260 doInvoke(opcode, clazz.getName(), cache.toTypeName(returnType), methodName, isInterface, 261 PlasticUtils.toTypeNames(argumentTypes)); 262 } 263 264 @Override 265 public InstructionBuilder invoke(Method method) 266 { 267 check(); 268 269 return invoke(method.getDeclaringClass(), method.getReturnType(), method.getName(), method.getParameterTypes()); 270 } 271 272 @Override 273 public InstructionBuilder invoke(Class clazz, Class returnType, String methodName, Class... argumentTypes) 274 { 275 check(); 276 277 doInvoke(clazz.isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL, clazz, returnType, methodName, 278 clazz.isInterface(), argumentTypes); 279 280 return this; 281 } 282 283 private void doInvoke(int opcode, String containingClassName, MethodDescription description, boolean isInterface) 284 { 285 v.visitMethodInsn(opcode, cache.toInternalName(containingClassName), description.methodName, 286 cache.toDesc(description), isInterface); 287 } 288 289 @Override 290 public InstructionBuilder returnResult() 291 { 292 check(); 293 294 PrimitiveType type = PrimitiveType.getByName(state.description.returnType); 295 296 int opcode = type == null ? ARETURN : type.returnOpcode; 297 298 v.visitInsn(opcode); 299 300 return this; 301 } 302 303 @Override 304 public InstructionBuilder boxPrimitive(String typeName) 305 { 306 check(); 307 308 PrimitiveType type = PrimitiveType.getByName(typeName); 309 310 if (type != null && type != PrimitiveType.VOID) 311 { 312 v.visitMethodInsn(INVOKESTATIC, type.wrapperInternalName, "valueOf", type.valueOfMethodDescriptor, 313 false); 314 } 315 316 return this; 317 } 318 319 @Override 320 public InstructionBuilder unboxPrimitive(String typeName) 321 { 322 check(); 323 324 PrimitiveType type = PrimitiveType.getByName(typeName); 325 326 if (type != null) 327 { 328 doUnbox(type); 329 } 330 331 return this; 332 } 333 334 private void doUnbox(PrimitiveType type) 335 { 336 v.visitMethodInsn(INVOKEVIRTUAL, type.wrapperInternalName, type.toValueMethodName, type.toValueMethodDescriptor, 337 false); 338 } 339 340 @Override 341 public InstructionBuilder getField(String className, String fieldName, String typeName) 342 { 343 check(); 344 345 v.visitFieldInsn(GETFIELD, cache.toInternalName(className), fieldName, cache.toDesc(typeName)); 346 347 return this; 348 } 349 350 @Override 351 public InstructionBuilder getStaticField(String className, String fieldName, String typeName) 352 { 353 check(); 354 355 v.visitFieldInsn(GETSTATIC, cache.toInternalName(className), fieldName, cache.toDesc(typeName)); 356 357 return this; 358 } 359 360 @Override 361 public InstructionBuilder getStaticField(String className, String fieldName, Class fieldType) 362 { 363 check(); 364 365 return getStaticField(className, fieldName, cache.toTypeName(fieldType)); 366 } 367 368 @Override 369 public InstructionBuilder putStaticField(String className, String fieldName, Class fieldType) 370 { 371 check(); 372 373 return putStaticField(className, fieldName, cache.toTypeName(fieldType)); 374 } 375 376 @Override 377 public InstructionBuilder putStaticField(String className, String fieldName, String typeName) 378 { 379 check(); 380 381 v.visitFieldInsn(PUTSTATIC, cache.toInternalName(className), fieldName, cache.toDesc(typeName)); 382 383 return this; 384 } 385 386 @Override 387 public InstructionBuilder getField(PlasticField field) 388 { 389 check(); 390 391 return getField(field.getPlasticClass().getClassName(), field.getName(), field.getTypeName()); 392 } 393 394 @Override 395 public InstructionBuilder putField(String className, String fieldName, String typeName) 396 { 397 check(); 398 399 v.visitFieldInsn(PUTFIELD, cache.toInternalName(className), fieldName, cache.toDesc(typeName)); 400 401 return this; 402 } 403 404 @Override 405 public InstructionBuilder putField(String className, String fieldName, Class fieldType) 406 { 407 check(); 408 409 return putField(className, fieldName, cache.toTypeName(fieldType)); 410 } 411 412 @Override 413 public InstructionBuilder getField(String className, String fieldName, Class fieldType) 414 { 415 check(); 416 417 return getField(className, fieldName, cache.toTypeName(fieldType)); 418 } 419 420 @Override 421 public InstructionBuilder loadArrayElement(int index, String elementType) 422 { 423 check(); 424 425 loadConstant(index); 426 427 PrimitiveType type = PrimitiveType.getByName(elementType); 428 429 if (type == null) 430 { 431 v.visitInsn(AALOAD); 432 } else 433 { 434 throw new RuntimeException("Access to non-object arrays is not yet supported."); 435 } 436 437 return this; 438 } 439 440 @Override 441 public InstructionBuilder loadArrayElement() 442 { 443 check(); 444 445 v.visitInsn(AALOAD); 446 447 return this; 448 } 449 450 @Override 451 public InstructionBuilder checkcast(String className) 452 { 453 check(); 454 455 // Found out the hard way that array names are handled differently; you cast to the descriptor, not the internal 456 // name. 457 458 String internalName = className.contains("[") ? cache.toDesc(className) : cache.toInternalName(className); 459 460 v.visitTypeInsn(CHECKCAST, internalName); 461 462 return this; 463 } 464 465 @Override 466 public InstructionBuilder checkcast(Class clazz) 467 { 468 check(); 469 470 return checkcast(cache.toTypeName(clazz)); 471 } 472 473 @Override 474 public InstructionBuilder startTryCatch(TryCatchCallback callback) 475 { 476 check(); 477 478 new TryCatchBlockImpl(this, state).doCallback(callback); 479 480 return this; 481 } 482 483 @Override 484 public InstructionBuilder newInstance(String className) 485 { 486 check(); 487 488 v.visitTypeInsn(NEW, cache.toInternalName(className)); 489 490 return this; 491 } 492 493 @Override 494 public InstructionBuilder newInstance(Class clazz) 495 { 496 check(); 497 498 return newInstance(clazz.getName()); 499 } 500 501 @Override 502 public InstructionBuilder invokeConstructor(String className, String... argumentTypes) 503 { 504 check(); 505 506 doInvoke(INVOKESPECIAL, className, "void", "<init>", false, argumentTypes); 507 508 return this; 509 } 510 511 @Override 512 public InstructionBuilder invokeConstructor(Class clazz, Class... argumentTypes) 513 { 514 check(); 515 516 return invokeConstructor(clazz.getName(), PlasticUtils.toTypeNames(argumentTypes)); 517 } 518 519 @Override 520 public InstructionBuilder dupe(int depth) 521 { 522 check(); 523 524 if (depth < 0 || depth >= DUPE_OPCODES.length) 525 throw new IllegalArgumentException(String.format( 526 "Dupe depth %d is invalid; values from 0 to %d are allowed.", depth, DUPE_OPCODES.length - 1)); 527 528 v.visitInsn(DUPE_OPCODES[depth]); 529 530 return this; 531 } 532 533 @Override 534 public InstructionBuilder dupe() 535 { 536 check(); 537 538 v.visitInsn(DUP); 539 540 return this; 541 } 542 543 @Override 544 public InstructionBuilder pop() 545 { 546 check(); 547 548 v.visitInsn(POP); 549 550 return this; 551 } 552 553 @Override 554 public InstructionBuilder swap() 555 { 556 check(); 557 558 v.visitInsn(SWAP); 559 560 return this; 561 } 562 563 @Override 564 public InstructionBuilder loadConstant(Object constant) 565 { 566 check(); 567 568 Integer opcode = constantOpcodes.get(constant); 569 570 if (opcode != null) 571 v.visitInsn(opcode); 572 else 573 v.visitLdcInsn(constant); 574 575 return this; 576 } 577 578 @Override 579 public InstructionBuilder loadTypeConstant(String typeName) 580 { 581 check(); 582 583 Type type = Type.getType(cache.toDesc(typeName)); 584 585 v.visitLdcInsn(type); 586 587 return this; 588 } 589 590 @Override 591 public InstructionBuilder loadTypeConstant(Class clazz) 592 { 593 check(); 594 595 Type type = Type.getType(clazz); 596 597 v.visitLdcInsn(type); 598 599 return this; 600 } 601 602 @Override 603 public InstructionBuilder castOrUnbox(String typeName) 604 { 605 check(); 606 607 PrimitiveType type = PrimitiveType.getByName(typeName); 608 609 if (type == null) 610 return checkcast(typeName); 611 612 v.visitTypeInsn(CHECKCAST, type.wrapperInternalName); 613 doUnbox(type); 614 615 return this; 616 } 617 618 @Override 619 public InstructionBuilder throwException(String className, String message) 620 { 621 check(); 622 623 newInstance(className).dupe().loadConstant(message); 624 625 invokeConstructor(className, "java.lang.String"); 626 627 v.visitInsn(ATHROW); 628 629 return this; 630 } 631 632 @Override 633 public InstructionBuilder throwException(Class<? extends Throwable> exceptionType, String message) 634 { 635 check(); 636 637 return throwException(cache.toTypeName(exceptionType), message); 638 } 639 640 @Override 641 public InstructionBuilder throwException() 642 { 643 check(); 644 645 v.visitInsn(ATHROW); 646 647 return this; 648 } 649 650 @Override 651 public InstructionBuilder startSwitch(int min, int max, SwitchCallback callback) 652 { 653 check(); 654 655 assert callback != null; 656 657 new SwitchBlockImpl(this, state, min, max).doCallback(callback); 658 659 return this; 660 } 661 662 @Override 663 public InstructionBuilder startVariable(String type, final LocalVariableCallback callback) 664 { 665 check(); 666 667 final LocalVariable var = state.startVariable(type); 668 669 callback.doBuild(var, this); 670 671 state.stopVariable(var); 672 673 return this; 674 } 675 676 @Override 677 public InstructionBuilder storeVariable(LocalVariable var) 678 { 679 check(); 680 681 state.store(var); 682 683 return this; 684 } 685 686 @Override 687 public InstructionBuilder loadVariable(LocalVariable var) 688 { 689 check(); 690 691 state.load(var); 692 693 return this; 694 } 695 696 @Override 697 public InstructionBuilder when(Condition condition, final InstructionBuilderCallback ifTrue) 698 { 699 check(); 700 701 assert ifTrue != null; 702 703 // This is nice for code coverage but could be more efficient, possibly generate 704 // more efficient bytecode, if it talked to the v directly. 705 706 return when(condition, new WhenCallback() 707 { 708 @Override 709 public void ifTrue(InstructionBuilder builder) 710 { 711 ifTrue.doBuild(builder); 712 } 713 714 @Override 715 public void ifFalse(InstructionBuilder builder) 716 { 717 } 718 }); 719 } 720 721 @Override 722 public InstructionBuilder when(Condition condition, final WhenCallback callback) 723 { 724 check(); 725 726 assert condition != null; 727 assert callback != null; 728 729 Label ifFalseLabel = new Label(); 730 Label endIfLabel = new Label(); 731 732 v.visitJumpInsn(conditionToOpcode.get(condition), ifFalseLabel); 733 734 callback.ifTrue(this); 735 736 v.visitJumpInsn(GOTO, endIfLabel); 737 738 v.visitLabel(ifFalseLabel); 739 740 callback.ifFalse(this); 741 742 v.visitLabel(endIfLabel); 743 744 return this; 745 } 746 747 @Override 748 public InstructionBuilder doWhile(Condition condition, final WhileCallback callback) 749 { 750 check(); 751 752 assert condition != null; 753 assert callback != null; 754 755 Label doCheck = state.newLabel(); 756 757 Label exitLoop = new Label(); 758 759 callback.buildTest(this); 760 761 v.visitJumpInsn(conditionToOpcode.get(condition), exitLoop); 762 763 callback.buildBody(this); 764 765 v.visitJumpInsn(GOTO, doCheck); 766 767 v.visitLabel(exitLoop); 768 769 return this; 770 } 771 772 @Override 773 public InstructionBuilder increment(LocalVariable variable) 774 { 775 check(); 776 777 LVInfo info = state.locals.get(variable); 778 779 v.visitIincInsn(info.offset, 1); 780 781 return this; 782 } 783 784 @Override 785 public InstructionBuilder arrayLength() 786 { 787 check(); 788 789 v.visitInsn(ARRAYLENGTH); 790 791 return this; 792 } 793 794 @Override 795 public InstructionBuilder iterateArray(final InstructionBuilderCallback callback) 796 { 797 startVariable("int", new LocalVariableCallback() 798 { 799 @Override 800 public void doBuild(final LocalVariable indexVariable, InstructionBuilder builder) 801 { 802 builder.loadConstant(0).storeVariable(indexVariable); 803 804 builder.doWhile(Condition.LESS_THAN, new WhileCallback() 805 { 806 @Override 807 public void buildTest(InstructionBuilder builder) 808 { 809 builder.dupe().arrayLength(); 810 builder.loadVariable(indexVariable).swap(); 811 } 812 813 @Override 814 public void buildBody(InstructionBuilder builder) 815 { 816 builder.dupe().loadVariable(indexVariable).loadArrayElement(); 817 818 callback.doBuild(builder); 819 820 builder.increment(indexVariable); 821 } 822 }); 823 } 824 }); 825 826 return this; 827 } 828 829 @Override 830 public InstructionBuilder dupeWide() 831 { 832 check(); 833 834 v.visitInsn(DUP2); 835 836 return this; 837 } 838 839 @Override 840 public InstructionBuilder popWide() 841 { 842 check(); 843 844 v.visitInsn(POP2); 845 846 return this; 847 } 848 849 @Override 850 public InstructionBuilder compareSpecial(String typeName) 851 { 852 check(); 853 854 Integer opcode = typeToSpecialComparisonOpcode.get(typeName); 855 856 if (opcode == null) 857 throw new IllegalArgumentException(String.format("Not a special primitive type: '%s'.", typeName)); 858 859 v.visitInsn(opcode); 860 861 return this; 862 } 863 864 void doCallback(InstructionBuilderCallback callback) 865 { 866 check(); 867 868 if (callback != null) 869 callback.doBuild(this); 870 871 lock(); 872 } 873}