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