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