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}