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