View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software 
12   * distributed under the License is distributed on an "AS IS" BASIS, 
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
14   * See the License for the specific language governing permissions and 
15   * limitations under the License.
16   */
17  
18  
19  package org.apache.jdo.impl.enhancer.core;
20  
21  import java.util.Iterator;
22  
23  import org.apache.jdo.impl.enhancer.classfile.ClassFile;
24  import org.apache.jdo.impl.enhancer.classfile.ClassMethod;
25  import org.apache.jdo.impl.enhancer.classfile.CodeAttribute;
26  import org.apache.jdo.impl.enhancer.classfile.ConstClass;
27  import org.apache.jdo.impl.enhancer.classfile.ConstFieldRef;
28  import org.apache.jdo.impl.enhancer.classfile.ConstMethodRef;
29  import org.apache.jdo.impl.enhancer.classfile.ConstNameAndType;
30  import org.apache.jdo.impl.enhancer.classfile.ConstantPool;
31  import org.apache.jdo.impl.enhancer.classfile.Descriptor;
32  import org.apache.jdo.impl.enhancer.classfile.Insn;
33  import org.apache.jdo.impl.enhancer.classfile.InsnConstOp;
34  import org.apache.jdo.impl.enhancer.classfile.VMConstants;
35  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaData;
36  import org.apache.jdo.impl.enhancer.util.Support;
37  
38  
39  
40  
41  
42  /***
43   * Handles the augmentation actions for a method.
44   */
45  class Annotater
46      extends Support
47      implements VMConstants
48  {
49      /***
50       * The classfile's enhancement controller.
51       */
52      private final Controller control;
53  
54      /***
55       * The class analyzer for this class.
56       */
57      private final Analyzer analyzer;
58  
59      /***
60       * The classfile to be enhanced.
61       */
62      private final ClassFile classFile;
63  
64      /***
65       * The class name in user ('.' delimited) form.
66       */
67      private final String userClassName;
68  
69      /***
70       * The classfile's constant pool.
71       */
72      private final ConstantPool pool;
73  
74      /***
75       * Repository for the enhancer options.
76       */
77      private final Environment env;
78  
79      /***
80       * Repository for JDO meta-data on classes.
81       */
82      private final EnhancerMetaData meta;
83  
84      /***
85       * Constructor
86       */
87      public Annotater(Controller control,
88                       Analyzer analyzer,
89                       Environment env)
90      {
91          affirm(control != null);
92          affirm(analyzer != null);
93          affirm(env != null);
94  
95          this.control = control;
96          this.analyzer = analyzer;
97          this.env = env;
98          this.meta = env.getEnhancerMetaData();
99          this.classFile = control.getClassFile();
100         this.userClassName = classFile.userClassName();
101         this.pool = classFile.pool();
102 
103         affirm(classFile != null);
104         affirm(userClassName != null);
105         affirm(meta != null);
106         affirm(pool != null);
107     }
108 
109     /***
110      * Performs necessary annotation actions on the class.
111      */
112     public void annotate()
113     {
114         affirm(analyzer.isAnnotateable() && !env.noAnnotate());
115         env.message("annotating class " + userClassName);
116 
117         boolean annotated = false;
118         for (final Iterator i = analyzer.getAnnotatableMethods().iterator();
119              i.hasNext();) {
120             final ClassMethod method = (ClassMethod)i.next();
121             annotated |= annotated(method);
122         }
123         
124         // notify controller if class changed
125         if (annotated) {
126             control.noteUpdate();
127         }
128 
129 //^olsen: reenable
130 /*
131         //@olsen: do special annotation if detected super.clone()
132         if ((annotate & SuperClone) != 0) {
133             //final String superName = control.classFile().superName().asString();
134             //annotateClone(method, superName);
135         }
136 */
137     }
138 
139     /***
140      * Annotate the class method.  For now, brute force rules.
141      */
142     private boolean annotated(ClassMethod method)
143     {
144         boolean annotated = false;
145         final CodeAttribute codeAttr = method.codeAttribute();
146         if (codeAttr == null) {
147             return annotated;
148         }
149 
150         env.message(
151             "annotating: " + userClassName
152             + "." + method.name().asString()
153             + Descriptor.userMethodArgs(method.signature().asString()));
154         
155         // first instruction is a target
156         final Insn firstInsn = codeAttr.theCode();
157         affirm(firstInsn.opcode() == Insn.opc_target);
158         Insn insn = firstInsn.next();
159         while (insn != null) {
160             switch(insn.opcode()) {
161             case opc_getfield:
162             case opc_putfield:
163                 final Insn newInsn = insnAnnotation(insn);
164                 if (insn != newInsn) {
165                     annotated = true;
166                 }
167                 insn = newInsn;
168                 break;
169             default:
170             }
171 
172             insn = insn.next();
173         }
174 
175         return annotated;
176     }
177 
178     /***
179      * Generate annotations for put/getfield instructions.
180      */
181     private Insn insnAnnotation(final Insn insn)
182     {
183         if (false) {
184             System.out.println("MethodAnnotator.insnAnnotation(): ");
185             insn.printInsn(System.out);
186             System.out.println();
187         }
188 
189         affirm(insn.opcode() == opc_getfield || insn.opcode() == opc_putfield);
190         final boolean isGet = (insn.opcode() == opc_getfield);
191 
192         // get the instruction arguments
193         final InsnConstOp fieldInsn = (InsnConstOp)insn;
194         final ConstFieldRef fieldRef = (ConstFieldRef)fieldInsn.value();
195 
196         final ConstNameAndType fieldNameAndType = fieldRef.nameAndType();
197         final String fieldName = fieldNameAndType.name().asString();
198         final String fieldType = fieldNameAndType.signature().asString();
199 
200         final String qualifyingClassName = fieldRef.className().asString();
201         // get the field's declaring class from the model
202         final String declClassName =
203             meta.getDeclaringClass(qualifyingClassName, fieldName);
204         affirm(declClassName != null, "Cannot get declaring class of " 
205                + qualifyingClassName + "." + fieldName);
206         final ConstClass declClass = pool.addClass(declClassName);
207 
208         // check if field is known to be non-managed
209         if (meta.isKnownNonManagedField(declClassName, fieldName, fieldType)) {
210             return insn;
211         }
212 
213         // never annotate a jdo field; such may occur in pre-enhanced clone()
214         if (meta.isPersistenceCapableClass(declClassName)
215             && (fieldName.equals(JDOConstants.JDO_PC_jdoStateManager_Name)
216                 || fieldName.equals(JDOConstants.JDO_PC_jdoFlags_Name))) {
217             return insn;
218         }
219 
220         if (false) {
221             System.out.println("    " + (isGet ? "get" : "put") + "field "
222                                + declClassName + "." + fieldName
223                                + " : " + fieldType);
224         }
225 
226         final String methodName;
227         final String methodSig;
228         if (isGet) {
229             methodName = "jdoGet" + fieldName;
230             methodSig = "(L" + declClassName + ";)" + fieldType;
231         } else {
232             methodName = "jdoSet" + fieldName;
233             methodSig = "(L" + declClassName + ";" + fieldType + ")V";
234         }
235 
236         if (false) {
237             System.out.println("    "
238                                + declClassName + "." + methodName
239                                + " : " + methodSig);
240         }
241 
242         // call the PC's static accessor/mutator
243         final Insn frag = Insn.create(opc_invokestatic,
244                                       pool.addMethodRef(declClassName,
245                                                         methodName,
246                                                         methodSig));
247 
248         //insn.prev().insert(Insn.create(opc_nop));
249         // replace instruction
250         final Insn prev = insn.prev();
251         insn.remove();
252 
253         // replace current instruction with new fragment
254         final Insn last = prev.insert(frag);
255         return last;
256     }
257 
258     private void annotateClone(ClassMethod method,
259                                String superName)
260     {
261         //^olsen: extend for full support of inheritance on PC classes
262 
263         if (false) {
264             final String methodName = method.name().asString();
265             final String methodSig = method.signature().asString();
266             System.out.println("annotateClone()");
267             System.out.println("    methodName = " + methodName);
268             System.out.println("    methodSig = " + methodSig);
269             System.out.println("    superName = " + superName);
270         }
271 
272         final CodeAttribute codeAttr = method.codeAttribute();
273         for (Insn insn = codeAttr.theCode();
274              insn != null;
275              insn = insn.next()) {
276 
277             // Found the clone method.  See if it is the flavor of clone()
278             // which does a super.clone() call, and if it is, add
279             // field initializations for the jdoStateManager and jdoFlags
280             // fields.
281             if (insn.opcode() != opc_invokespecial)
282                 continue;
283 
284             final InsnConstOp invoke = (InsnConstOp)insn;
285             final ConstMethodRef methodRef = (ConstMethodRef)invoke.value();
286             final ConstNameAndType methodNT = methodRef.nameAndType();
287             final String methodName = methodNT.name().asString();
288             final String methodSig = methodNT.signature().asString();
289 
290             if (!(methodName.equals("clone")
291                   && methodSig.equals("()Ljava/lang/Object;")))
292                 continue;
293 
294             if (false) {
295                 final ConstClass methodClass = methodRef.className();
296                 final String methodClassName = methodClass.asString();
297                 System.out.println("        found invocation of: "
298                                    + methodClassName
299                                    + "." + methodName + methodSig);
300             }
301 
302             // check whether next instruction already is a downcast to a
303             // class implementing PersistenceCapable
304             final String thisClass = classFile.classNameString();
305             final Insn checkCastInsn = insn.next();
306             final boolean needCheckcast;
307             if (checkCastInsn.opcode() != opc_checkcast) {
308                 needCheckcast = true;
309             } else {
310                 ConstClass target =
311                     (ConstClass) ((InsnConstOp)checkCastInsn).value();
312                 if (target.asString().equals(thisClass)) {
313                     insn = checkCastInsn;
314                     needCheckcast = false;
315                 } else {
316                     needCheckcast = true;
317                 }
318             }
319 
320             // clear jdo fields of clone
321             {
322                 // duplicate downcasted reference
323                 final Insn newInsn = Insn.create(opc_dup);
324                 if (needCheckcast) {
325                     newInsn.append(Insn.create(opc_checkcast,
326                                                pool.addClass(thisClass)));
327                 }
328                 newInsn.append(Insn.create(opc_dup));
329 
330                 // clear jdo fields
331                 newInsn.append(Insn.create(opc_aconst_null));
332                 newInsn.append(Insn.create(
333                     opc_putfield,
334                     pool.addFieldRef(
335                         thisClass,
336                         JDOConstants.JDO_PC_jdoStateManager_Name,
337                         JDOConstants.JDO_PC_jdoStateManager_Sig)));
338                 newInsn.append(Insn.create(opc_iconst_0));
339                 newInsn.append(Insn.create(
340                     opc_putfield,
341                     pool.addFieldRef(
342                         thisClass,
343                         JDOConstants.JDO_PC_jdoFlags_Name,
344                         JDOConstants.JDO_PC_jdoFlags_Sig)));
345 
346                 // insert code
347                 insn.insert(newInsn);
348 
349                 // increase stack
350                 final int annotationStack = 3;
351                 codeAttr.setStackUsed(codeAttr.stackUsed() + annotationStack);
352             }
353         }
354     }
355 }