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.util;
18  
19  import java.util.Collection;
20  import java.util.Iterator;
21  import java.util.Enumeration;
22  import java.util.List;
23  
24  import java.io.PrintWriter;
25  import java.io.StringWriter;
26  import java.io.IOException;
27  import java.io.DataInputStream;
28  
29  import org.apache.jdo.impl.enhancer.EnhancerFatalError;
30  import org.apache.jdo.impl.enhancer.JdoMetaMain;
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.ConstFieldRef;
36  import org.apache.jdo.impl.enhancer.classfile.ConstMethodRef;
37  import org.apache.jdo.impl.enhancer.classfile.ConstNameAndType;
38  import org.apache.jdo.impl.enhancer.classfile.Descriptor;
39  import org.apache.jdo.impl.enhancer.classfile.Insn;
40  import org.apache.jdo.impl.enhancer.classfile.InsnConstOp;
41  import org.apache.jdo.impl.enhancer.classfile.VMConstants;
42  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaData;
43  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaDataFatalError;
44  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaDataUserException;
45  
46  
47  
48  
49  
50  /***
51   * Utility class for testing a class file for correct annotation.
52   *
53   * @author Martin Zaun
54   */
55  public class AnnotationTest
56      extends JdoMetaMain
57  {
58      // return values of internal test methods
59      static public final int AFFIRMATIVE = 1;
60      static public final int NEGATIVE = 0;
61      static public final int ERROR = -1;
62  
63      // ----------------------------------------------------------------------
64  
65      private boolean verbose;
66      private String className;
67      private String classFileName;
68      private ClassFile classFile;
69  
70      public AnnotationTest(PrintWriter out,
71                            PrintWriter err)
72      {
73          super(out, err);
74      }
75  
76      private int checkGetPutField(PrintWriter out,
77                                   Insn insn,
78                                   boolean jdoMethod) 
79          throws EnhancerMetaDataUserException, EnhancerMetaDataFatalError
80      {
81          // get the instruction arguments
82          final InsnConstOp fieldInsn = (InsnConstOp)insn;
83          final ConstFieldRef fieldRef = (ConstFieldRef)fieldInsn.value();
84          final ConstClass declClass = fieldRef.className();
85          final String declClassName = declClass.asString();
86          final ConstNameAndType fieldNameAndType = fieldRef.nameAndType();
87          final String fieldName = fieldNameAndType.name().asString();
88          final String fieldType = fieldNameAndType.signature().asString();
89  
90          // check if field is known to be non-managed or not annotatable
91          final int res;
92          if (jdoMeta.isKnownNonManagedField(declClassName,
93                                             fieldName, fieldType)) {
94              if (false) { // verbose
95                  out.println("        --- unannotated field access: "
96                              + declClassName + "." + fieldName);
97              }
98              res = NEGATIVE;
99          } else if (jdoMethod) {
100             if (false) { // verbose
101                 out.println("        --- unannotated field access: "
102                             + declClassName + "." + fieldName);
103             } 
104             res = NEGATIVE;
105         } else if (jdoMeta.isPersistenceCapableClass(declClassName)
106                    && (fieldName.equals("jdoStateManager")
107                        || fieldName.equals("jdoFlags"))) {
108             if (false) { // verbose
109                 out.println("        --- unannotated field access: "
110                             + declClassName + "." + fieldName);
111             } 
112             res = NEGATIVE;
113         } else {
114             out.println("        !!! ERROR: missing annotation of field access: "
115                         + declClassName + "." + fieldName);
116             res = ERROR;
117         }
118         return res;
119     }
120     
121     private int checkInvokeStatic(PrintWriter out,
122                                   Insn insn,
123                                   boolean jdoMethod) 
124         throws EnhancerMetaDataUserException, EnhancerMetaDataFatalError
125     {
126         // get the instruction arguments
127         final InsnConstOp methodInsn = (InsnConstOp)insn;
128         final ConstMethodRef methodRef = (ConstMethodRef)methodInsn.value();
129         final ConstClass declClass = methodRef.className();
130         final String declClassName = declClass.asString();
131         final ConstNameAndType methodNameAndType = methodRef.nameAndType();
132         final String methodName = methodNameAndType.name().asString();
133         final String methodType = methodNameAndType.signature().asString();
134 
135         if (!methodName.startsWith("jdoSet")
136             && (!methodName.startsWith("jdoGet")
137                 || methodName.equals("jdoGetManagedFieldCount"))) {
138             return NEGATIVE;
139         }
140         final String fieldName = methodName.substring(6);
141 
142         final int res;
143         final String fieldType;
144         if (methodName.startsWith("jdoGet")) {
145             fieldType = Descriptor.extractResultSig(methodType);
146         } else {
147             final String argSig = Descriptor.extractArgSig(methodType);
148             final int idx = Descriptor.nextSigElement(argSig, 0);
149             fieldType = argSig.substring(idx);
150         }
151         affirm(fieldType != null);
152         
153         // check if field is known to be non-managed or non-annotable
154         if (jdoMeta.isKnownNonManagedField(declClassName,
155                                            fieldName, fieldType)) {
156             out.println("        !!! ERROR: annotated access to non-managed field: "
157                         + declClassName + "." + fieldName);
158             res = ERROR;
159         } else if (jdoMethod) {
160             out.println("        !!! ERROR: annotated field access in JDO method: "
161                         + declClassName + "." + fieldName);
162             res = ERROR;
163         } else {
164             if (verbose) {
165                 out.println("        +++ annotated field access: "
166                             + declClassName + "." + fieldName);
167             }
168             res = AFFIRMATIVE;
169         }
170 
171         return res;
172     }
173     
174     private int hasAnnotation(PrintWriter out,
175                               ClassMethod method,
176                               String methodName) 
177         throws EnhancerMetaDataUserException, EnhancerMetaDataFatalError
178     {
179         final CodeAttribute codeAttr = method.codeAttribute();
180 
181         // return if method is abstract or native
182         if (codeAttr == null)
183             return NEGATIVE;
184 
185         int res = NEGATIVE;
186         // don't annotate readObject(ObjectInputStream) or any jdo* methods
187         // except for jdoPreStore() and jdoPreDelete().
188         final boolean jdoMethod
189             = ((methodName.startsWith("jdo") 
190                 && !(methodName.equals("jdoPreStore()")
191                      || methodName.equals("jdoPreDelete()")))
192                || methodName.equals("readObject(java.io.ObjectInputStream)"));
193 
194         // first instruction is a target
195         final Insn firstInsn = codeAttr.theCode();
196         Insn insn = firstInsn.next();
197         while (insn != null) {
198             switch(insn.opcode()) {
199             case VMConstants.opc_getfield:
200             case VMConstants.opc_putfield: {
201                 final int r = checkGetPutField(out, insn, jdoMethod);
202                 if (r < NEGATIVE) {
203                     res = ERROR;
204                 }
205                 break;
206             }
207             case VMConstants.opc_invokestatic: {
208                 final int r = checkInvokeStatic(out, insn, jdoMethod);
209                 if (r < NEGATIVE) {
210                     res = ERROR;
211                 } else if (r > NEGATIVE) {
212                     if (res == NEGATIVE) {
213                         res = AFFIRMATIVE;
214                     }
215                 }
216                 break;
217             }
218             default:
219             }
220 
221             insn = insn.next();
222         }
223 
224         return res;
225     }
226 
227     private int testAnnotation(PrintWriter out)
228         throws EnhancerMetaDataUserException, EnhancerMetaDataFatalError
229     {
230         affirm(ERROR < NEGATIVE && NEGATIVE < AFFIRMATIVE);
231         affirm(classFile);
232 
233         int res = NEGATIVE;
234         
235         Enumeration e = classFile.methods().elements();
236         while (e.hasMoreElements()) {
237             final ClassMethod method = (ClassMethod)e.nextElement();
238             final String methodSig = method.signature().asString();
239             final String methodArgs = Descriptor.userMethodArgs(methodSig);
240             final String methodName = method.name().asString() + methodArgs;
241             
242             // check class-specific enhancement
243             final StringWriter s = new StringWriter();
244             int r = hasAnnotation(new PrintWriter(s), method, methodName);
245             if (r < NEGATIVE) {
246                 out.println("    !!! ERROR: incorrect annotation in: "
247                             + methodName);
248                 out.println(s.toString());
249                 res = ERROR;
250             } else if (r == NEGATIVE) {
251                 if (verbose) {
252                     out.println("    --- not annotated: "
253                                 + methodName);
254                     out.println(s.toString());
255                 }
256             } else {
257                 affirm(r > NEGATIVE);
258                 if (verbose) {
259                     out.println("    +++ has correct annotation: "
260                                 + methodName);
261                     out.println(s.toString());
262                 }
263                 if (res == NEGATIVE) {
264                     res = AFFIRMATIVE;
265                 }
266             }
267         }
268         
269         return res;
270     }
271 
272     private int parseClass(PrintWriter out)
273     {
274         DataInputStream dis = null;
275         try {
276             affirm(className == null ^ classFileName == null);
277             if (className != null) {
278                 dis = new DataInputStream(openClassInputStream(className));
279             } else {
280                 dis = new DataInputStream(openFileInputStream(classFileName));
281             }
282             final boolean allowJDK12ClassFiles = true;
283             classFile = new ClassFile(dis, allowJDK12ClassFiles);
284 
285             // check user class name from ClassFile
286             final String userClassName
287                 = classFile.className().asString().replace('/', '.');
288             //^olsen: better throw user exception or error
289             affirm(className == null || className.equals(userClassName));
290             out.println("    +++ parsed classfile");
291         } catch (ClassFormatError ex) {
292             out.println("    !!! ERROR: format error when parsing class: "
293                         + className);
294             out.println("        error: " + err);
295             return ERROR;
296         } catch (IOException ex) {
297             out.println("    !!! ERROR: exception while reading class: "
298                         + className);
299             out.println("        exception: " + ex);
300             return ERROR;
301         } finally {
302             closeInputStream(dis);
303         }
304 
305         affirm(classFile);
306         return AFFIRMATIVE;
307     }
308 
309     private int test(PrintWriter out,
310                      String className,
311                      String classFileName)
312         throws EnhancerMetaDataUserException, EnhancerMetaDataFatalError
313     {
314         this.className = className;
315         this.classFileName = classFileName;
316         affirm(className == null ^ classFileName == null);
317         final String name = (className != null ? className : classFileName);
318 
319         if (verbose) {
320             out.println("-------------------------------------------------------------------------------");
321             out.println();
322             out.println("Test class for correct annotation: "
323                         + name + " ...");
324         }
325         
326         // check parsing class
327         StringWriter s = new StringWriter();
328         if (parseClass(new PrintWriter(s)) <= NEGATIVE) {
329             out.println();
330             out.println("!!! ERROR: failed parsing class: " + name);
331             out.println(s.toString());
332             return ERROR;
333         }
334 
335         if (verbose) {
336             out.println();
337             out.println("+++ parsed class: " + name);
338             out.println(s.toString());
339         }
340         
341         // check annotation
342         s = new StringWriter();
343         final int r = testAnnotation(new PrintWriter(s));
344         if (r < NEGATIVE) {
345             out.println();
346             out.println("!!! ERROR: incorrect annotation: " + name);
347             out.println(s.toString());
348             return ERROR;
349         }
350         
351         if (r == NEGATIVE) {
352             out.println();
353             out.println("--- class not annotated: " + name);
354         } else {
355             out.println();
356             out.println("+++ class annotated: " + name);
357         }
358         if (verbose) {
359             out.println(s.toString());
360         }
361 
362         return r;
363     }
364 
365     protected int test(PrintWriter out,
366                        boolean verbose,
367                        List classNames,
368                        List classFileNames)
369     {
370         affirm(classNames);
371         this.verbose = verbose;
372 
373         out.println();
374         out.println("AnnotationTest: Testing Classes for JDO Persistence-Capability Enhancement");
375 
376         int nofFailed = 0;
377         final int all = classNames.size() + classFileNames.size();
378         for (int i = 0; i < classNames.size(); i++) {
379             if (test(out, (String)classNames.get(i), null) < NEGATIVE) {
380                 nofFailed++;
381             }
382         }
383         for (int i = 0; i < classFileNames.size(); i++) {
384             if (test(out, null, (String)classFileNames.get(i)) < NEGATIVE) {
385                 nofFailed++;
386             }
387         }
388         final int nofPassed = all - nofFailed;
389 
390         out.println();
391         out.println("AnnotationTest: Summary:  TESTED: " + all
392                     + "  PASSED: " + nofPassed
393                     + "  FAILED: " + nofFailed);
394         return nofFailed;
395     }
396     
397     // ----------------------------------------------------------------------
398 
399     /***
400      * Run the annotation test.
401      */
402     protected int process()
403     {
404         //^olsen: to be extended for zip/jar file arguments
405         return test(out, options.verbose.value,
406                     options.classNames, options.classFileNames);
407     }
408 
409     static public void main(String[] args)
410     {
411         final PrintWriter out = new PrintWriter(System.out, true);
412         out.println("--> AnnotationTest.main()");
413         final AnnotationTest main = new AnnotationTest(out, out);
414         int res = main.run(args);
415         out.println("<-- AnnotationTest.main(): exit = " + res);
416         System.exit(res);
417     }
418 }