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    }