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