    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}