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    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}