View Javadoc

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