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}