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 }