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}