    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}