001    package ca.discotek.feenix.asm;
002    
003    import java.io.File;
004    import java.io.FileInputStream;
005    import java.io.IOException;
006    import java.util.ArrayList;
007    import java.util.Iterator;
008    import java.util.List;
009    
010    import ca.discotek.feenix.ClassManager;
011    import ca.discotek.feenix.Util;
012    import ca.discotek.rebundled.org.objectweb.asm.ClassReader;
013    import ca.discotek.rebundled.org.objectweb.asm.ClassVisitor;
014    import ca.discotek.rebundled.org.objectweb.asm.ClassWriter;
015    import ca.discotek.rebundled.org.objectweb.asm.FieldVisitor;
016    import ca.discotek.rebundled.org.objectweb.asm.Label;
017    import ca.discotek.rebundled.org.objectweb.asm.MethodVisitor;
018    import ca.discotek.rebundled.org.objectweb.asm.Opcodes;
019    import ca.discotek.rebundled.org.objectweb.asm.Type;
020    import ca.discotek.rebundled.org.objectweb.asm.tree.AbstractInsnNode;
021    import ca.discotek.rebundled.org.objectweb.asm.tree.MethodInsnNode;
022    import ca.discotek.rebundled.org.objectweb.asm.tree.MethodNode;
023    import ca.discotek.rebundled.org.objectweb.asm.tree.VarInsnNode;
024    
025    public class ImplementationGenerator extends ClassVisitor {
026    
027        public static final String CONSTRUCTOR_METHOD_NEW_NAME = "__init__";
028        public static final String STATIC_INITIALIZER_METHOD_NEW_NAME = "__clinit__";
029    
030        String className;
031        String invokerClassName;
032        String superName;
033        
034        List<MethodDescriptor> methodList = new ArrayList<MethodDescriptor>();
035        
036        public ImplementationGenerator(String className, ClassVisitor cv) {
037            super(Opcodes.ASM5, cv);
038            this.className = className;
039        }
040        
041        public static byte[] generate(String className, File file) {
042            FileInputStream fis = null;
043            try {
044                ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
045                ImplementationGenerator cv = new ImplementationGenerator(className, cw);
046                fis = new FileInputStream(file);
047                ClassReader cr = new ClassReader(fis);
048                cr.accept(cv, ClassReader.SKIP_FRAMES);
049                
050                return cw.toByteArray();
051            } 
052            catch (Exception e) {
053                // log
054                e.printStackTrace();
055                return null;
056            }
057            finally {
058                if (fis != null) {
059                    try { fis.close(); } 
060                    catch (IOException e) {
061                        // no other option - log
062                        e.printStackTrace();
063                    }
064                }
065            }
066        }
067        
068        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
069            this.superName = superName;
070            this.invokerClassName = name;
071            String interfaceName = ClassManager.getInterfaceName(name);
072            
073            int modifiedAccess = Util.convertToPublicAccess(access);
074            super.visit(version, modifiedAccess, className, signature, "java/lang/Object", new String[]{interfaceName});
075            
076            MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
077            mv.visitCode();
078            mv.visitVarInsn(Opcodes.ALOAD, 0);
079            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
080            mv.visitInsn(Opcodes.RETURN);
081            mv.visitMaxs(0, 0);
082            mv.visitEnd();
083        }
084    
085        public FieldVisitor visitField(int access,
086                String name,
087                String desc,
088                String signature,
089                Object value) {
090            return null;
091        }
092        
093        List<MethodNode> constructorMethodNodeList = new ArrayList<MethodNode>();
094        boolean endVisited = false;
095        
096        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
097            if (endVisited)
098                return super.visitMethod(access, name, desc, signature, exceptions);
099            else {
100                String modifiedDesc = desc;
101    
102                if (name.equals("<init>")) { // constructors
103                    modifiedDesc = addInokerTypeToDesc(invokerClassName, desc);
104                    int modifiedAccess = Util.convertToPublicAccess(access);
105                    methodList.add(new MethodDescriptor(modifiedAccess, CONSTRUCTOR_METHOD_NEW_NAME, modifiedDesc, signature, exceptions));
106                   
107                    MethodNode node = new ConstructorMethodNode(modifiedAccess, CONSTRUCTOR_METHOD_NEW_NAME, modifiedDesc, signature, exceptions);
108                    constructorMethodNodeList.add(node);
109                    return node;
110                }
111                else if (name.equals("<clinit>")) { // static initializer
112                    methodList.add(new MethodDescriptor(access, STATIC_INITIALIZER_METHOD_NEW_NAME, desc, signature, exceptions));
113                    return super.visitMethod(access, STATIC_INITIALIZER_METHOD_NEW_NAME, desc, signature, exceptions);
114                }
115                else { // all other methods
116                    boolean isStatic = Util.isEnabled(access, Opcodes.ACC_STATIC);
117                    if (!isStatic) 
118                        modifiedDesc = addInokerTypeToDesc(invokerClassName, desc);
119                    methodList.add(new MethodDescriptor(access, name, modifiedDesc, signature, exceptions));
120                    return new ImplementationMethodVisitor(super.visitMethod(access, name, modifiedDesc, signature, exceptions), isStatic);
121                }            
122            }
123        }
124        
125        static String addInokerTypeToDesc(String type, String desc) {
126            Type methodType = Type.getType(desc);
127            Type argTypes[] = methodType.getArgumentTypes();
128            Type newArgTypes[] = new Type[argTypes.length+1];
129            newArgTypes[0] = Type.getType("L" + type + ";");
130            for (int i=0; i<argTypes.length; i++)
131                newArgTypes[i+1] = argTypes[i];
132            
133            StringBuffer buffer = new StringBuffer();
134            buffer.append('(');
135            for (int i=0; i<newArgTypes.length; i++)
136                buffer.append(newArgTypes[i].getDescriptor());
137            buffer.append(')');
138            buffer.append(methodType.getReturnType().getDescriptor());
139            return buffer.toString();
140        }
141        
142        class ConstructorMethodNode extends MethodNode {
143            
144            public ConstructorMethodNode(int access, String name, String desc, String signature, String exceptions[]) {
145                super(Opcodes.ASM5, access, name, desc, signature, exceptions);
146            }
147            
148            public void visitVarInsn(int opcode, int var) {
149                super.visitVarInsn(opcode, var + 1); // +1 changes the variable from "this" to the first parameter.
150            }
151        }
152        
153        class ImplementationMethodVisitor extends MethodVisitor {
154            final boolean isStatic;
155            
156            public ImplementationMethodVisitor(MethodVisitor mv, boolean isStatic) {
157                super(Opcodes.ASM5, mv);
158                this.isStatic = isStatic;
159            }
160            
161            public void visitVarInsn(int opcode, int var) {
162                super.visitVarInsn(opcode, var + (isStatic ? 0 : 1)); // +1 changes the variable from "this" to the first parameter.
163            }
164        }
165        
166    
167        public void visitEnd() {
168            // Need this variable to let the visitMethod(...) method above know that it is being invoked via the nodes[i].accept(this)
169            // call below and it has already processed the method.
170            endVisited = true; 
171            
172            // The following code is strips out the constructors' call to initialize the super class instance. In the implementation class
173            // the constructor will be a regular method where you can't call instance constructors.
174            AbstractInsnNode lastInstruction;
175            AbstractInsnNode instruction;
176            boolean found;
177            Iterator<AbstractInsnNode> instructionIterator;
178            
179            MethodInsnNode methodInstruction = null;
180            VarInsnNode varInstruction = null;
181            
182            MethodNode nodes[] = constructorMethodNodeList.toArray(new MethodNode[constructorMethodNodeList.size()]);
183            for (int i=0; i<nodes.length; i++) {
184                instructionIterator = nodes[i].instructions.iterator();
185                lastInstruction = null;
186                found = false;
187                while (instructionIterator.hasNext()) {
188                    instruction = instructionIterator.next();
189                    if (lastInstruction != null)  {
190                        if (instruction instanceof MethodInsnNode) {
191                            methodInstruction = (MethodInsnNode) instruction;
192                            if (methodInstruction.getOpcode() == Opcodes.INVOKESPECIAL && methodInstruction.name.equals("<init>")) {
193                                if (lastInstruction instanceof VarInsnNode) {
194                                    varInstruction = (VarInsnNode) lastInstruction;
195                                    if (varInstruction.var == 0) {
196                                        found = true;
197                                        break;
198                                    }
199                                }
200                            }
201                        }
202                    }
203                    lastInstruction = instruction;
204                }
205                if (found) {
206                    nodes[i].instructions.remove(methodInstruction);
207                    nodes[i].instructions.remove(varInstruction);
208                }
209                
210                nodes[i].accept(this);
211            }
212            ///////////////////////////////
213            
214            
215            // The rest of the code below is used to generate the invoke method in the implementation class.
216            MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, InterfaceGenerator.INVOKE_METHOD_NAME, "(IL" + invokerClassName + ";[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
217            mv.visitCode();
218    
219            MethodDescriptor methodDescriptor;
220            Type methodType;
221            Type argTypes[];
222            Type returnType;
223            Label label;
224            int methodCount = methodList.size();
225            boolean isStatic;
226            for (int i=0; i<methodCount; i++) {
227                label = new Label();
228                methodDescriptor = methodList.get(i);
229                isStatic = Util.isEnabled(methodDescriptor.access, Opcodes.ACC_STATIC);
230    
231                mv.visitVarInsn(Opcodes.ILOAD, 1);
232                mv.visitLdcInsn(i);
233                mv.visitJumpInsn(Opcodes.IF_ICMPNE, label);
234                
235                methodType = Type.getMethodType(methodDescriptor.desc);
236                argTypes = methodType.getArgumentTypes();
237                returnType = methodType.getReturnType();
238                
239                if (!isStatic) {
240                    mv.visitVarInsn(Opcodes.ALOAD, 0);
241                    mv.visitVarInsn(Opcodes.ALOAD, 2);
242                }
243    
244                /*
245                 * We modified the parameter types. We added the invoker type, but that is a separate
246                 * parameter in the second local var slot, not a variable in the parameter array.
247                 */
248                for (int j=0; j<argTypes.length; j++) {
249                    if (!isStatic) {
250                        if (j == 0) continue;
251                    }
252                    mv.visitVarInsn(Opcodes.ALOAD, 3);
253                    mv.visitLdcInsn(j + (!isStatic ? -1 : 0));
254                    mv.visitInsn(Opcodes.AALOAD);
255                    Util.insertCheckCast(argTypes[j], mv);
256                }
257                
258                mv.visitMethodInsn(isStatic ? Opcodes.INVOKESTATIC : Opcodes.INVOKEVIRTUAL, className, methodDescriptor.name, methodDescriptor.desc, false);
259                
260                if (returnType.getSort() == Type.VOID)
261                    mv.visitInsn(Opcodes.ACONST_NULL);
262    
263                Util.boxPrimitive(mv, returnType);
264                mv.visitInsn(Opcodes.ARETURN);
265                
266                mv.visitLabel(label);
267            }
268            
269            mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalArgumentException");
270            mv.visitInsn(Opcodes.DUP);
271            mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
272            mv.visitInsn(Opcodes.DUP);
273            mv.visitLdcInsn("Unknown method index: ");
274            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false);
275            mv.visitVarInsn(Opcodes.ILOAD, 1);
276            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false);
277            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
278            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V", false);
279            mv.visitInsn(Opcodes.ATHROW);
280            
281            mv.visitMaxs(0,  0);
282            mv.visitEnd();
283            
284            
285            super.visitEnd();
286        }
287    }