View Javadoc

1   /*
2    * Copyright 2010-2011 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.callbackparams.support;
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Map.Entry;
25  import org.apache.bcel.Constants;
26  import org.apache.bcel.classfile.Attribute;
27  import org.apache.bcel.classfile.Code;
28  import org.apache.bcel.classfile.Method;
29  import org.apache.bcel.classfile.Unknown;
30  import org.apache.bcel.generic.ALOAD;
31  import org.apache.bcel.generic.ClassGen;
32  import org.apache.bcel.generic.ConstantPoolGen;
33  import org.apache.bcel.generic.DUP;
34  import org.apache.bcel.generic.DUP2;
35  import org.apache.bcel.generic.DUP2_X1;
36  import org.apache.bcel.generic.DUP2_X2;
37  import org.apache.bcel.generic.DUP_X1;
38  import org.apache.bcel.generic.DUP_X2;
39  import org.apache.bcel.generic.EmptyVisitor;
40  import org.apache.bcel.generic.INVOKESPECIAL;
41  import org.apache.bcel.generic.Instruction;
42  import org.apache.bcel.generic.InstructionConstants;
43  import org.apache.bcel.generic.InstructionFactory;
44  import org.apache.bcel.generic.InstructionHandle;
45  import org.apache.bcel.generic.InstructionList;
46  import org.apache.bcel.generic.MethodGen;
47  import org.apache.bcel.generic.SWAP;
48  import org.apache.bcel.generic.Type;
49  import org.apache.bcel.generic.Visitor;
50  import org.apache.bcel.util.ClassLoaderRepository;
51  
52  /**
53   * A builder class for modifying class byte-code by starting from an existing
54   * template class. It comes with a few convience methods that can make some
55   * common generical byte-code modifications.
56   * One of the main features is to enable constant-time lookup of methods
57   * through hashing - which might not be that important for most cases but could
58   * prove useful once someone comes along with some generated class containing
59   * thousands of methods :)
60   *
61   * @author Henrik Kaipe
62   */
63  public class ClassBytecodeBuilder {
64  
65      /**
66       * Is used for sending a "complete" exit-code from inside a visitor.
67       */
68      private static class VisitorTaskPerformed extends RuntimeException {
69          private final static VisitorTaskPerformed instance =
70                  new VisitorTaskPerformed();
71      }
72  
73      private static final int defaultAccessModifiers =
74              ~Constants.ACC_PRIVATE
75              & ~Constants.ACC_PROTECTED & ~Constants.ACC_PUBLIC;
76  
77      private final ClassGen cg;
78  
79      /**
80       * Maps MethodHashKey instances to their respective bcel-Method instances.
81       */
82      private final Map methodMap = new HashMap();
83  
84      private ClassBytecodeBuilder(final ClassGen cg) {
85          this.cg = cg;
86          final Method[] methods = cg.getMethods();
87          for (int i = 0 ; i < methods.length ; ++i) {
88              final Method m = methods[i];
89              methodMap.put(MethodHashKey.getHashKey(cg, m), m);
90          }
91      }
92  
93      public static ClassBytecodeBuilder newInstance(Class templateClass) {
94          ClassLoaderRepository loaderRepo =
95                  new ClassLoaderRepository(templateClass.getClassLoader());
96          try {
97              return new ClassBytecodeBuilder(new ClassGen(
98                      loaderRepo.loadClass(templateClass)));
99          } catch (ClassNotFoundException ex) {
100             throw new Error(ex);
101         }
102     }
103 
104     public InstructionFactory getInstructionFactory() {
105         return new InstructionFactory(cg);
106     }
107 
108     private static void filterUnknownAttributes(Code codeAttribute) {
109         final Attribute[] subAttrs = codeAttribute.getAttributes();
110         List list = new ArrayList(subAttrs.length);
111         for (int i = 0; i < subAttrs.length; ++i) {
112             if (false == subAttrs[i] instanceof Unknown) {
113                 list.add(subAttrs[i]);
114             }
115         }
116         if (subAttrs.length != list.size()) {
117             codeAttribute.setAttributes(
118                     (Attribute[]) list.toArray(new Attribute[list.size()]));
119         }
120     }
121 
122     private static Method filterUnknownCodeAttributes(Method m) {
123         Attribute[] attributes = m.getAttributes();
124         for (int i = 0; i < attributes.length; ++i) {
125             if (attributes[i] instanceof Code) {
126                 filterUnknownAttributes((Code) attributes[i]);
127             }
128         }
129         return m;
130     }
131 
132     public void prependMethod(Method m, InstructionList instructionList) {
133         MethodGen mg = new MethodGen(
134                 m, cg.getClassName(), cg.getConstantPool());
135         mg.getInstructionList().insert(instructionList);
136         mg.setMaxStack();
137         replaceMethod(filterUnknownCodeAttributes(mg.getMethod()));
138     }
139 
140     public Method getMethod(java.lang.reflect.Method m) {
141         return (Method) methodMap.get(MethodHashKey.getHashKey(m));
142     }
143 
144     public ClassBytecodeBuilder replaceMethod(Method newMethod) {
145         MethodHashKey hashkey = MethodHashKey.getHashKey(cg, newMethod);
146         Method oldMethod = (Method) methodMap.get(hashkey);
147         if (null == oldMethod) {
148             throw new NullPointerException(
149                     "There is no equivalent method to replace - " + newMethod);
150         }
151         cg.replaceMethod(oldMethod, newMethod);
152         methodMap.put(hashkey, newMethod);
153         return this;
154     }
155 
156     public ClassBytecodeBuilder addMethod(Method m) {
157         cg.addMethod(m);
158         methodMap.put(MethodHashKey.getHashKey(cg, m), m);
159         return this;
160     }
161 
162     public byte[] getByteCode() {
163         return cg.getJavaClass().getBytes();
164     }
165 
166     public void setPublic() {
167         cg.setModifiers(cg.getModifiers() & defaultAccessModifiers
168                 | Constants.ACC_PUBLIC);
169         Method[] methods = cg.getMethods();
170         for (int i = 0; i < methods.length; ++i) {
171             final Method m = methods[i];
172             if ("<init>".equals(m.getName())) {
173                 m.setModifiers(m.getModifiers() & defaultAccessModifiers
174                         | Constants.ACC_PUBLIC);
175             }
176         }
177     }
178 
179     public void setNewSuperClass(String newSuperClassName) {
180         for (Iterator i = methodMap.values().iterator() ; i.hasNext() ;) {
181             final Method m = (Method) i.next();
182             if ("<init>".equals(m.getName())) {
183                 modifyConstructor(m, newSuperClassName);
184             }
185         }
186         cg.setSuperclassName(newSuperClassName);
187     }
188 
189     public void setupTransparentConstructors(
190             List constructorParamTypes, String newSuperClassName) {
191         Method defaultConstructor = findDefaultConstructor();
192         List instructionsToCopy = listInstructionsToKeep(defaultConstructor);
193 
194         /* Ditch the construction - it will be replaced by new ones. */
195         removeMethod(defaultConstructor);
196 
197         for (Iterator i = constructorParamTypes.iterator() ; i.hasNext() ;) {
198             final Type[] paramTypes = (Type[]) i.next();
199             addNewTransparentConstructor(
200                     paramTypes, newSuperClassName, instructionsToCopy);
201         }
202         cg.setSuperclassName(newSuperClassName);
203     }
204 
205     private void addNewTransparentConstructor(final Type[] paramTypes,
206             String newSuperClassName, List instructionsToCopy) {
207         InstructionList il = new InstructionList();
208         MethodGen mg = new MethodGen(Constants.ACC_PUBLIC, Type.VOID,
209                 paramTypes, null, "<init>", cg.getClassName(), il,
210                 cg.getConstantPool());
211 
212         il.append(InstructionConstants.ALOAD_0);
213         for (int i = 0 ; i < paramTypes.length ; ++i) {
214             il.append(InstructionFactory.createLoad(paramTypes[i], i + 1));
215         }
216         il.append(getInstructionFactory().createInvoke(newSuperClassName,
217                 "<init>", Type.VOID, paramTypes, Constants.INVOKESPECIAL));
218         for (Iterator i = instructionsToCopy.iterator() ; i.hasNext() ;) {
219             il.append((Instruction) i.next());
220         }
221         mg.setMaxStack();
222         addMethod(mg.getMethod());
223     }
224 
225     /**
226      * Assumes there is only one constructor for this class.
227      */
228     private Method findDefaultConstructor() {
229         for (Iterator i = methodMap.entrySet().iterator() ; i.hasNext() ;) {
230             final Map.Entry entry = (Entry) i.next();
231             final MethodHashKey key = (MethodHashKey) entry.getKey();
232             if ("<init>".equals(key.methodName)) {
233                 return (Method) entry.getValue();
234             }
235         }
236         throw new Error("No constructor was found");
237     }
238 
239     private void modifyConstructor(
240             Method method, final String newSuperClassName) {
241 
242         final String className = cg.getClassName();
243         final Type thisType = Type.getType('L' + className + ';');
244         final ConstantPoolGen cpg = cg.getConstantPool();
245         final MethodGen mg = new MethodGen(method, className, cpg);
246 
247         new org.apache.bcel.generic.EmptyVisitor() {
248 
249             int stackDistanceToA0 = Integer.MAX_VALUE/2; // Initial dummy-value
250             InstructionHandle ih;
251             boolean pending = true;
252 
253 //            @Override
254             public void visitALOAD(ALOAD obj) {
255                 if (0 == obj.getIndex()) {
256                     stackDistanceToA0 = 0;
257                     throw VisitorTaskPerformed.instance;
258                 }
259             }
260 
261             /** Used for handling the duplicating stack-operations DUP... */
262             void donotModifyStackDistanceLessThan(int sizeOfUnchangedStackTop) {
263                 if (stackDistanceToA0 < sizeOfUnchangedStackTop) {
264                     throw VisitorTaskPerformed.instance;
265                 }
266             }
267 
268 //            @Override
269             public void visitDUP(DUP obj) {
270                 donotModifyStackDistanceLessThan(1);
271             }
272 
273 //            @Override
274             public void visitDUP2(DUP2 obj) {
275                 donotModifyStackDistanceLessThan(2);
276             }
277 
278 //            @Override
279             public void visitDUP2_X1(DUP2_X1 obj) {
280                 donotModifyStackDistanceLessThan(3);
281             }
282 
283 //            @Override
284             public void visitDUP2_X2(DUP2_X2 obj) {
285                 donotModifyStackDistanceLessThan(4);
286             }
287 
288 //            @Override
289             public void visitDUP_X1(DUP_X1 obj) {
290                 donotModifyStackDistanceLessThan(2);
291             }
292 
293 //            @Override
294             public void visitDUP_X2(DUP_X2 obj) {
295                 donotModifyStackDistanceLessThan(3);
296             }
297 
298 //            @Override
299             public void visitSWAP(SWAP obj) {
300                 if (stackDistanceToA0 < 2) {
301                     stackDistanceToA0 = 1 - stackDistanceToA0;
302                 }
303                 throw VisitorTaskPerformed.instance;
304             }
305 
306 //            @Override
307             public void visitINVOKESPECIAL(INVOKESPECIAL obj) {
308 //                System.out.println("Invoke special on " + className);
309 //                System.out.println("Distance to A0 :" + stackDistanceToA0);
310 //                System.out.println("Stack to consume: " + obj.consumeStack(cpg));
311 //                System.out.println("Instruction name: " + obj.getName(cpg));
312 
313                 if (stackDistanceToA0 < obj.consumeStack(cpg)) {
314                     assert "<init>".equals(obj.getName(cpg));
315                     if (false == thisType.equals(obj.getReferenceType(cpg))) {
316                         ih.setInstruction(getInstructionFactory().createInvoke(
317                                 newSuperClassName,
318                                 "<init>",
319                                 Type.VOID,
320                                 obj.getArgumentTypes(cpg),
321                                 Constants.INVOKESPECIAL));
322                         mg.setMaxStack();
323                         replaceMethod(mg.getMethod());
324                     }
325                     pending = false;
326                     throw VisitorTaskPerformed.instance;
327                 }
328             }
329 
330             void replaceSuperConstructorInvocation() {
331                 for (ih = mg.getInstructionList().getStart()
332                         ; pending ; ih = ih.getNext()) {
333                     try {
334                         ih.accept(this);
335 
336                         Instruction instr = ih.getInstruction();
337                         int produceStack = instr.produceStack(cpg);
338                         if (0 < produceStack) {
339                             stackDistanceToA0 += produceStack;
340                         } else {
341                             int consumeStack= instr.consumeStack(cpg);
342                             if (0 < consumeStack) {
343                                 stackDistanceToA0 -= consumeStack;
344                             }
345                         }
346                     } catch (VisitorTaskPerformed x) {
347                         continue;
348                     }
349                 }
350             }
351 
352             /**
353              * Initiator (An anonymous class cannot have a constructor)
354              */
355             {replaceSuperConstructorInvocation();}
356         };
357     }
358 
359     private List listInstructionsToKeep(Method defaultConstructor) {
360         List instructionsToKeepInNewConstructors = new ArrayList();
361         InstructionList il = new MethodGen(
362                 defaultConstructor, cg.getClassName(), cg.getConstantPool()).getInstructionList();
363         Visitor superConstructorInvocationDetector = new EmptyVisitor() {
364 //            @Override
365             public void visitINVOKESPECIAL(INVOKESPECIAL obj) {
366                 throw VisitorTaskPerformed.instance;
367             }
368         };
369         Iterator i = il.iterator();
370         try {
371             while (i.hasNext()) {
372                 final InstructionHandle ih = (InstructionHandle) i.next();
373                 ih.accept(superConstructorInvocationDetector);
374             }
375             throw new Error("Found no super() invocation in instruction list: " + il);
376         } catch (VisitorTaskPerformed x) {
377             while (i.hasNext()) {
378                 final InstructionHandle ih = (InstructionHandle) i.next();
379                 instructionsToKeepInNewConstructors.add(ih.getInstruction());
380             }
381         }
382         return instructionsToKeepInNewConstructors;
383     }
384 
385     private void removeMethod(Method methodToRemove) {
386         cg.removeMethod(methodToRemove);
387         for (Iterator i = methodMap.entrySet().iterator() ; i.hasNext() ;) {
388             Map.Entry entry = (Entry) i.next();
389             if (methodToRemove == entry.getValue()) {
390                 i.remove();
391                 break;
392             }
393         }
394     }
395 }