View Javadoc

1   /*
2    * Copyright 2005 The Apache Software Foundation.
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.apache.jdo.impl.enhancer.core;
18  
19  import java.util.Enumeration;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Set;
25  import java.util.HashSet;
26  import java.util.Map;
27  import java.util.HashMap;
28  
29  import org.apache.jdo.impl.enhancer.classfile.AttributeVector;
30  import org.apache.jdo.impl.enhancer.classfile.ClassField;
31  import org.apache.jdo.impl.enhancer.classfile.ClassFile;
32  import org.apache.jdo.impl.enhancer.classfile.ClassMethod;
33  import org.apache.jdo.impl.enhancer.classfile.CodeAttribute;
34  import org.apache.jdo.impl.enhancer.classfile.ConstClass;
35  import org.apache.jdo.impl.enhancer.classfile.ConstantPool;
36  import org.apache.jdo.impl.enhancer.classfile.Descriptor;
37  import org.apache.jdo.impl.enhancer.classfile.ExceptionsAttribute;
38  import org.apache.jdo.impl.enhancer.classfile.Insn;
39  import org.apache.jdo.impl.enhancer.classfile.InsnTarget;
40  import org.apache.jdo.impl.enhancer.classfile.LineNumberTableAttribute;
41  import org.apache.jdo.impl.enhancer.classfile.SyntheticAttribute;
42  import org.apache.jdo.impl.enhancer.classfile.VMConstants;
43  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaData;
44  import org.apache.jdo.impl.enhancer.util.Support;
45  
46  
47  
48  
49  /***
50   * Handles the augmentation actions for a class.
51   */
52  final class Augmenter
53      extends Support
54      implements JDOConstants
55  {
56      //@olsen: fix for bug 4467428:
57      // Debugging under jdk 1.3.1 shows the problem that any breakpoints
58      // in PC classes are ignored if the added jdo methods do NOT have a
59      // non-empty line number table attribute, no matter whether the
60      // 'Synthetic' attribute is given or not.  However, this doesn't
61      // seem to comply with the JVM Spec (2nd edition), which states
62      // that the synthetic attribute _must_ be specified if no source
63      // code information is available for the member:
64      //
65      //     4.7.6 The Synthetic Attribute
66      //     ... A class member that does not appear in the source code must
67      //     be marked using a Synthetic attribute. ...
68      //
69      //     4.7.8 The LineNumberTable Attribute
70      //     The LineNumberTable attribute is an optional variable-length
71      //     attribute in the attributes table of a Code (ß4.7.3)
72      //     attribute. It may be used by debuggers to determine which
73      //     part of the Java virtual machine code array corresponds to a
74      //     given line number in the original source file. ... Furthermore,
75      //     multiple LineNumberTable attributes may together represent a
76      //     given line of a source file; that is, LineNumberTable attributes
77      //     need not be one-to-one with source lines.
78      //
79      // Unfortunately, if we do both, adding the synthetic attribute and
80      // a (dummy) line number table on generated methods, jdk's 1.3.1 javap
81      // fails to disassemble the classfile with an exception:
82      //
83      //     sun.tools.java.CompilerError: checkOverride() synthetic
84      //
85      // So, to workaround these problems and to allow for both, debugging
86      // and disassembling with the jdk (1.3.1) tools, we pretend that the
87      // generated jdo methods have source code equivalents by
88      // - not adding the synthetic code attribute
89      // - providing a dummy line number table code attribute
90      static private final boolean addSyntheticAttr = false;
91      static private final boolean addLineNumberTableAttr = true;
92  
93      /***
94       * The classfile's enhancement controller.
95       */
96      private final Controller control;
97  
98      /***
99       * The class analyzer for this class.
100      */
101     private final Analyzer analyzer;
102 
103     /***
104      * The classfile to be enhanced.
105      */
106     private final ClassFile classFile;
107 
108     /***
109      * The class name in VM form.
110      */
111     private final String className;
112 
113     /***
114      * The class name in user ('.' delimited) form.
115      */
116     private final String userClassName;
117 
118     /***
119      * The classfile's constant pool.
120      */
121     private final ConstantPool pool;
122 
123     /***
124      * Repository for the enhancement options.
125      */
126     private final Environment env;
127 
128     /***
129      * The method builder helper object.
130      */
131     private final Builder builder;
132 
133     // public accessors
134 
135     /***
136      * Constructor
137      */
138     public Augmenter(Controller control,
139                      Analyzer analyzer,
140                      Environment env)
141     {
142         affirm(control != null);
143         affirm(analyzer != null);
144         affirm(env != null);
145 
146         this.control = control;
147         this.analyzer = analyzer;
148         this.env = env;
149         this.classFile = control.getClassFile();
150         this.className = classFile.classNameString();
151         this.userClassName = classFile.userClassName();
152         this.pool = classFile.pool();
153         this.builder = new Builder(analyzer, this, env);
154 
155         affirm(classFile != null);
156         affirm(className != null);
157         affirm(userClassName != null);
158         affirm(pool != null);
159         affirm(builder != null);
160     }
161 
162     // ----------------------------------------------------------------------
163 
164     //^olsen: check public access modifier
165     
166     /***
167      * Adds the augmentation to the class.
168      */
169     public void augment()
170     {
171         affirm(analyzer.isAugmentable() && !env.noAugment());
172         env.message("augmenting class " + userClassName);
173 
174         if (analyzer.isAugmentableAsRoot()) {
175             augmentGenericJDOFields();
176             augmentGenericJDOMethods();
177         }
178         augmentClassInterface(JDO_PersistenceCapable_Path);
179         augmentSpecificJDOFields();
180         augmentSpecificJDOMethods();
181         augmentJDOAccessorMutatorMethods();
182         augmentSerializableSupportMethods();
183     }
184 
185     /***
186      * Adds the specified interface to the implements clause of the class.
187      */
188     private void augmentClassInterface(String interfaceName)
189     {
190         env.message("adding: implements "
191                     + ClassFile.userClassFromVMClass(interfaceName));
192 
193         final ConstClass iface = pool.addClass(interfaceName);
194         classFile.addInterface(iface);
195 
196         // notify controller of class change
197         control.noteUpdate();
198     }
199 
200     /***
201      * Adds the generic JDO fields to the class.
202      */
203     public void augmentGenericJDOFields()
204     {
205         //protected transient javax.jdo.StateManager jdoStateManager
206         addField(
207             JDO_PC_jdoStateManager_Name,
208             JDO_PC_jdoStateManager_Sig,
209             JDO_PC_jdoStateManager_Mods);        
210 
211         //protected transient byte jdoFlags
212         addField(
213             JDO_PC_jdoFlags_Name,
214             JDO_PC_jdoFlags_Sig,
215             JDO_PC_jdoFlags_Mods);
216     }
217 
218     /***
219      * Adds the specific JDO fields to the class.
220      */
221     public void augmentSpecificJDOFields()
222     {
223         //private static final int jdoInheritedFieldCount
224         addField(
225             JDO_PC_jdoInheritedFieldCount_Name,
226             JDO_PC_jdoInheritedFieldCount_Sig,
227             JDO_PC_jdoInheritedFieldCount_Mods);
228 
229         //private static final String[] jdoFieldNames
230         addField(
231             JDO_PC_jdoFieldNames_Name,
232             JDO_PC_jdoFieldNames_Sig,
233             JDO_PC_jdoFieldNames_Mods);
234 
235         //private static final Class[] jdoFieldTypes
236         addField(
237             JDO_PC_jdoFieldTypes_Name,
238             JDO_PC_jdoFieldTypes_Sig,
239             JDO_PC_jdoFieldTypes_Mods);
240 
241         //private static final byte[] jdoFieldFlags
242         addField(
243             JDO_PC_jdoFieldFlags_Name,
244             JDO_PC_jdoFieldFlags_Sig,
245             JDO_PC_jdoFieldFlags_Mods);
246 
247         //private static final Class jdoPersistenceCapableSuperclass
248         addField(
249             JDO_PC_jdoPersistenceCapableSuperclass_Name,
250             JDO_PC_jdoPersistenceCapableSuperclass_Sig,
251             JDO_PC_jdoPersistenceCapableSuperclass_Mods);
252     }
253 
254     /***
255      * Adds a field to the class.
256      */
257     private void addField(String fieldName,
258                           String fieldSig,
259                           int accessFlags)
260     {
261         affirm(fieldName != null);
262         affirm(fieldSig != null);
263 
264         env.message("adding: "
265                     + Descriptor.userFieldSig(fieldSig)
266                     + " " + fieldName);
267 
268         //@olsen: fix 4467428, add synthetic attribute for generated fields
269         final AttributeVector fieldAttrs = new AttributeVector();
270         fieldAttrs.addElement(
271             new SyntheticAttribute(
272                 pool.addUtf8(SyntheticAttribute.expectedAttrName)));
273 
274         // create and add the field
275         final ClassField field
276             = new ClassField(accessFlags,
277                              pool.addUtf8(fieldName),
278                              pool.addUtf8(fieldSig),
279                              fieldAttrs);
280         affirm(classFile.findField(fieldName) == null,
281                "Attempt to add a repeated field.");
282         classFile.addField(field);
283 
284         // notify controller of class change
285         control.noteUpdate();
286     }
287 
288     /***
289      * Adds the generic JDO methods to the class.
290      */
291     public void augmentGenericJDOMethods()
292     {
293         builder.addJDOReplaceFlags();
294         builder.addJDOIsPersistentMethod();
295         builder.addJDOIsTransactionalMethod();
296         builder.addJDOIsNewMethod();
297         builder.addJDOIsDeletedMethod();
298         builder.addJDOIsDirtyMethod();        
299         builder.addJDOIsDetachedMethod();       
300         builder.addJDOMakeDirtyMethod();
301         builder.addJDOPreSerializeMethod();
302         builder.addJDOGetPersistenceManagerMethod();
303         builder.addJDOGetObjectIdMethod();
304         builder.addJDOGetTransactionalObjectIdMethod();
305         builder.addJDOGetVersionMethod();
306         builder.addJDOReplaceStateManager();
307         builder.addJDOProvideFieldsMethod();
308         builder.addJDOReplaceFieldsMethod();
309 
310         builder.addSunJDOClassForNameMethod();
311 
312 /*
313         if (!hasCloneMethod) {
314             classFile.addMethod(
315                 builder.makeJDOClone(
316                     this,
317                     JAVA_Object_clone_Name));
318         }
319 */
320     }
321 
322     /***
323      * Adds the specific JDO methods to the class.
324      */
325     public void augmentSpecificJDOMethods()
326     {
327         // class registration
328         builder.addJDOGetManagedFieldCountMethod();
329         builder.addStaticInitialization();
330 
331         // instantiation methods
332         builder.addJDONewInstanceMethod();
333         builder.addJDONewInstanceOidMethod();
334         
335         // field handling methods
336         builder.addJDOProvideFieldMethod();
337         builder.addJDOReplaceFieldMethod();
338         builder.addJDOCopyFieldMethod();
339         builder.addJDOCopyFieldsMethod();
340 
341         // key handling methods
342         if (analyzer.isAugmentableAsRoot()
343             || analyzer.getKeyClassName() != null) {
344             builder.addJDONewObjectIdInstanceMethod();
345             builder.addJDONewObjectIdInstanceObjectMethod();
346             builder.addJDOCopyKeyFieldsToObjectIdMethod();
347             builder.addJDOCopyKeyFieldsFromObjectIdMethod();
348             builder.addJDOCopyKeyFieldsToObjectIdOIFSMethod();
349             builder.addJDOCopyKeyFieldsFromObjectIdOIFCMethod();
350         }
351 
352 /*
353         builder.addNullMethod(JDO_PC_jdoProvideField_Name,
354                               JDO_PC_jdoProvideField_Sig,
355                               JDO_PC_jdoProvideField_Mods);
356         builder.addNullMethod(JDO_PC_jdoReplaceField_Name,
357                               JDO_PC_jdoReplaceField_Sig,
358                               JDO_PC_jdoReplaceField_Mods);
359 */
360     }
361 
362     /***
363      * Adds the JDO accessor+mutator method for a field.
364      */
365     public void augmentJDOAccessorMutatorMethod(String fieldName,
366                                                 String fieldSig,
367                                                 int fieldMods,
368                                                 int fieldFlags,
369                                                 int index)
370     {
371         affirm(fieldName != null);
372         affirm(fieldSig != null);
373         affirm((fieldMods & ACCStatic) == 0);
374         affirm((fieldFlags & CHECK_READ) == 0
375                | (fieldFlags & MEDIATE_READ) == 0);
376         affirm((fieldFlags & CHECK_WRITE) == 0
377                | (fieldFlags & MEDIATE_WRITE) == 0);
378 
379         // these combinations are not supported by JDO
380         affirm((fieldFlags & CHECK_READ) == 0
381                | (fieldFlags & MEDIATE_WRITE) == 0);
382         affirm((fieldFlags & CHECK_WRITE) == 0
383                | (fieldFlags & MEDIATE_READ) == 0);
384 
385         // add accessor
386         final String aName
387             = JDONameHelper.getJDO_PC_jdoAccessor_Name(fieldName);
388         final String aSig
389             = JDONameHelper.getJDO_PC_jdoAccessor_Sig(className, fieldSig);
390         final int aMods
391             = JDONameHelper.getJDO_PC_jdoAccessor_Mods(fieldMods);
392         if ((fieldFlags & CHECK_READ) != 0) {
393             builder.addJDOCheckedReadAccessMethod(aName, aSig, aMods, index);
394         } else if ((fieldFlags & MEDIATE_READ) != 0) {
395             builder.addJDOMediatedReadAccessMethod(aName, aSig, aMods, index);
396         } else {
397             builder.addJDODirectReadAccessMethod(aName, aSig, aMods, index);
398         }
399 
400         // add mutator
401         final String mName
402             = JDONameHelper.getJDO_PC_jdoMutator_Name(fieldName);
403         final String mSig
404             = JDONameHelper.getJDO_PC_jdoMutator_Sig(className, fieldSig);
405         final int mMods
406             = JDONameHelper.getJDO_PC_jdoMutator_Mods(fieldMods);
407         if ((fieldFlags & CHECK_WRITE) != 0) {
408             builder.addJDOCheckedWriteAccessMethod(mName, mSig, mMods, index);
409         } else if ((fieldFlags & MEDIATE_WRITE) != 0) {
410             builder.addJDOMediatedWriteAccessMethod(mName, mSig, mMods, index);
411         } else {
412             builder.addJDODirectWriteAccessMethod(mName, mSig, mMods, index);
413         }
414     }
415     
416     /***
417      * Adds the JDO accessor+mutator methods to the class.
418      */
419     public void augmentJDOAccessorMutatorMethods()
420     {
421         final int annotatedFieldCount = analyzer.getAnnotatedFieldCount();
422         final String[] annotatedFieldNames = analyzer.getAnnotatedFieldNames();
423         final String[] annotatedFieldSigs = analyzer.getAnnotatedFieldSigs();
424         final int[] annotatedFieldMods = analyzer.getAnnotatedFieldMods();
425         final int[] annotatedFieldFlags = analyzer.getAnnotatedFieldFlags();
426         affirm(annotatedFieldNames.length == annotatedFieldCount);
427         affirm(annotatedFieldSigs.length == annotatedFieldCount);
428         affirm(annotatedFieldMods.length == annotatedFieldCount);
429         affirm(annotatedFieldFlags.length == annotatedFieldCount);
430 
431         for (int i = 0; i < annotatedFieldCount; i++) {
432             augmentJDOAccessorMutatorMethod(annotatedFieldNames[i],
433                                             annotatedFieldSigs[i],
434                                             annotatedFieldMods[i],
435                                             annotatedFieldFlags[i], i);
436         }
437     }
438 
439     /***
440      *
441      */
442     public void augmentSerializableSupportMethods()
443     {
444         final EnhancerMetaData meta = env.getEnhancerMetaData();
445         final String pcSuperClassName = analyzer.getPCSuperClassName();
446         
447         // Add serializable support, if 
448         // - this class implements Serializable and
449         // - the pc superclass (if available) does NOT implement Serializable
450         if (meta.isSerializableClass(className) &&
451             (pcSuperClassName == null || 
452              !meta.isSerializableClass(pcSuperClassName))) {
453             // add writeObject if this class does not provide method writeObject and 
454             // does not provide method writeReplace
455             if (!analyzer.hasWriteObjectMethod() && 
456                 !analyzer.hasWriteReplaceMethod()) {
457                 builder.addWriteObjectMethod();
458             }
459             else {
460                 if (analyzer.hasWriteObjectMethod()) {
461                     // add call of jdoPreSerialize to writeObject
462                     builder.addJDOPreSerializeCall(
463                         JAVA_Object_writeObject_Name, 
464                         JAVA_Object_writeObject_Sig);
465                 }
466                 if (analyzer.hasWriteReplaceMethod()) {
467                     // add call of jdoPreSerialize to writeReplace
468                     builder.addJDOPreSerializeCall(
469                         JAVA_Object_writeReplace_Name, 
470                         JAVA_Object_writeReplace_Sig);
471                 }
472             }
473         }
474     }
475 
476     /***
477      * Adds a method to the class.
478      */
479     void addMethod(String methodName,
480                    String methodSig,
481                    int accessFlags,
482                    CodeAttribute codeAttr,
483                    ExceptionsAttribute exceptAttr)
484     {
485         affirm(methodName != null);
486         affirm(methodSig != null);
487         affirm(codeAttr != null);
488 
489         env.message("adding: "
490                     + Descriptor.userMethodResult(methodSig)
491                     + " " + methodName
492                     + Descriptor.userMethodArgs(methodSig));
493 
494         //@olsen: fix 4467428, add dummy, non-empty line number table
495         if (addLineNumberTableAttr) {
496             // get first instruction which always is an instruction target
497             affirm(codeAttr.theCode().opcode() == Insn.opc_target);
498             final InsnTarget begin = (InsnTarget)codeAttr.theCode();
499 
500             // get attributes of the code attribute
501             final AttributeVector codeSpecificAttrs = codeAttr.attributes();
502             affirm(codeSpecificAttrs != null);
503             
504             // add dummy line number attribute with first instruction
505             codeSpecificAttrs.addElement(
506                 new LineNumberTableAttribute(
507                     pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
508                     new short[]{ 0 }, new InsnTarget[]{ begin }));
509         }
510 
511         // add the method's code and exception attributes
512         final AttributeVector methodAttrs = new AttributeVector();
513         methodAttrs.addElement(codeAttr);
514         if (exceptAttr != null) {
515             methodAttrs.addElement(exceptAttr);
516         }
517         
518         //@olsen: fix 4467428, add synthetic attribute for generated methods
519         if (addSyntheticAttr) {
520             methodAttrs.addElement(
521                 new SyntheticAttribute(
522                     pool.addUtf8(SyntheticAttribute.expectedAttrName)));
523         }
524         
525         // create and add the method
526         final ClassMethod method
527             = new ClassMethod(accessFlags,
528                               pool.addUtf8(methodName),
529                               pool.addUtf8(methodSig),
530                               methodAttrs);
531         affirm(classFile.findMethod(methodName, methodSig) == null,
532                "Attempt to add a repeated method.");
533         classFile.addMethod(method);
534 
535         // notify controller of class change
536         control.noteUpdate();
537     }
538 
539     /***
540      * Extends an exisiting method by prepending code.
541      */
542     void prependMethod(String methodName,
543                        String methodSig,
544                        CodeAttribute codeAttr,
545                        ExceptionsAttribute exceptAttr)
546     {
547         affirm(methodName != null);
548         affirm(methodSig != null);
549         affirm(codeAttr != null);
550 
551         env.message("extending: "
552                     + Descriptor.userMethodResult(methodSig)
553                     + " " + methodName
554                     + Descriptor.userMethodArgs(methodSig));
555 
556         // get method
557         final ClassMethod method = classFile.findMethod(methodName, methodSig);
558         affirm(method != null,
559                "Attempt to add code to a non-existing method.");
560 
561         // check the found method
562         affirm(!method.isAbstract(),
563                "Attempt to add code to an abstract method.");
564         affirm(!method.isNative(),
565                "Attempt to add code to a native method.");
566         final CodeAttribute foundCodeAttr = method.codeAttribute();
567         affirm(foundCodeAttr != null);  // by JVM spec
568 
569         // prepend the new code to the current one
570         final Insn firstInsn = codeAttr.theCode();
571         affirm(firstInsn != null);
572         final Insn foundFirstInsn = foundCodeAttr.theCode();
573         affirm(foundFirstInsn != null);
574         final Insn lastInsn = firstInsn.append(foundFirstInsn);
575         affirm(lastInsn != null);
576         foundCodeAttr.setTheCode(firstInsn);
577 
578         // ajust the method's stack and locals demand
579         foundCodeAttr.setStackUsed(max(foundCodeAttr.stackUsed(),
580                                        codeAttr.stackUsed()));
581         foundCodeAttr.setLocalsUsed(max(foundCodeAttr.localsUsed(),
582                                         codeAttr.localsUsed()));
583 
584         // add the exception attribute or its exceptions
585         if (exceptAttr != null) {
586             affirm((exceptAttr.getExceptions().size()
587                     == new HashSet(exceptAttr.getExceptions()).size()),
588                    "Exception attribute contains duplicate exceptions.");
589             
590             final ExceptionsAttribute foundExceptAttr
591                 = method.exceptionsAttribute();
592             if (foundExceptAttr == null) {
593                 // add the exception attribute
594                 final AttributeVector methodAttrs = method.attributes();
595                 affirm(methodAttrs != null);
596                 methodAttrs.addElement(exceptAttr);
597             } else {
598                 // add those exceptions not already present
599                 final List foundEx = foundExceptAttr.getExceptions();
600                 final List newEx = exceptAttr.getExceptions();
601                 newEx.removeAll(foundEx);
602                 foundEx.addAll(newEx);
603             }
604         }
605         
606         // notify controller of class change
607         control.noteUpdate();
608     }
609 
610     static private int max(int i, int j) 
611     {
612         return (i < j) ? j : i;
613     }
614 }