View Javadoc

1   package org.kuali.student.r1.common.dictionary.service.impl;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
5   import java.util.Comparator;
6   import java.util.HashSet;
7   import java.util.LinkedHashMap;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import org.kuali.student.r1.common.dictionary.dto.CaseConstraint;
13  import org.kuali.student.r1.common.dictionary.dto.CommonLookupParam;
14  import org.kuali.student.r1.common.dictionary.dto.Constraint;
15  import org.kuali.student.r1.common.dictionary.dto.DataType;
16  import org.kuali.student.r1.common.dictionary.dto.FieldDefinition;
17  import org.kuali.student.r1.common.dictionary.dto.LookupConstraint;
18  import org.kuali.student.r1.common.dictionary.dto.ObjectStructureDefinition;
19  import org.kuali.student.r1.common.dictionary.dto.RequiredConstraint;
20  import org.kuali.student.r1.common.dictionary.dto.ValidCharsConstraint;
21  import org.kuali.student.r1.common.dictionary.dto.WhenConstraint;
22  
23  public class DictionaryDiscrepencyTester
24  {
25  
26  	private boolean printOnlyDiscrepencies = false;
27   private StringBuilder builder = new StringBuilder (5000);
28   private ObjectStructureDefinition os;
29   private String rowSeperator = "\n";
30   private String colSeperator = "|";
31   private String name;
32   private String className;
33   private boolean processSubstructures = false;
34   private int level;
35   private Map<String, ObjectStructureDefinition> subStructuresToProcess =
36                                                  new LinkedHashMap ();
37   private Set<ObjectStructureDefinition> subStructuresAlreadyProcessed;
38  
39   public DictionaryDiscrepencyTester (String name,
40                               String className,
41                               ObjectStructureDefinition os,
42                               Set<ObjectStructureDefinition> subStructuresAlreadyProcessed,
43                               int level,
44                               boolean processSubstructures, boolean printOnlyDiscrepencies)
45   {
46    this.name = name;
47    this.className = className;
48    this.os = os;
49    this.subStructuresAlreadyProcessed = subStructuresAlreadyProcessed;
50    this.level = level;
51    this.processSubstructures = processSubstructures;
52    this.printOnlyDiscrepencies = printOnlyDiscrepencies;
53   }
54   public static final String UNBOUNDED = "unbounded";
55  
56   public String getRowSeperator ()
57   {
58    return rowSeperator;
59   }
60  
61   public void setRowSeperator (String rowSeperator)
62   {
63    this.rowSeperator = rowSeperator;
64   }
65  
66   public String getColSeparator ()
67   {
68    return colSeperator;
69   }
70  
71   public void setColSeparator (String separator)
72   {
73    this.colSeperator = separator;
74   }
75  
76   private String pad (String str, int size)
77   {
78    StringBuilder padStr = new StringBuilder (size);
79    padStr.append (str);
80    while (padStr.length () < size)
81    {
82     padStr.append (' ');
83    }
84    return padStr.toString ();
85   }
86  
87   public List<String> formatForWiki ()
88   {
89    builder.append (rowSeperator);
90  //  builder.append ("======= start dump of object structure definition ========");
91    builder.append (rowSeperator);
92    builder.append ("h" + level + ". " + calcNotSoSimpleName (name));
93    builder.append ("{anchor:" + name + "}");
94    builder.append (rowSeperator);
95    if (className != null)
96    {
97     builder.append ("The corresponding java class for this dictionary object is "
98                     + os.getName ());
99    }
100   //
101 //  if (!printOnlyDiscrepencies) {
102 //	
103 //
104 //  if (os.isHasMetaData ())
105 //  {
106 //   builder.append (rowSeperator);
107 //   builder.append ("The dictionary says this object holds metadata");
108 //  }
109 //  builder.append (rowSeperator);
110 //  builder.append (colSeperator);
111 //  builder.append (colSeperator);
112 //  builder.append ("Field");
113 //  builder.append (colSeperator);
114 //  builder.append (colSeperator);
115 //  builder.append ("Required?");
116 //  builder.append (colSeperator);
117 //  builder.append (colSeperator);
118 //  builder.append ("DataType");
119 //  builder.append (colSeperator);
120 //  builder.append (colSeperator);
121 //  builder.append ("Length");
122 //  builder.append (colSeperator);
123 //  builder.append (colSeperator);
124 //  builder.append ("Dynamic or Hidden");
125 //  builder.append (colSeperator);
126 //  builder.append (colSeperator);
127 //  builder.append ("Default");
128 //  builder.append (colSeperator);
129 //  builder.append (colSeperator);
130 //  builder.append ("Repeats?");
131 //  builder.append (colSeperator);
132 //  builder.append (colSeperator);
133 //  builder.append ("Valid Characters");
134 //  builder.append (colSeperator);
135 //  builder.append (colSeperator);
136 //  builder.append ("Lookup");
137 //  builder.append (colSeperator);
138 //  builder.append (colSeperator);
139 //  builder.append ("Cross Field");
140 //  builder.append (colSeperator);
141 //  builder.append (colSeperator);
142 //  builder.append (rowSeperator);
143 //  for (FieldDefinition fd : getSortedFields ())
144 //  {
145 //   builder.append (colSeperator);
146 //   builder.append (pad (fd.getName (), 30));
147 //   builder.append (colSeperator);
148 //   builder.append (pad (calcRequired (fd), 10));
149 //   builder.append (colSeperator);
150 //   builder.append (pad (calcDataType (fd), 25));
151 //   builder.append (colSeperator);
152 //   builder.append (pad (calcLength (fd), 15));
153 //   builder.append (colSeperator);
154 //   builder.append (pad (calcDynamicOrHidden (fd), 7));
155 //   builder.append (colSeperator);
156 //   builder.append (pad (calcDefaultValue (fd), 15));
157 //   builder.append (colSeperator);
158 //   builder.append (calcRepeating (fd));
159 //   builder.append (colSeperator);
160 //   builder.append (calcValidCharsMinMax (fd));
161 //   builder.append (colSeperator);
162 //   builder.append (calcLookup (fd));
163 //   builder.append (colSeperator);
164 //   builder.append (calcCrossField (fd));
165 //   builder.append (colSeperator);
166 //   builder.append (rowSeperator);
167 //  }
168 //  }
169   List<String> discrepancies = null;
170   if (className == null)
171   {
172    discrepancies = new ArrayList (1);
173    discrepancies.add (
174      "There is no corresponding java class for this dictionary object structure");
175   }
176   else
177   {
178    discrepancies = new Dictionary2BeanComparer (className, os).compare ();
179   }
180   if (discrepancies.size () > 0)
181   {
182    builder.append ("h" + (level + 1) + ". " + discrepancies.size ()
183                    + " discrepancie(s) found in "
184                    + calcSimpleName (name));
185    builder.append (rowSeperator);
186    builder.append (formatAsString (discrepancies));
187    builder.append (rowSeperator);
188   }
189 
190 //  builder.append ("======= end dump of object structure definition ========");
191   builder.append (rowSeperator);
192   Set<ObjectStructureDefinition> subStructuresAlreadyProcessedBeforeProcessingSubStructures =
193                                  new HashSet ();
194   subStructuresAlreadyProcessedBeforeProcessingSubStructures.addAll (
195     subStructuresAlreadyProcessed);
196   for (String subName : this.subStructuresToProcess.keySet ())
197   {
198    ObjectStructureDefinition subOs = this.subStructuresToProcess.get (subName);
199    if ( ! subStructuresAlreadyProcessedBeforeProcessingSubStructures.contains (
200      subOs))
201    {
202     this.subStructuresAlreadyProcessed.add (subOs);
203 //    System.out.println ("formatting substructure " + subName);
204     Class<?> subClazz = getClass (subOs.getName ());
205     DictionaryDiscrepencyTester formatter =
206                         new DictionaryDiscrepencyTester (subName, subOs.getName (),
207                                                  subOs,
208                                                  subStructuresAlreadyProcessed,
209                                                  level + 1,
210                                                  this.processSubstructures, this.printOnlyDiscrepencies);
211     builder.append (formatter.formatForWiki ());
212     builder.append (rowSeperator);
213    }
214   }
215 
216   return discrepancies;
217  }
218 
219 
220 
221  private List<FieldDefinition> getSortedFields ()
222  {
223    List<FieldDefinition> fields = os.getAttributes ();
224    Collections.sort (fields, new FieldDefinitionNameComparator ());
225    return fields;
226  }
227 
228  private static class FieldDefinitionNameComparator implements Comparator <FieldDefinition>
229  {
230   @Override
231   public int compare (FieldDefinition o1, FieldDefinition o2)
232   {
233    return o1.getName ().toLowerCase ().compareTo (o2.getName ().toLowerCase ());
234   }
235 
236  }
237 
238  private Class getClass (String className)
239  {
240   try
241   {
242    return Class.forName (className);
243   }
244   catch (ClassNotFoundException ex)
245   {
246    return null;
247 //   throw new IllegalArgumentException ("Could not find class for " + className);
248   }
249  }
250 
251  private String formatAsString (List<String> discrepancies)
252  {
253   int i = 0;
254   StringBuilder builder = new StringBuilder ();
255   for (String discrep : discrepancies)
256   {
257    i ++;
258    builder.append (i + ". " + discrep + "\n");
259   }
260   return builder.toString ();
261  }
262 
263  private String calcDataType (FieldDefinition fd)
264  {
265   if (fd.getDataType ().equals (DataType.COMPLEX))
266   {
267    if (fd.getDataObjectStructure () == null)
268    {
269     throw new IllegalArgumentException (
270       fd.getName () + " is complex but does not have a sub-structure defined");
271    }
272    Class subClazz = this.getClass (fd.getDataObjectStructure ().getName ());
273    String subStrucName = calcComplexSubStructureName (fd);
274    // process if explicity asking for substructures OR the field is a freestanding field
275    // so it won't be processed by just processing all of the DTO's and their sub-objects
276    if (this.processSubstructures || subClazz == null)
277    {
278     if ( ! this.subStructuresAlreadyProcessed.contains (
279       fd.getDataObjectStructure ()))
280     {
281 //     System.out.println ("Adding " + subStrucName + " to set to be processed");
282      this.subStructuresToProcess.put (subStrucName, fd.getDataObjectStructure ());
283     }
284    }
285    return "[" + calcNotSoSimpleName (subStrucName) + "|#" + subStrucName + "]";
286   }
287   return fd.getDataType ().toString ();
288  }
289 
290  private String calcDefaultValue (FieldDefinition fd)
291  {
292   if (fd.getDefaultValue () != null)
293   {
294    return fd.getDefaultValue ().toString ();
295   }
296   return " ";
297  }
298 
299 
300  private String calcDynamicOrHidden (FieldDefinition fd)
301  {
302   if (fd.isHide ())
303   {
304    if (fd.isDynamic ())
305    {
306     return "dynamic and hidden";
307    }
308    return "hidden";
309   }
310   if (fd.isDynamic ())
311   {
312    return "dynamic";
313   }
314   return " ";
315  }
316 
317  private String calcComplexSubStructureName (FieldDefinition fd)
318  {
319   if (this.processSubstructures)
320   {
321    return name + "." + fd.getName () + "." + calcSimpleName (
322      fd.getDataObjectStructure ().getName ());
323   }
324   return calcSimpleName (fd.getDataObjectStructure ().getName ());
325  }
326 
327  private String calcSimpleName (String name)
328  {
329   if (name.lastIndexOf (".") != -1)
330   {
331    name = name.substring (name.lastIndexOf (".") + 1);
332   }
333   return name;
334  }
335 
336  private String calcNotSoSimpleName (String name)
337  {
338   if (name.lastIndexOf (".") == -1)
339   {
340    return name;
341   }
342   String simpleName = calcSimpleName (name);
343   String fieldName = calcSimpleName (name.substring (0, name.length ()
344                                                         - simpleName.length ()
345                                                         - 1));
346   return fieldName + "." + simpleName;
347  }
348 
349  private String calcRequired (FieldDefinition fd)
350  {
351   if (fd.getMaxOccurs () != null)
352   {
353    if ( ! fd.getMaxOccurs ().equals (UNBOUNDED))
354    {
355     if (Integer.parseInt (fd.getMaxOccurs ()) == 0)
356     {
357      return "Not allowed";
358     }
359    }
360   }
361 
362   if (fd.getMinOccurs () != null)
363   {
364    if (fd.getMinOccurs () >= 1)
365    {
366     return "required";
367    }
368   }
369 
370   return " ";
371 //  return "optional";
372  }
373  private static final String LINK_TO_DEFINITIONS =
374                              "KULSTG:Formatted View of Base Dictionary#Valid Character Definitions";
375 
376  private String calcValidChars (FieldDefinition fd)
377  {
378   if (fd.getValidChars () == null)
379   {
380    return " ";
381   }
382   return calcValidChars (fd.getValidChars ());
383  }
384 
385  private String calcValidChars (ValidCharsConstraint cons)
386  {
387   String labelKey = cons.getLabelKey ();
388   if (labelKey == null)
389   {
390    labelKey = "validation.validChars";
391   }
392   String validChars = escapeWiki (cons.getValue ());
393   String descr = "[" + labelKey + "|" + LINK_TO_DEFINITIONS + "]" + "\\\\\n"
394                  + validChars;
395   return descr;
396  }
397 
398  private String escapeWiki (String str)
399  {
400   StringBuilder bldr = new StringBuilder (str.length ());
401   for (int i = 0; i < str.length (); i ++)
402   {
403    char c = str.charAt (i);
404    switch (c)
405    {
406     case '{':
407     case '}':
408     case '[':
409     case ']':
410     case '|':
411      bldr.append ('\\');
412    }
413    bldr.append (c);
414   }
415   return bldr.toString ();
416  }
417 
418  private String calcLookup (FieldDefinition fd)
419  {
420   if (fd.getLookupDefinition () == null)
421   {
422    return " ";
423   }
424   return calcLookup (fd.getLookupDefinition ());
425  }
426 
427  private String calcLookup (LookupConstraint lc)
428  {
429   StringBuilder bldr = new StringBuilder ();
430   bldr.append (lc.getId ());
431 //  this is the search description not the lookup description
432 //  builder.append (" - ");
433 //  builder.append (lc.getDesc ());
434   String and = "";
435   bldr.append ("\\\\");
436   bldr.append ("\n");
437   bldr.append ("Implemented using search: ");
438   String searchPage = calcWikiSearchPage (lc.getSearchTypeId ());
439   bldr.append ("[" + lc.getSearchTypeId () + "|" + searchPage + "#"
440                   + lc.getSearchTypeId () + "]");
441   List<CommonLookupParam> configuredParameters = filterConfiguredParams (
442     lc.getParams ());
443   if (configuredParameters.size () > 0)
444   {
445    bldr.append ("\\\\");
446    bldr.append ("\n");
447    bldr.append (" where ");
448    and = "";
449    for (CommonLookupParam param : configuredParameters)
450    {
451     bldr.append (and);
452     and = " and ";
453     bldr.append (param.getName ());
454     bldr.append ("=");
455     if (param.getDefaultValueString () != null)
456     {
457      bldr.append (param.getDefaultValueString ());
458      continue;
459     }
460     if (param.getDefaultValueList () != null)
461     {
462      String comma = "";
463      for (String defValue : param.getDefaultValueList ())
464      {
465       bldr.append (comma);
466       comma = ", ";
467       bldr.append (defValue);
468      }
469     }
470    }
471   }
472   return bldr.toString ();
473  }
474 
475  private String calcValidCharsMinMax (FieldDefinition fd)
476  {
477   String validChars = calcValidChars (fd);
478   String minMax = calcMinMax (fd);
479   String and = " and ";
480   if (validChars.trim ().equals (""))
481   {
482    return minMax;
483   }
484   if (minMax.trim ().equals (""))
485   {
486    return validChars;
487   }
488   return validChars + "\\\\\n" + minMax;
489  }
490 
491  private String calcMinMax (FieldDefinition fd)
492  {
493   if (fd.getExclusiveMin () == null)
494   {
495    if (fd.getInclusiveMax () == null)
496    {
497     return " ";
498    }
499    return "Must be <= " + fd.getInclusiveMax ();
500   }
501   if (fd.getInclusiveMax () == null)
502   {
503    return "Must be > " + fd.getExclusiveMin ();
504   }
505   return "Must be > " + fd.getExclusiveMin () + " and < "
506          + fd.getInclusiveMax ();
507  }
508  private static final String PAGE_PREFIX = "Formatted View of ";
509  private static final String PAGE_SUFFIX = " Searches";
510 
511  private String calcWikiSearchPage (String searchType)
512  {
513   return PAGE_PREFIX + calcWikigPageAbbrev (searchType) + PAGE_SUFFIX;
514  }
515 
516  private String calcWikigPageAbbrev (String searchType)
517  {
518   if (searchType == null)
519   {
520    return null;
521   }
522   if (searchType.equals ("enumeration.management.search"))
523   {
524    return "EM";
525   }
526   if (searchType.startsWith ("lu."))
527   {
528    return "LU";
529   }
530   if (searchType.startsWith ("cluset."))
531   {
532    return "LU";
533   }
534   if (searchType.startsWith ("lo."))
535   {
536    return "LO";
537   }
538   if (searchType.startsWith ("lrc."))
539   {
540    return "LRC";
541   }
542   if (searchType.startsWith ("comment."))
543   {
544    return "Comment";
545   }
546   if (searchType.startsWith ("org."))
547   {
548    return "Organization";
549   }
550   if (searchType.startsWith ("atp."))
551   {
552    return "ATP";
553   }
554   if (searchType.startsWith ("subjectCode."))
555   {
556    return "SC";
557   }
558   throw new IllegalArgumentException ("Unknown type of search: " + searchType);
559  }
560 
561  private List<CommonLookupParam> filterConfiguredParams (
562    List<CommonLookupParam> params)
563  {
564   List list = new ArrayList ();
565   if (params == null)
566   {
567    return list;
568   }
569   if (params.size () == 0)
570   {
571    return list;
572   }
573   for (CommonLookupParam param : params)
574   {
575    if (param.getDefaultValueString () != null)
576    {
577     list.add (param);
578     continue;
579    }
580    if (param.getDefaultValueList () != null)
581    {
582     list.add (param);
583    }
584   }
585   return list;
586  }
587 
588  private String calcRepeating (FieldDefinition fd)
589  {
590   if (fd.getMaxOccurs () == null)
591   {
592    return "???";
593   }
594   if (fd.getMaxOccurs ().equals (UNBOUNDED))
595   {
596    if (fd.getMinOccurs () != null && fd.getMinOccurs () > 1)
597    {
598     return "repeating: minimum " + fd.getMinOccurs () + " times";
599    }
600    return "repeating: unlimited";
601   }
602   if (Integer.parseInt (fd.getMaxOccurs ()) == 0)
603   {
604    return "NOT USED";
605   }
606   if (Integer.parseInt (fd.getMaxOccurs ()) == 1)
607   {
608    return " ";
609 //   return "single";
610   }
611 
612   if (fd.getMinOccurs () != null)
613   {
614    if (fd.getMinOccurs () > 1)
615    {
616     return "repeating: " + fd.getMinOccurs () + " to " + fd.getMaxOccurs ()
617            + " times";
618    }
619   }
620   return "repeating: maximum " + fd.getMaxOccurs () + " times";
621  }
622 
623  private String calcLength (FieldDefinition fd)
624  {
625   if (fd.getMaxLength () != null)
626   {
627    if (fd.getMinLength () != null && fd.getMinLength () != 0)
628    {
629     if (Integer.parseInt (fd.getMaxLength ()) == fd.getMinLength ())
630     {
631      return ("(must be " + fd.getMaxLength () + ")");
632     }
633     return "(" + fd.getMinLength () + " to " + fd.getMaxLength () + ")";
634    }
635    return "(up to " + fd.getMaxLength () + ")";
636   }
637   if (fd.getMinLength () != null)
638   {
639    return "(over " + fd.getMinLength () + ")";
640   }
641   return " ";
642  }
643 
644  private String calcCrossField (FieldDefinition fd)
645  {
646   StringBuilder b = new StringBuilder ();
647   String semicolon = "";
648   String cfr = calcCrossFieldRequire (fd);
649   if (cfr != null)
650   {
651    b.append (semicolon);
652    semicolon = "; ";
653    b.append (cfr);
654   }
655   String cfw = calcCrossFieldWhen (fd);
656   if (cfw != null)
657   {
658    b.append (semicolon);
659    semicolon = "; ";
660    b.append (cfw);
661   }
662   if (b.length () == 0)
663   {
664    return " ";
665   }
666   return b.toString ();
667  }
668 
669  private String calcCrossFieldRequire (FieldDefinition fd)
670  {
671   if (fd.getRequireConstraint () == null)
672   {
673    return null;
674   }
675   if (fd.getRequireConstraint ().size () == 0)
676   {
677    return null;
678   }
679   StringBuilder b = new StringBuilder ();
680   String comma = "";
681   b.append ("if not empty then ");
682   for (RequiredConstraint rc : fd.getRequireConstraint ())
683   {
684    b.append (comma);
685    comma = ", ";
686    b.append (rc.getFieldPath ());
687   }
688   if (fd.getRequireConstraint ().size () == 1)
689   {
690    b.append (" is");
691   }
692   else
693   {
694    b.append (" are");
695   }
696   b.append (" also required");
697   return b.toString ();
698  }
699 
700  private String calcCrossFieldWhen (FieldDefinition fd)
701  {
702   if (fd.getCaseConstraint () == null)
703   {
704    return null;
705   }
706   StringBuilder b = new StringBuilder ();
707   CaseConstraint cc = fd.getCaseConstraint ();
708   for (WhenConstraint wc : cc.getWhenConstraint ())
709   {
710    b.append ("\\\\");
711    b.append ("\n");
712    b.append ("when ");
713    b.append (cc.getFieldPath ());
714    b.append (" ");
715    if ( ! cc.isCaseSensitive ())
716    {
717     b.append ("ignoring case ");
718    }
719    b.append (cc.getOperator ());
720    b.append (" ");
721 
722    b.append ("\\\\");
723    b.append ("\n");
724    String comma = "";
725    for (Object value : wc.getValues ())
726    {
727     b.append (comma);
728     comma = " or ";
729     b.append (asString (value));
730    }
731    b.append ("\\\\");
732    b.append ("\n");
733    b.append ("then override constraint:"
734              + calcOverride (fd, wc.getConstraint ()));
735   }
736   return b.toString ();
737  }
738 
739  private String calcOverride (FieldDefinition fd, Constraint cons)
740  {
741   StringBuilder b = new StringBuilder ();
742   b.append (calcOverride ("serviceSide", fd.isServerSide (),
743                           cons.isServerSide ()));
744   b.append (calcOverride ("exclusiveMin", fd.getExclusiveMin (),
745                           cons.getExclusiveMin ()));
746   b.append (calcOverride ("inclusiveMax", fd.getInclusiveMax (),
747                           cons.getInclusiveMax ()));
748   String minOccursMessage = calcOverride ("minOccurs", fd.getMinOccurs (),
749                                           cons.getMinOccurs ());
750   if ( ! minOccursMessage.trim ().equals (""))
751   {
752    if (cons.getMinOccurs () != null && cons.getMinOccurs () == 1)
753    {
754     minOccursMessage = " REQUIRED";
755    }
756   }
757   b.append (minOccursMessage);
758   b.append (calcOverride ("validchars", fd.getValidChars (),
759                           cons.getValidChars ()));
760   b.append (calcOverride ("lookup", fd.getLookupDefinition (),
761                           cons.getLookupDefinition ()));
762   //TODO: other more complex constraints
763   return b.toString ();
764  }
765 
766  private String calcOverride (String attribute, LookupConstraint val1,
767                               LookupConstraint val2)
768  {
769   if (val1 == val2)
770   {
771    return "";
772   }
773   if (val1 == null && val2 != null)
774   {
775    return " add lookup " + this.calcLookup (val2);
776   }
777   if (val1 != null && val2 == null)
778   {
779    return " remove lookup constraint";
780   }
781   return " change lookup to " + calcLookup (val2);
782  }
783 
784  private String calcOverride (String attribute, ValidCharsConstraint val1,
785                               ValidCharsConstraint val2)
786  {
787   if (val1 == val2)
788   {
789    return "";
790   }
791   if (val1 == null && val2 != null)
792   {
793    return " add validchars " + calcValidChars (val2);
794   }
795   if (val1 != null && val2 == null)
796   {
797    return " remove validchars constraint";
798   }
799   return " change validchars to " + calcValidChars (val2);
800  }
801 
802  private String calcOverride (String attribute, boolean val1, boolean val2)
803  {
804   if (val1 == val2)
805   {
806    return "";
807   }
808   return " " + attribute + "=" + val2;
809  }
810 
811  private String calcOverride (String attribute, String val1, String val2)
812  {
813   if (val1 == null && val2 == null)
814   {
815    return "";
816   }
817   if (val1 == val2)
818   {
819    return "";
820   }
821   if (val1 == null)
822   {
823    return " " + attribute + "=" + val2;
824   }
825   if (val1.equals (val2))
826   {
827    return "";
828   }
829   return " " + attribute + "=" + val2;
830  }
831 
832  private String calcOverride (String attribute, Object val1, Object val2)
833  {
834   if (val1 == null && val2 == null)
835   {
836    return "";
837   }
838   if (val1 == val2)
839   {
840    return "";
841   }
842   if (val1 == null)
843   {
844    return " " + attribute + "=" + val2;
845   }
846   if (val1.equals (val2))
847   {
848    return "";
849   }
850   return " " + attribute + "=" + asString (val2);
851  }
852 
853  private String asString (Object value)
854  {
855   if (value == null)
856   {
857    return "null";
858   }
859   if (value instanceof String)
860   {
861    return (String) value;
862   }
863   return value.toString ();
864  }
865  }