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.lang.reflect.Modifier;
21  import java.lang.reflect.Field;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Constructor;
24  
25  import java.util.Collection;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Arrays;
29  import java.util.HashSet;
30  
31  import java.io.PrintWriter;
32  import java.io.StringWriter;
33  import java.io.IOException;
34  
35  import org.apache.jdo.impl.enhancer.EnhancerFatalError;
36  import org.apache.jdo.impl.enhancer.EnhancerUserException;
37  import org.apache.jdo.impl.enhancer.JdoMetaMain;
38  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaDataFatalError;
39  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaDataUserException;
40  
41  
42  
43  
44  /***
45   * Utility class for testing a class file for correct augmentation.
46   *
47   * @author Martin Zaun
48   */
49  public class AugmentationTest
50      extends JdoMetaMain
51  {
52      // return values of internal test methods
53      static public final int AFFIRMATIVE = 1;
54      static public final int NEGATIVE = 0;
55      static public final int ERROR = -1;
56  
57      static private final String[] transientPrefixes
58          = {"java.",
59             "javax." };
60  
61      static String toString(int mods,
62                             Class type,
63                             String name)
64      {
65          final StringBuffer s = new StringBuffer();
66          s.append(Modifier.toString(mods));
67          s.append(" ");
68          s.append(type.getName());
69          s.append(" ");
70          s.append(name);
71          return s.toString();
72      }
73  
74      static String toString(int mods,
75                             String name,
76                             Class[] params)
77      {
78          final StringBuffer s = new StringBuffer();
79          s.append(Modifier.toString(mods));
80          s.append(" ");
81          s.append(name);
82          s.append("(");
83          final int j = params.length - 1;
84          for (int i = 0; i <= j; i++) {
85              s.append(params[i].getName());
86              if (i < j)
87                  s.append(",");
88          }
89          s.append(")");
90          return s.toString();
91      }
92  
93      static String toString(int mods,
94                             Class result,
95                             String name,
96                             Class[] params)
97      {
98          final StringBuffer s = new StringBuffer();
99          s.append(Modifier.toString(mods));
100         s.append(" ");
101         s.append(result.getName());
102         s.append(" ");
103         s.append(name);
104         s.append("(");
105         final int j = params.length - 1;
106         for (int i = 0; i <= j; i++) {
107             s.append(params[i].getName());
108             if (i < j)
109                 s.append(",");
110         }
111         s.append(")");
112         return s.toString();
113     }
114 
115     static String toString(int mods,
116                            Class result,
117                            String name,
118                            Class[] params,
119                            Class[] ex)
120     {
121         final StringBuffer s = new StringBuffer();
122         s.append(toString(mods, result, name, params));
123         s.append(" throws ");
124         final int j = ex.length - 1;
125         for (int i = 0; i <= j; i++) {
126             s.append(ex[i].getName());
127             if (i < j)
128                 s.append(",");
129         }
130         return s.toString();
131     }
132 
133     // ----------------------------------------------------------------------
134 
135     // information on currently processed class
136     private boolean verbose;
137     private String className;
138     private String classPath;
139     private Class classObject;
140     private HashSet fields;
141     private HashSet methods;
142 
143     // jdo class objects used by reflective tests
144     private ClassLoader classLoader;
145     private Class persistenceManagerClass;
146     private Class instanceCallbacksClass;
147     private Class persistenceCapableClass;
148     private Class objectIdFieldSupplierClass;
149     private Class objectIdFieldConsumerClass;
150     private Class stateManagerClass;
151 
152     public AugmentationTest(PrintWriter out,
153                             PrintWriter err) 
154     {
155         super(out, err);
156     }
157 
158     private int implementsInterface(PrintWriter out,
159                                     Class intf)
160     {
161         final Class[] interfaces = classObject.getInterfaces();
162         for (int i = interfaces.length - 1; i >= 0; i--) {
163             if (interfaces[i].equals(intf)) {
164                 out.println("        +++ implements interface: "
165                             + intf.getName());
166                 return AFFIRMATIVE;
167             }
168         }
169         out.println("        --- not implementing interface: "
170                     + intf.getName());
171         return NEGATIVE;
172     }
173 
174     private int hasField(PrintWriter out,
175                          int mods,
176                          Class type,
177                          String name)
178     {
179         try {
180             final Field field = classObject.getDeclaredField(name);
181             fields.remove(field);
182             
183             if ((field.getModifiers() & mods) != mods) {
184                 out.println("        !!! ERROR: field declaration: unmatched modifiers");
185                 out.println("            expected: "
186                             + toString(mods, type, name));
187                 out.println("            found:    "
188                             + field.toString());
189                 return ERROR;
190             }
191 
192             if (!field.getType().equals(type)) {
193                 out.println("        !!! ERROR: field declaration: unexpected type");
194                 out.println("            expected: "
195                             + toString(mods, type, name));
196                 out.println("            found:    "
197                             + field.toString());
198                 return ERROR;
199             }
200 
201             out.println("        +++ has field: "
202                         + field.toString());
203             return AFFIRMATIVE;
204         } catch (NoSuchFieldException ex) {
205             out.println("        --- no field: "
206                         + toString(mods, type, name));
207             return NEGATIVE;
208         }
209     }
210 
211     private int hasConstructor(PrintWriter out,
212                                int mods,
213                                Class[] params)
214     {
215         try {
216             final Constructor ctor = classObject.getDeclaredConstructor(params);
217 
218             if ((ctor.getModifiers() & mods) != mods) {
219                 out.println("        !!! ERROR: constructor declaration: unmatched modifiers");
220                 out.println("            expected: "
221                             + toString(mods, className, params));
222                 out.println("            found:    "
223                             + ctor.toString());
224                 return ERROR;
225             }
226 
227             out.println("        +++ has constructor: "
228                         + ctor.toString());
229             return AFFIRMATIVE;
230         } catch (NoSuchMethodException ex) {
231             out.println("        --- no constructor: "
232                         + toString(mods, className, params));
233             return NEGATIVE;
234         }
235     }
236 
237     private int hasMethod(PrintWriter out,
238                           int mods,
239                           Class result,
240                           String name,
241                           Class[] params,
242                           Class[] exepts)
243     {
244         try {
245             final Method method = classObject.getDeclaredMethod(name, params);
246             methods.remove(method);
247 
248             if ((method.getModifiers() & mods) != mods) {
249                 out.println("        !!! ERROR: method declaration: unmatched modifiers");
250                 out.println("            expected: "
251                             + toString(mods, result, name, params));
252                 out.println("            found:    "
253                             + method.toString());
254                 return ERROR;
255             }
256 
257             if (!method.getReturnType().equals(result)) {
258                 out.println("        !!! ERROR: method declaration: unexpected result type");
259                 out.println("            expected: "
260                             + toString(mods, result, name, params));
261                 out.println("            found:    "
262                             + method.toString());
263                 return ERROR;
264             }
265 
266             final Collection c0 = Arrays.asList(exepts);
267             final Collection c1 = Arrays.asList(method.getExceptionTypes());
268             if (!c0.containsAll(c1)) {
269                 out.println("        !!! ERROR: method declaration: unexpected exceptions");
270                 out.println("            expected: "
271                             + toString(mods, result, name, params, exepts));
272                 out.println("            found:    "
273                             + method.toString());
274                 return ERROR;
275             }
276             if (!c1.containsAll(c0)) {
277                 out.println("        !!! ERROR: method declaration: unmatched exceptions");
278                 out.println("            expected: "
279                             + toString(mods, result, name, params, exepts));
280                 out.println("            found:    "
281                             + method.toString());
282                 return ERROR;
283             }
284 
285             out.println("        +++ has method: "
286                         + method.toString());
287             return AFFIRMATIVE;
288         } catch (NoSuchMethodException ex) {
289             out.println("        --- no method: "
290                         + toString(mods, result, name, params));
291             return NEGATIVE;
292         }
293     }
294 
295     private int hasMethod(PrintWriter out,
296                           int mods,
297                           Class result,
298                           String name,
299                           Class[] params)
300     {
301         return hasMethod(out, mods, result, name, params, new Class[]{});
302     }
303 
304     private int evaluate(int nofFeatures,
305                          int[] r)
306     {
307         affirm(nofFeatures <= r.length);
308         
309         int res = 0;
310         for (int i = 0; i < nofFeatures; i++) {
311             final int j = r[i];
312             affirm(ERROR <= j && j <= AFFIRMATIVE);
313 
314             if (j < ERROR) {
315                 return ERROR;
316             }
317 
318             if (j > NEGATIVE) {
319                 res++;
320             }
321         }
322         affirm(res >= 0);
323         
324         if (res >= nofFeatures) {
325             return AFFIRMATIVE;
326         }
327         return NEGATIVE;
328     }
329     
330     private int hasGenericAugmentation(PrintWriter out)
331     {
332         affirm(ERROR < NEGATIVE && NEGATIVE < AFFIRMATIVE);
333         affirm(classObject);
334 
335         final int nofFeatures = 18;
336         final int[] r = new int[nofFeatures];
337         {
338             int i = 0;
339 
340             r[i++] = hasField(
341                 out,
342                 Modifier.PROTECTED | Modifier.TRANSIENT,
343                 stateManagerClass,
344                 "jdoStateManager");
345 
346             r[i++] = hasField(
347                 out,
348                 Modifier.PROTECTED | Modifier.TRANSIENT,
349                 byte.class,
350                 "jdoFlags");
351             
352             r[i++] = hasMethod(
353                 out,
354                 Modifier.PUBLIC
355                 | Modifier.FINAL
356                 | Modifier.SYNCHRONIZED,
357                 void.class,
358                 "jdoReplaceStateManager",
359                 new Class[]{stateManagerClass});
360 
361             r[i++] = hasMethod(
362                 out,
363                 Modifier.PUBLIC | Modifier.FINAL,
364                 void.class,
365                 "jdoReplaceFlags",
366                 new Class[]{});
367 
368             r[i++] = hasMethod(
369                 out,
370                 Modifier.PUBLIC | Modifier.FINAL,
371                 persistenceManagerClass,
372                 "jdoGetPersistenceManager",
373                 new Class[]{});
374 
375             r[i++] = hasMethod(
376                 out,
377                 Modifier.PUBLIC | Modifier.FINAL,
378                 Object.class,
379                 "jdoGetObjectId",
380                 new Class[]{});
381 
382             r[i++] = hasMethod(
383                 out,
384                 Modifier.PUBLIC | Modifier.FINAL,
385                 Object.class,
386                 "jdoGetTransactionalObjectId",
387                 new Class[]{});
388 
389             r[i++] = hasMethod(
390                 out,
391                 Modifier.PUBLIC | Modifier.FINAL,
392                 Object.class,
393                 "jdoGetVersion",
394                 new Class[]{});
395 
396             r[i++] = hasMethod(
397                 out,
398                 Modifier.PUBLIC | Modifier.FINAL,
399                 boolean.class,
400                 "jdoIsPersistent",
401                 new Class[]{});
402             r[i++] = hasMethod(
403                 out,
404                 Modifier.PUBLIC | Modifier.FINAL,
405                 boolean.class,
406                 "jdoIsTransactional",
407                 new Class[]{});
408             r[i++] = hasMethod(
409                 out,
410                 Modifier.PUBLIC | Modifier.FINAL,
411                 boolean.class,
412                 "jdoIsNew",
413                 new Class[]{});
414             r[i++] = hasMethod(
415                 out,
416                 Modifier.PUBLIC | Modifier.FINAL,
417                 boolean.class,
418                 "jdoIsDeleted",
419                 new Class[]{});
420             r[i++] = hasMethod(
421                 out,
422                 Modifier.PUBLIC | Modifier.FINAL,
423                 boolean.class,
424                 "jdoIsDirty",
425                 new Class[]{});
426             r[i++] = hasMethod(
427                 out,
428                 Modifier.PUBLIC | Modifier.FINAL,
429                 boolean.class,
430                 "jdoIsDetached",
431                 new Class[]{});
432 
433             r[i++] = hasMethod(
434                 out,
435                 Modifier.PUBLIC | Modifier.FINAL,
436                 void.class,
437                 "jdoMakeDirty",
438                 new Class[]{String.class});
439 
440             r[i++] = hasMethod(
441                 out,
442                 Modifier.PUBLIC | Modifier.FINAL,
443                 void.class,
444                 "jdoReplaceFields",
445                 new Class[]{int[].class});
446 
447             r[i++] = hasMethod(
448                 out,
449                 Modifier.PUBLIC | Modifier.FINAL,
450                 void.class,
451                 "jdoProvideFields",
452                 new Class[]{int[].class});
453 
454             r[i++] = hasMethod(
455                 out,
456                 Modifier.PROTECTED | Modifier.FINAL,
457                 void.class,
458                 "jdoPreSerialize",
459                 new Class[]{});
460 
461             affirm(i == nofFeatures);
462         }
463         
464         return evaluate(nofFeatures, r);
465     }
466 
467     private int hasSpecificAugmentation(PrintWriter out)
468     {
469         affirm(ERROR < NEGATIVE && NEGATIVE < AFFIRMATIVE);
470         affirm(classObject);
471 
472         final int nofFeatures = 15;
473         final int[] r = new int[nofFeatures];
474         {
475             int i = 0;
476 
477             r[i++] = implementsInterface(
478                 out,
479                 persistenceCapableClass);
480 
481             r[i++] = hasField(
482                 out,
483                 Modifier.PRIVATE | Modifier.FINAL | Modifier.STATIC,
484                 int.class,
485                 "jdoInheritedFieldCount");
486 
487             r[i++] = hasField(
488                 out,
489                 Modifier.PRIVATE | Modifier.FINAL | Modifier.STATIC,
490                 String[].class,
491                 "jdoFieldNames");
492 
493             r[i++] = hasField(
494                 out,
495                 Modifier.PRIVATE | Modifier.FINAL | Modifier.STATIC,
496                 Class[].class,
497                 "jdoFieldTypes");
498 
499             r[i++] = hasField(
500                 out,
501                 Modifier.PRIVATE | Modifier.FINAL | Modifier.STATIC,
502                 byte[].class,
503                 "jdoFieldFlags");
504 
505             r[i++] = hasField(
506                 out,
507                 Modifier.PRIVATE | Modifier.FINAL | Modifier.STATIC,
508                 Class.class,
509                 "jdoPersistenceCapableSuperclass");
510 
511             r[i++] = hasMethod(
512                 out,
513                 Modifier.PROTECTED | Modifier.STATIC,
514                 int.class,
515                 "jdoGetManagedFieldCount",
516                 new Class[]{});
517 
518             r[i++] = hasMethod(
519                 out,
520                 Modifier.PUBLIC,
521                 persistenceCapableClass,
522                 "jdoNewInstance",
523                 new Class[]{stateManagerClass});
524 
525             r[i++] = hasMethod(
526                 out,
527                 Modifier.PUBLIC,
528                 persistenceCapableClass,
529                 "jdoNewInstance",
530                 new Class[]{stateManagerClass, Object.class});
531 
532             r[i++] = hasMethod(
533                 out,
534                 Modifier.PUBLIC,
535                 void.class,
536                 "jdoReplaceField",
537                 new Class[]{int.class});
538 
539             r[i++] = hasMethod(
540                 out,
541                 Modifier.PUBLIC,
542                 void.class,
543                 "jdoProvideField",
544                 new Class[]{int.class});
545 
546             r[i++] = hasMethod(
547                 out,
548                 Modifier.PUBLIC,
549                 void.class,
550                 "jdoCopyFields",
551                 new Class[]{Object.class, int[].class});
552 
553             r[i++] = hasMethod(
554                 out,
555                 Modifier.PROTECTED | Modifier.FINAL,
556                 void.class,
557                 "jdoCopyField",
558                 new Class[]{classObject, int.class});
559 
560 //^olsen: hack for debugging
561 /*
562             r[i++] = hasField(
563                 out,
564                 Modifier.PRIVATE | Modifier.FINAL | Modifier.STATIC,
565                 longClass,
566                 "serialVersionUID");
567 
568             r[i++] = hasMethod(
569                 out,
570                 Modifier.PRIVATE,
571                 void.class,
572                 "writeObject",
573                 new Class[]{java.io.ObjectOutputStream.class},
574                 new Class[]{java.io.IOException.class});
575 
576             //^olsen: need to check for clone()?
577 */
578 
579             //^olsen: hack for debugging
580             affirm(i == nofFeatures-2);
581             //affirm(i == nofFeatures);
582         }
583 
584         //^olsen: hack for debugging
585         return evaluate(nofFeatures - 2, r);
586     }
587 
588     private int hasKeyHandlingAugmentation(PrintWriter out)
589     {
590         affirm(ERROR < NEGATIVE && NEGATIVE < AFFIRMATIVE);
591         affirm(classObject);
592 
593         final int nofFeatures = 6;
594         final int[] r = new int[nofFeatures];
595         {
596             int i = 0;
597 
598             r[i++] = hasMethod(
599                 out,
600                 Modifier.PUBLIC,
601                 Object.class,
602                 "jdoNewObjectIdInstance",
603                 new Class[]{});
604 
605             r[i++] = hasMethod(
606                 out,
607                 Modifier.PUBLIC,
608                 Object.class,
609                 "jdoNewObjectIdInstance",
610                 new Class[]{Object.class});
611 
612             r[i++] = hasMethod(
613                 out,
614                 Modifier.PUBLIC,
615                 void.class,
616                 "jdoCopyKeyFieldsToObjectId",
617                 new Class[]{Object.class});
618 
619             r[i++] = hasMethod(
620                 out,
621                 Modifier.PUBLIC,
622                 void.class,
623                 "jdoCopyKeyFieldsToObjectId",
624                 new Class[]{objectIdFieldSupplierClass, Object.class});
625 
626             r[i++] = hasMethod(
627                 out,
628                 Modifier.PROTECTED,
629                 void.class,
630                 "jdoCopyKeyFieldsFromObjectId",
631                 new Class[]{Object.class});
632 
633             r[i++] = hasMethod(
634                 out,
635                 Modifier.PUBLIC,
636                 void.class,
637                 "jdoCopyKeyFieldsFromObjectId",
638                 new Class[]{objectIdFieldConsumerClass, Object.class});
639 
640             affirm(i == nofFeatures);
641         }
642 
643         return evaluate(nofFeatures, r);
644     }
645 
646     private int hasAccessorMutators(PrintWriter out)
647         throws EnhancerMetaDataUserException, EnhancerMetaDataFatalError
648     {
649         affirm(classObject);
650         int res = NEGATIVE;
651         
652         // find managed field candidates by scanning for jdo[GS]et methods
653         final HashSet managedFields = new HashSet();
654         for (Iterator i = new HashSet(methods).iterator(); i.hasNext();) {
655             final Method method = (Method)i.next();
656             final String name = method.getName();
657 
658             if (!name.startsWith("jdoGet") && !name.startsWith("jdoSet")) {
659                 continue;
660             }
661             final String fieldName = name.substring(6);
662 
663             // find declared field
664             final Field field;
665             try {
666                 field = classObject.getDeclaredField(fieldName);
667             } catch (NoSuchFieldException ex) {
668                 out.println("        !!! ERROR: potential jdo accessor/mutator method doesn't match declared field");
669                 out.println("            found method: " + method);
670                 methods.remove(method);
671                 res = ERROR;
672                 continue;
673             }
674 
675             // field must not be static
676             final int fieldMods = field.getModifiers();
677             if ((fieldMods & Modifier.STATIC) != 0) {
678                 out.println("        !!! ERROR: potential jdo accessor/mutator method matches a static field");
679                 out.println("            found method: " + method);
680                 out.println("            found field:  " + field);
681                 methods.remove(method);
682                 res = ERROR;
683                 continue;
684             }
685             
686             // field must be managed by jdo metadata
687             if (jdoMeta != null && !jdoMeta.isManagedField(classPath, fieldName)) {
688                 out.println("        !!! ERROR: potential jdo accessor/mutator method matches a non-managed field");
689                 out.println("            found method: " + method);
690                 out.println("            found field:  " + field);
691                 methods.remove(method);
692                 res = ERROR;
693                 continue;
694             }
695 
696             managedFields.add(field);
697         }
698         
699         // find managed field candidates by jdo meta-data
700         final String[] metaFieldNames = (jdoMeta != null
701                                          ? jdoMeta.getManagedFields(classPath)
702                                          : new String[]{});
703         for (int i = 0; i < metaFieldNames.length; i++) {
704             final String fieldName = metaFieldNames[i];
705             
706             // find declared field
707             final Field field;
708             try {
709                 field = classObject.getDeclaredField(fieldName);
710                 fields.remove(field);
711             } catch (NoSuchFieldException ex) {
712                 out.println("        !!! ERROR: field defined by jdo meta-data not declared in class");
713                 out.println("            no declared field: " + fieldName);
714                 res = ERROR;
715                 continue;
716             }
717 
718             // field must not be static
719             final int fieldMods = field.getModifiers();
720             if ((fieldMods & Modifier.STATIC) != 0) {
721 
722                 out.println("        !!! ERROR: field defined by jdo meta-data is declared static in class");
723                 out.println("            static field:  " + field);
724                 res = ERROR;
725                 continue;
726             }
727             
728             managedFields.add(field);
729         }
730 
731         // check accessor/mutator methods for managed field candidates
732         final HashSet methodSet = new HashSet(methods);
733         for (Iterator i = managedFields.iterator(); i.hasNext();) {
734             final Field field = (Field)i.next();
735             final String fieldName = field.getName();
736             final Class fieldType = field.getType();
737             final int fieldMods = field.getModifiers();
738 
739             // accessor's and mutator's signature
740             final int mods = (Modifier.STATIC
741                               | (fieldMods
742                                  & (Modifier.PUBLIC
743                                     | Modifier.PROTECTED
744                                     | Modifier.PRIVATE)));
745             final String accessorName = "jdoGet" + fieldName;
746             final Class[] accessorParams = new Class[]{classObject};
747             final Class accessorReturnType = fieldType;
748             final String mutatorName = "jdoSet" + fieldName;
749             final Class[] mutatorParams = new Class[]{classObject, fieldType};
750             final Class mutatorReturnType = void.class;
751             final Class[] exeptions = new Class[]{};
752 
753             // find accessor
754             final int r0 = hasMethod(out,
755                                      mods,
756                                      accessorReturnType,
757                                      accessorName,
758                                      accessorParams,
759                                      exeptions);
760             if (r0 < NEGATIVE) {
761                 res = ERROR;
762             } else if (r0 == NEGATIVE) {
763                 out.println("        !!! ERROR: missing or incorrect jdo accessor for declared field");
764                 out.println("            field:  " + field);
765                 out.println("            expected: "
766                             + toString(mods,
767                                        accessorReturnType,
768                                        accessorName,
769                                        accessorParams,
770                                        exeptions));
771                 for (Iterator j = methodSet.iterator(); j.hasNext();) {
772                     final Method method = (Method)j.next();
773                     if (method.getName().equals(accessorName)) {
774                         out.println("            found:  " + method);
775                         methods.remove(method);
776                     }
777                 }
778                 res = ERROR;
779             }
780 
781             // find mutator
782             final int r1 = hasMethod(out,
783                                      mods,
784                                      mutatorReturnType,
785                                      mutatorName,
786                                      mutatorParams,
787                                      exeptions);
788             if (r1 < NEGATIVE) {
789                 res = ERROR;
790             } else if (r1 == NEGATIVE) {
791                 out.println("        !!! ERROR: missing or incorrect jdo mutator for declared field");
792                 out.println("            field:  " + field);
793                 out.println("            expected: "
794                             + toString(mods,
795                                        mutatorReturnType,
796                                        mutatorName,
797                                        mutatorParams,
798                                        exeptions));
799                 for (Iterator j = methodSet.iterator(); j.hasNext();) {
800                     final Method method = (Method)j.next();
801                     if (method.getName().equals(accessorName)) {
802                         out.println("            found:  " + method);
803                         methods.remove(method);
804                     }
805                 }
806                 res = ERROR;
807             }
808 
809             // have found legal accessor/mutator pair
810             if (res == NEGATIVE) {
811                 res = AFFIRMATIVE;
812             }
813         }
814 
815         return res;
816     }    
817 
818     private int hasInstanceCallbacks(PrintWriter out)
819     {
820         affirm(ERROR < NEGATIVE && NEGATIVE < AFFIRMATIVE);
821         affirm(classObject);
822 
823         final int nofFeatures = 5;
824         final int[] r = new int[nofFeatures];
825         {
826             int i = 0;
827 
828             r[i++] = implementsInterface(
829                 out,
830                 instanceCallbacksClass);
831 
832             r[i++] = hasMethod(
833                 out,
834                 Modifier.PUBLIC,
835                 void.class,
836                 "jdoPostLoad",
837                 new Class[]{});
838 
839             r[i++] = hasMethod(
840                 out,
841                 Modifier.PUBLIC,
842                 void.class,
843                 "jdoPreStore",
844                 new Class[]{});
845 
846             r[i++] = hasMethod(
847                 out,
848                 Modifier.PUBLIC,
849                 void.class,
850                 "jdoPreClear",
851                 new Class[]{});
852 
853             r[i++] = hasMethod(
854                 out,
855                 Modifier.PUBLIC,
856                 void.class,
857                 "jdoPreDelete",
858                 new Class[]{});
859 
860             affirm(i == nofFeatures);
861         }
862 
863         return evaluate(1, r);
864     }
865 
866     private int testPCFeasibility(PrintWriter out)
867     {
868         affirm(classObject);
869 
870         int status = AFFIRMATIVE;
871 
872         final int mods = classObject.getModifiers();
873 
874         // PC class must provide default constructor
875         StringWriter s = new StringWriter();
876         final int hasCtor = hasConstructor(new PrintWriter(s),
877                                            0,
878                                            new Class[]{});
879         if (hasCtor <= NEGATIVE) {
880             status = ERROR;
881         } else {
882             if (verbose) {
883                 out.print(s.toString());
884             }
885         }
886 
887         // PC class must not be an interface type
888         if (classObject.isInterface()) {
889             out.println("        !!! ERROR: class is interface");
890             status = ERROR;
891         } else {
892             if (verbose) {
893                 out.println("        +++ is not an interface");
894             }
895         }
896 
897         // PC class may be abstract if not instantiated at class
898         // registration with JDOImplHelper
899         //if (Modifier.isAbstract(mods)) {
900         //    out.println("        !!! ERROR: class is abstract");
901         //    status = ERROR;
902         //} else {
903         //    if (verbose) {
904         //        out.println("        +++ is not abstract");
905         //    }
906         //}
907 
908         // PC class cannot be an inner classes because of instantiation
909         // from static context (registration with JDOImplHelper)
910         if (classObject.getDeclaringClass() != null
911             && !Modifier.isStatic(mods)) {
912             out.println("        !!! ERROR: class is inner class");
913             status = ERROR;
914         } else {
915             if (verbose) {
916                 out.println("        +++ is not an inner class");
917             }
918         }
919 
920         // PC class must not have transient package prefix
921         for (int i = 0; i < transientPrefixes.length; i++) {
922             final String typePrefix = transientPrefixes[i];
923             if (className.startsWith(typePrefix)) {
924                 out.println("        !!! ERROR: class is in package: "
925                             + typePrefix + "..");
926                 status = ERROR;
927             } else {
928                 if (verbose) {
929                     out.println("        +++ is not in package: "
930                                 + typePrefix + "..");
931                 }
932             }
933         }
934         
935         //^olsen: PC class must not be SCO type?
936 
937         // PC class is better not a Throwable
938         //if (Throwable.class.isAssignableFrom(classObject)) {
939         //    out.println("        !!! ERROR: class extends Throwable");
940         //    status = ERROR;
941         //} else {
942         //    if (verbose) {
943         //        out.println("        +++ does not extend Throwable");
944         //    }
945         //}
946         
947         // PC class can have any access modifier; JDO runtime accesses it
948         // through PersistenceCapable interface only
949         //if (!Modifier.isPublic(mods)) {
950         //    out.println("        !!! ERROR: class is not public");
951         //    status = ERROR;
952         //} else {
953         //    if (verbose) {
954         //        out.println("        +++ is public");
955         //    }
956         //}
957 
958         // pathological: PC class must not be a primitive type
959         if (classObject.isPrimitive()) {
960             out.println("        !!! ERROR: class is of primitive type");
961             status = ERROR;
962         }
963 
964         // pathological: PC class must not be an array type
965         if (classObject.isArray()) {
966             out.println("        !!! ERROR: class is of array type");
967             status = ERROR;
968         }
969 
970         // pathological: PC class must have superclass
971         if (classObject.getSuperclass() == null) {
972             out.println("        !!! ERROR: class doesn't have super class");
973             status = ERROR;
974         }
975 
976         return status;
977     }
978 
979     private int hasNoIllegalJdoMembers(PrintWriter out)
980     {
981         affirm(classObject);
982         int res = AFFIRMATIVE;
983         
984         for (Iterator i = new HashSet(methods).iterator(); i.hasNext();) {
985             final Method method = (Method)i.next();
986             final String name = method.getName();
987             if (name.startsWith("jdo")) {
988                 out.println("        !!! ERROR: illegal jdo method");
989                 out.println("            found method: " + method);
990                 methods.remove(method);
991                 res = ERROR;
992             }
993         }
994         
995         for (Iterator i = new HashSet(fields).iterator(); i.hasNext();) {
996             final Field field = (Field)i.next();
997             final String name = field.getName();
998             if (name.startsWith("jdo")) {
999                 out.println("        !!! ERROR: illegal jdo field");
1000                 out.println("            found field: " + field);
1001                 fields.remove(field);
1002                 res = ERROR;
1003             }
1004         }
1005 
1006         return res;
1007     }
1008     
1009     private int testAugmentation(PrintWriter out)
1010         throws EnhancerMetaDataUserException, EnhancerMetaDataFatalError
1011     {
1012         affirm(ERROR < NEGATIVE && NEGATIVE < AFFIRMATIVE);
1013         affirm(classObject);
1014         affirm(className);
1015 
1016         // check class-specific enhancement
1017         StringWriter s = new StringWriter();
1018         int r0 = hasSpecificAugmentation(new PrintWriter(s));
1019         //System.out.println("hasSpecificAugmentation = " + r0);
1020         if (r0 < NEGATIVE) {
1021             out.println("    !!! ERROR: inconsistent \"class-specific\" augmentation");
1022             out.println(s.toString());
1023             r0 = ERROR;
1024         } else if (r0 == NEGATIVE) {
1025             if (jdoMeta != null && jdoMeta.isPersistenceCapableClass(classPath)) {
1026                 out.println("    !!! ERROR: missing \"class-specific\" augmentation");
1027                 out.println(s.toString());
1028                 r0 = ERROR;
1029             } else {
1030                 if (verbose) {
1031                     out.println("    --- no \"class-specific\" augmentation");
1032                     out.println(s.toString());
1033                 }
1034             }
1035         } else {
1036             affirm(r0 > NEGATIVE);
1037             if (jdoMeta != null && !jdoMeta.isPersistenceCapableClass(classPath)) {
1038                 out.println("    !!! ERROR: unexpected \"class-specific\" augmentation");
1039                 out.println(s.toString());
1040                 r0 = ERROR;
1041             } else {
1042                 if (verbose) {
1043                     out.println("    +++ has correct \"class-specific\" augmentation");
1044                     out.println(s.toString());
1045                 }
1046             }
1047         }
1048 
1049         // check key-handling enhancement
1050         s = new StringWriter();
1051         int r1 = hasKeyHandlingAugmentation(new PrintWriter(s));
1052         //System.out.println("hasKeyHandlingAugmentation = " + r1);
1053         if (r1 < NEGATIVE) {
1054             out.println("    !!! ERROR: inconsistent \"key-handling\" augmentation");
1055             out.println(s.toString());
1056             r1 = ERROR;
1057         } else if (r1 == NEGATIVE) {
1058             if (jdoMeta != null
1059                 && (jdoMeta.isPersistenceCapableClass(classPath)
1060                     && jdoMeta.getKeyClass(classPath) != null)) {
1061                 out.println("    !!! ERROR: missing \"key-handling\" augmentation");
1062                 out.println(s.toString());
1063                 r1 = ERROR;
1064             } else {
1065                 if (verbose) {
1066                     out.println("    --- no \"key-handling\" augmentation");
1067                     out.println(s.toString());
1068                 }
1069             }
1070         } else {
1071             affirm(r1 > NEGATIVE);
1072             if (r0 == NEGATIVE
1073                 || (jdoMeta != null
1074                     && (!jdoMeta.isPersistenceCapableRootClass(classPath)
1075                         && jdoMeta.getKeyClass(classPath) == null))) {
1076                 out.println("    !!! ERROR: unexpected \"key-handling\" augmentation");
1077                 out.println(s.toString());
1078                 r1 = ERROR;
1079             } else {
1080                 if (verbose) {
1081                     out.println("    +++ has correct \"key-handling\" augmentation");
1082                     out.println(s.toString());
1083                 }
1084             }
1085         }
1086         affirm(r0 != NEGATIVE || r1 <= NEGATIVE);
1087         
1088         // check generic enhancement
1089         s = new StringWriter();
1090         int r2 = hasGenericAugmentation(new PrintWriter(s));
1091         //System.out.println("hasGenericAugmentation = " + r2);
1092         if (r2 < NEGATIVE) {
1093             out.println("    !!! ERROR: inconsistent \"generic\" augmentation");
1094             out.println(s.toString());
1095             r2 = ERROR;
1096         } else if (r2 == NEGATIVE) {
1097             if (jdoMeta != null
1098                 && jdoMeta.isPersistenceCapableRootClass(classPath)) {
1099                 out.println("    !!! ERROR: missing \"generic\" augmentation");
1100                 out.println(s.toString());
1101                 r2 = ERROR;
1102             } else {
1103                 if (verbose) {
1104                     out.println("    --- no \"generic\" augmentation");
1105                     out.println(s.toString());
1106                 }
1107             }
1108         } else {
1109             affirm(r2 > NEGATIVE);
1110             if (r0 == NEGATIVE
1111                 || (jdoMeta != null
1112                     && !jdoMeta.isPersistenceCapableRootClass(classPath))) {
1113                 out.println("    !!! ERROR: unexpected \"generic\" augmentation");
1114                 out.println(s.toString());
1115                 r2 = ERROR;
1116             } else {
1117                 if (verbose) {
1118                     out.println("    +++ has correct \"generic\" augmentation");
1119                     out.println(s.toString());
1120                 }
1121             }
1122         }
1123         affirm(r0 != NEGATIVE || r2 <= NEGATIVE);
1124         
1125         // check accessor/mutator enhancement
1126         s = new StringWriter();
1127         int r3 = hasAccessorMutators(new PrintWriter(s));
1128         //System.out.println("hasAccessorMutators = " + r3);
1129         if (r3 < NEGATIVE) {
1130             out.println("    !!! ERROR: inconsistent \"accessor/mutator\" augmentation");
1131             out.println(s.toString());
1132         } else if (r3 == NEGATIVE) {
1133             if (verbose) {
1134                 out.println("    --- no \"accessor/mutator\" augmentation");
1135                 out.println(s.toString());
1136             }
1137         } else {
1138             affirm(r3 > NEGATIVE);
1139             if (r0 == NEGATIVE) {
1140                 out.println("    !!! ERROR: unexpected \"accessor/mutator\" augmentation");
1141                 out.println(s.toString());
1142                 r3 = ERROR;
1143             } else {
1144                 if (verbose) {
1145                     out.println("    +++ has correct \"accessor/mutator\" augmentation");
1146                     out.println(s.toString());
1147                 }
1148             }
1149         }        
1150         affirm(r0 != NEGATIVE || r3 <= NEGATIVE);
1151 
1152         // check user-defined instance callback features
1153         s = new StringWriter();
1154         int r4 = hasInstanceCallbacks(new PrintWriter(s));
1155         //System.out.println("hasInstanceCallbacks = " + r4);
1156         if (r4 < NEGATIVE) {
1157             out.println("    !!! ERROR: inconsistent instance callback features");
1158             out.println(s.toString());
1159         } else if (r4 == NEGATIVE) {
1160             if (verbose) {
1161                 out.println("    --- no instance callback features");
1162                 out.println(s.toString());
1163             }
1164         } else {
1165             affirm(r4 > NEGATIVE);
1166             if (verbose) {
1167                 out.println("    +++ has instance callback features");
1168                 out.println(s.toString());
1169             }
1170         }        
1171 
1172         // check for illegal jdo* member enhancement
1173         s = new StringWriter();
1174         int r5 = hasNoIllegalJdoMembers(new PrintWriter(s));
1175         if (r5 <= NEGATIVE) {
1176             out.println("    !!! ERROR: illegal jdo member");
1177             out.println(s.toString());
1178         } else {
1179             if (verbose) {
1180                 out.println("    +++ no illegal jdo member");
1181                 out.println(s.toString());
1182             }
1183         }        
1184 
1185         // return if error so far
1186         if (r0 < NEGATIVE || r1 < NEGATIVE || r2 < NEGATIVE || r3 < NEGATIVE
1187             || r4 < NEGATIVE || r5 < NEGATIVE) {
1188             return ERROR;
1189         }
1190 
1191         // return if class not PC and no error
1192         if (r0 == NEGATIVE) {
1193             affirm(r1 == NEGATIVE);
1194             affirm(r2 == NEGATIVE);
1195             affirm(r3 == NEGATIVE);
1196             affirm(r4 >= NEGATIVE);
1197             affirm(r5 > NEGATIVE);
1198             return NEGATIVE;
1199         }
1200 
1201         // check feasibility if class PC
1202         s = new StringWriter();
1203         int r6 = testPCFeasibility(new PrintWriter(s));
1204         if (r6 <= NEGATIVE) {
1205             out.println("    !!! not feasible for persistence-capability");
1206             out.println(s.toString());
1207             r6 = ERROR;
1208         } else {
1209             if (verbose) {
1210                 out.println("    +++ is feasible for persistence-capability");
1211                 out.println(s.toString());
1212             }
1213         }
1214         
1215         // return with error if class not PC feasible
1216         if (r6 < NEGATIVE) {
1217             return ERROR;
1218         }
1219 
1220         // class PC and no errors with augmentation
1221         return AFFIRMATIVE;
1222     }
1223 
1224     private int testLoadingClass(PrintWriter out)
1225     {
1226         try {
1227             classObject = classLoader.loadClass(className);
1228             out.println("    +++ loaded class");
1229         } catch (LinkageError err) {
1230             out.println("    !!! ERROR: linkage error when loading class: "
1231                         + className);
1232             out.println("        error: " + err);
1233             return ERROR;
1234         } catch (ClassNotFoundException ex) {
1235             out.println("    !!! ERROR: class not found: " + className);
1236             out.println("        exception: " + ex);
1237             return ERROR;
1238         }
1239 
1240         try {
1241             fields = new HashSet();
1242             fields.addAll(Arrays.asList(classObject.getDeclaredFields()));
1243             methods = new HashSet();
1244             methods.addAll(Arrays.asList(classObject.getDeclaredMethods()));
1245         } catch (SecurityException ex) {
1246             affirm(false);
1247             return ERROR;
1248         }   
1249         return AFFIRMATIVE;
1250     }
1251 
1252     private int test(PrintWriter out,
1253                      String className)
1254         throws EnhancerMetaDataUserException, EnhancerMetaDataFatalError
1255     {
1256         affirm(className);
1257         this.className = className;
1258         this.classPath = className.replace('.', '/');
1259 
1260 
1261         if (verbose) {
1262             out.println("-------------------------------------------------------------------------------");
1263             out.println();
1264             out.println("Test class for augmentation: "
1265                         + className + " ...");
1266         }
1267         
1268         // check loading class
1269         StringWriter s = new StringWriter();
1270         if (testLoadingClass(new PrintWriter(s)) <= NEGATIVE) {
1271             out.println();
1272             out.println("!!! ERROR: failed loading class: " + className);
1273             out.println(s.toString());
1274             return ERROR;
1275         }
1276 
1277         if (verbose) {
1278             out.println();
1279             out.println("+++ loaded class: " + className);
1280             out.println(s.toString());
1281         }
1282         
1283         // check augmentation
1284         s = new StringWriter();
1285         final int r = testAugmentation(new PrintWriter(s));
1286         if (r < NEGATIVE) {
1287             out.println();
1288             out.println("!!! ERROR: incorrect augmentation: " + className);
1289             out.println(s.toString());
1290             return ERROR;
1291         }
1292         
1293         if (r == NEGATIVE) {
1294             out.println();
1295             out.println("--- class not augmented: " + className);
1296         } else {
1297             out.println();
1298             out.println("+++ class augmented: " + className);
1299         }
1300         if (verbose) {
1301             out.println(s.toString());
1302         }
1303 
1304         return r;
1305     }
1306 
1307     protected int test(PrintWriter out,
1308                        boolean verbose,
1309                        List classNames)
1310     {
1311         affirm(classNames);
1312         this.verbose = verbose;
1313         final int all = classNames.size();
1314 
1315         out.println();
1316         out.println("AugmentationTest: Testing Classes for JDO Persistence-Capability Enhancement");
1317 
1318         int nofFailed = 0;
1319         for (int i = 0; i < all; i++) {
1320             if (test(out, (String)classNames.get(i)) < NEGATIVE) {
1321                 nofFailed++;
1322             }
1323         }
1324         final int nofPassed = all - nofFailed;
1325 
1326         out.println();
1327         out.println("AugmentationTest: Summary:  TESTED: " + all
1328                     + "  PASSED: " + nofPassed
1329                     + "  FAILED: " + nofFailed);
1330         return nofFailed;
1331     }
1332     
1333     // ----------------------------------------------------------------------
1334 
1335     /***
1336      * Initializes all components.
1337      */
1338     protected void init()
1339         throws EnhancerFatalError, EnhancerUserException
1340     {
1341         super.init();
1342         if (!options.classFileNames.isEmpty()
1343             || !options.archiveFileNames.isEmpty()) {
1344             throw new EnhancerFatalError("Sorry, this test right now only support class name arguments, not class or archive files.");
1345         }
1346         affirm(classes != null);
1347         try {
1348             classLoader = classes.getClassLoader();
1349             persistenceManagerClass
1350                 = classLoader.loadClass("javax.jdo.PersistenceManager");
1351             instanceCallbacksClass
1352                 = classLoader.loadClass("javax.jdo.InstanceCallbacks");
1353             persistenceCapableClass
1354                 = classLoader.loadClass("javax.jdo.spi.PersistenceCapable");
1355             objectIdFieldSupplierClass
1356                 = classLoader.loadClass("javax.jdo.spi.PersistenceCapable$ObjectIdFieldSupplier");
1357             objectIdFieldConsumerClass
1358                 = classLoader.loadClass("javax.jdo.spi.PersistenceCapable$ObjectIdFieldConsumer");
1359             stateManagerClass
1360                 = classLoader.loadClass("javax.jdo.spi.StateManager");
1361         } catch (Exception ex) {
1362             throw new EnhancerFatalError(ex);
1363         }
1364     }
1365     
1366     /***
1367      * Run the augmentation test.
1368      */
1369     protected int process()
1370     {
1371         //^olsen: Unfortunately, this test cannot reasonably deal with
1372         // java classfiles passed as command line arguments but only with
1373         // archive files (.zip/.jar) or a source-path argument.
1374         // For this restriction, the inherited parsing/checking of options
1375         // and the usage-help needs to be overriden/corrected.
1376         // --> BaseOptions.check(), printUsageHeader() ...
1377 
1378         //^olsen: to be extended for zip/jar arguments
1379         return test(out, options.verbose.value, options.classNames);
1380     }
1381 
1382     /***
1383      * Runs this class
1384      */
1385     static public void main(String[] args)
1386     {
1387         final PrintWriter out = new PrintWriter(System.out, true);
1388         out.println("--> AugmentationTest.main()");
1389         final AugmentationTest main = new AugmentationTest(out, out);
1390         int res = main.run(args);
1391         out.println("<-- AugmentationTest.main(): exit = " + res);
1392         System.exit(res);
1393     }
1394 }