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