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.Iterator;
21  import java.util.Enumeration;
22  import java.util.List;
23  import java.util.ArrayList;
24  import java.util.Map;
25  import java.util.HashMap;
26  import java.util.Set;
27  import java.util.HashSet;
28  import java.util.Stack;
29  
30  import java.io.PrintWriter;
31  import java.io.StringWriter;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.FileInputStream;
35  import java.io.BufferedInputStream;
36  import java.io.DataInputStream;
37  import java.io.FileNotFoundException;
38  import java.io.ByteArrayOutputStream;
39  import java.io.PrintStream;
40  
41  import org.apache.jdo.impl.enhancer.classfile.ClassFile;
42  import org.apache.jdo.impl.enhancer.classfile.ClassMethod;
43  import org.apache.jdo.impl.enhancer.classfile.Descriptor;
44  
45  
46  
47  /***
48   * Utility class for testing two class files for equal augmentation.
49   *
50   * @author Martin Zaun
51   */
52  public class AugmentationDiffTest
53  {
54      // return values of main()
55      static public final int OK = 0;
56      static public final int USAGE_ERROR = -1;
57      static public final int INTERNAL_ERROR = -3;
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      // output streams
65      static private boolean debug = false;
66      static private final PrintWriter out = new PrintWriter(System.out, true);
67      static private final PrintWriter err = new PrintWriter(System.err, true);
68  
69      static final void affirm(boolean cond)
70      {
71          if (debug && !cond)
72              //^olsen: throw AssertionException ?
73              throw new RuntimeException("Assertion failed.");
74      }
75  
76      static final void affirm(Object obj)
77      {
78          if (debug && obj == null)
79              //^olsen: throw AssertionException ?
80              throw new RuntimeException("Assertion failed: obj = null");
81      }
82  
83      static private InputStream openFileInputStream(String fileName)
84          throws FileNotFoundException
85      {
86       	return new BufferedInputStream(new FileInputStream(fileName));
87      }
88  
89      static private void closeInputStream(InputStream in)
90      {
91          if (in != null) {
92              try {
93                  in.close();
94              } catch (IOException ex) {
95                  err.println("Exception caught: " + ex);
96              }
97          }
98      }
99  
100     // ----------------------------------------------------------------------
101 
102     private boolean verbose;
103     private String[] classFileNames;
104     private String[] classNames;
105     private String[] userClassNames;
106     private ClassFile[] classFiles;
107 
108     public AugmentationDiffTest()
109     {}
110 
111     private int diffAugmentation(PrintWriter out)
112     {
113         affirm(ERROR < NEGATIVE && NEGATIVE < AFFIRMATIVE);
114         affirm(classFiles.length == 2);
115         affirm(classNames.length == 2);
116 
117         int res = NEGATIVE;
118 
119         final Map[] classMethods = { new HashMap(), new HashMap() };
120         for (int i = 0; i < 2; i++) {
121             for (Enumeration e = classFiles[i].methods().elements();
122                  e.hasMoreElements();) {
123                 final ClassMethod method = (ClassMethod)e.nextElement();
124                 final String methodSig = method.signature().asString();
125                 final String methodArgs = Descriptor.userMethodArgs(methodSig);
126                 final String methodName = method.name().asString();
127 
128                 if (methodName.startsWith("jdo")) {
129                 //if (methodName.equals("jdoReplaceField")) {
130                     final Object obj
131                         = classMethods[i].put(methodName + methodArgs, method);
132                     affirm(obj == null);
133                 }
134             }
135         }
136         
137         final Set keySet = new HashSet();
138         keySet.addAll(classMethods[0].keySet());
139         keySet.addAll(classMethods[1].keySet());
140         for (Iterator i = keySet.iterator(); i.hasNext();) {
141             final Object key = i.next();
142 
143             final ClassMethod method0
144                 = (ClassMethod)classMethods[0].remove(key);
145             final ClassMethod method1
146                 = (ClassMethod)classMethods[1].remove(key);
147             affirm(method0 != method1);
148             affirm(method0 != null || method1 != null);
149             
150             if (method0 == null || method1 == null) {
151                 out.println("    !!! ERROR: missing method: " + key);
152                 out.println("        <<< method: " + method0);
153                 out.println("        >>> method: " + method1);
154                 res = ERROR;
155                 continue;
156             }
157 
158             final Stack msg = new Stack();
159             if (method0.isEqual(msg, method1)) {
160                 if (verbose) {
161                     out.println("    +++ equal augmentation: " + key);
162                 }
163             } else {
164                 out.println("    !!! not equal augmentation: " + key);
165                 msg.push("method = " + method1);
166                 msg.push("method = " + method0);
167                 final StringWriter s0 = new StringWriter();
168                 final StringWriter s1 = new StringWriter();
169                 final PrintWriter p0 = new PrintWriter(s0);
170                 final PrintWriter p1 = new PrintWriter(s1);
171                 int j = 0;
172                 while (!msg.empty()) {
173                     p0.println("    <<< " + pad(j) + msg.pop());
174                     p1.println("    >>> " + pad(j) + msg.pop());
175                     j += 4;
176                 }
177                 out.println(s0.toString());
178                 out.println(s1.toString());
179 
180                 if (verbose) {
181                     ByteArrayOutputStream b0 = new ByteArrayOutputStream();
182                     ByteArrayOutputStream b1 = new ByteArrayOutputStream();
183                     method0.print(new PrintStream(b0), 4);
184                     method1.print(new PrintStream(b1), 4);
185                     out.println(b0.toString());
186                     out.println(b1.toString());
187                     if (res == NEGATIVE) {
188                         res = AFFIRMATIVE;
189                     }
190                 }
191                 break;
192             }
193         }
194 
195         return res;
196     }
197 
198     static private String pad(int n) 
199     {
200         final StringBuffer s = new StringBuffer();
201         for (int i = 0; i < n; i++) {
202             s.append(' ');
203         }
204         return s.toString();
205     }
206 
207     private int parseClass(PrintWriter out,
208                            int i)
209     {
210         affirm(0 <= i && i <= 1);
211         affirm(classFileNames.length == 2);
212         affirm(classFiles.length == 2);
213         affirm(classNames.length == 2);
214         affirm(userClassNames.length == 2);
215         final String fileName = classFileNames[i];
216         
217         DataInputStream dis = null;
218         try {
219             // create class file
220             dis = new DataInputStream(openFileInputStream(fileName));
221             final boolean allowJDK12ClassFiles = true;
222             classFiles[i] = new ClassFile(dis, allowJDK12ClassFiles);
223 
224             // get real class name
225             classNames[i] = classFiles[i].className().asString();
226             userClassNames[i] = classNames[i].replace('/', '.');
227             out.println("    +++ parsed classfile");
228         } catch (ClassFormatError ex) {
229             out.println("    !!! ERROR: format error when parsing classfile: "
230                         + fileName);
231             out.println("        error: " + err);
232             return ERROR;
233         } catch (IOException ex) {
234             out.println("    !!! ERROR: exception while reading classfile: "
235                         + fileName);
236             out.println("        exception: " + ex);
237             return ERROR;
238         } finally {
239             closeInputStream(dis);
240         }
241 
242         return AFFIRMATIVE;
243     }
244 
245     private int test(PrintWriter out,
246                      String[] classFileNames)
247     {
248         affirm(classFileNames.length == 2);
249         this.classFileNames = classFileNames;
250 
251         if (verbose) {
252             out.println("-------------------------------------------------------------------------------");
253             out.println();
254             out.println("Test classfiles for equal augmentation: ...");
255         }
256         
257         // check parsing class
258         classFiles = new ClassFile[2];
259         classNames = new String[2];
260         userClassNames = new String[2];
261         for (int i = 0; i < 2; i++) {
262             final StringWriter s = new StringWriter();
263             if (parseClass(new PrintWriter(s), i) <= NEGATIVE) {
264                 out.println();
265                 out.println("!!! ERROR: failed parsing classfile: "
266                             + classFileNames[i]);
267                 out.println(s.toString());
268                 return ERROR;
269             }
270 
271             if (verbose) {
272                 out.println();
273                 out.println("+++ parsed classfile: " + classFileNames[i]);
274                 out.println(s.toString());
275             }
276         }
277         
278         // check class names
279         {
280             final StringWriter s = new StringWriter();
281             if (!classNames[0].equals(classNames[1])) {
282                 out.println();
283                 out.println("!!! ERROR: different class names:");
284                 out.println("<<< class name = " + userClassNames[0]);
285                 out.println(">>> class name = " + userClassNames[1]);
286                 out.println(s.toString());
287                 return ERROR;
288             }
289         }
290         
291         // check for augmentation differences
292         final StringWriter s = new StringWriter();
293         final int r = diffAugmentation(new PrintWriter(s));
294         if (r < NEGATIVE) {
295             out.println();
296             out.println("!!! ERROR: incorrect augmentation: "
297                         + userClassNames[0]);
298             out.println(s.toString());
299             return ERROR;
300         }
301         
302         if (r == NEGATIVE) {
303             out.println();
304             out.println("+++ equal augmentation:"
305                         + userClassNames[0]);
306         } else {
307             out.println();
308             out.println("!!! not equal augmentation:"
309                         + userClassNames[0]);
310         }
311         if (verbose) {
312             out.println(s.toString());
313         }
314 
315         return r;
316     }
317 
318     public int test(PrintWriter out,
319                     boolean verbose,
320                     List classFileNames)
321     {
322         affirm(classFileNames.size() % 2 == 0);
323         this.verbose = verbose;
324 
325         out.println();
326         out.println("AugmentationDiffTest: Testing Classes for JDO Persistence-Capability Enhancement");
327 
328         final int all = classFileNames.size() / 2;
329         int nofFailed = 0;
330         for (int i = 0; i < all; i++) {
331             String name0 = (String)classFileNames.get(i);
332             String name1 = (String)classFileNames.get(all + i);
333             String[] pair = { name0, name1 };
334             if (test(out, pair) != NEGATIVE) {
335                 nofFailed++;
336             }
337         }
338         final int nofPassed = all - nofFailed;
339 
340         out.println();
341         out.println("AugmentationDiffTest: Summary:  TESTED: " + all
342                     + "  PASSED: " + nofPassed
343                     + "  FAILED: " + nofFailed);
344         return nofFailed;
345     }
346     
347     // ----------------------------------------------------------------------
348 
349     /***
350      * Prints usage message.
351      */
352     static private void usage()
353     {
354         err.println();
355         err.println("Usage: AugmentationDiffTest <options> <classfile1> ... <classfile2> ...");
356         err.println();
357         err.println("This class pairwise tests if two classes have structurally the same code");
358         err.println("enhancement for persistence-capability (\"augmentation\").");
359         err.println();
360         err.println("Options include:");
361         err.println("    -h, --help               print usage");
362         err.println("    -v, --verbose            enable verbose output");
363         err.println();
364         err.println("Return value:");
365         err.println("= 0   equally augmented classfiles");
366         err.println("> 0   not equally augmented classfiles");
367         err.println("< 0   severe errors preventing the test to complete");
368         err.println();
369     }
370 
371     static public void main(String[] argv)
372     {
373         // parse args
374         boolean verbose = false;
375         List classFileNames = new ArrayList();
376         for (int i = 0; i < argv.length; i++) {
377             String arg = argv[i];
378             if (arg.equals("-h") || arg.equals("--help")) {
379                 usage();
380                 System.exit(OK);
381             }
382             if (arg.equals("-v") || arg.equals("--verbose")) {
383                 verbose = true;
384                 continue;
385             }
386             if (arg.equals("-d") ||
387                 arg.equals("--debug")) {
388                 debug = true;
389                 continue;
390             }
391             if (arg.startsWith("-")) {
392                 err.println();
393                 err.println("Unrecognized option: " + arg);
394                 usage();
395                 System.exit(USAGE_ERROR);
396             }
397             classFileNames.add(arg);
398         }
399 
400         // check arguments
401         if (classFileNames.size() % 2 != 0) {
402             err.println();
403             err.println("Odd number of classfiles arguments.");
404             usage();
405             System.exit(USAGE_ERROR);
406             return;
407         }
408 
409         if (debug) {
410             out.println("AugmentationDiffTest: options:");
411             out.println("    verbose = " + verbose);
412             out.print("    classFileNames =");
413             for (int i = 0; i < classFileNames.size(); i++)
414                 out.print(" " + classFileNames.get(i));
415             out.println();
416         }
417 
418         try {
419             final AugmentationDiffTest test = new AugmentationDiffTest();
420             final int res = test.test(out, verbose, classFileNames);
421             System.exit(res);
422         } catch (RuntimeException ex) {
423             err.println("Internal error;");
424             err.println("Exception caught:" + ex);
425             ex.printStackTrace(err);
426             System.exit(INTERNAL_ERROR);
427         }
428     }
429 }