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