    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
    015    package org.apache.tapestry5.internal.plastic;
    016
    017    import org.apache.tapestry5.internal.plastic.InstructionBuilderState.LVInfo;
    018    import org.apache.tapestry5.internal.plastic.asm.Label;
    019    import org.apache.tapestry5.internal.plastic.asm.MethodVisitor;
    020    import org.apache.tapestry5.internal.plastic.asm.Opcodes;
    021    import org.apache.tapestry5.internal.plastic.asm.Type;
    022    import org.apache.tapestry5.plastic.*;
    023
    024    import java.lang.reflect.Method;
    025    import java.util.HashMap;
    026    import java.util.Map;
    027
    028    @SuppressWarnings("rawtypes")
    029    public 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    }