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
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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
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
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
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
275
276 if (this.processSubstructures || subClazz == null)
277 {
278 if ( ! this.subStructuresAlreadyProcessed.contains (
279 fd.getDataObjectStructure ()))
280 {
281
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
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
432
433
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
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
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 }