View Javadoc

1   /*
2    * Copyright 2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may	obtain a copy of the License at
7    *
8    * 	http://www.osedu.org/licenses/ECL-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.student.contract.model.util;
17  
18  import java.io.File;
19  import java.io.FileNotFoundException;
20  import java.io.FileOutputStream;
21  import java.io.PrintStream;
22  import java.text.BreakIterator;
23  import java.util.Collections;
24  import java.util.Date;
25  import java.util.Enumeration;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Stack;
30  import org.apache.commons.lang.StringEscapeUtils;
31  
32  import org.kuali.student.contract.model.MessageStructure;
33  import org.kuali.student.contract.model.ServiceContractModel;
34  import org.kuali.student.contract.model.XmlType;
35  import org.kuali.student.contract.writer.XmlWriter;
36  
37  /**
38   *
39   * @author nwright
40   */
41  public class KradDictionaryCreator {
42  
43      private ServiceContractModel model;
44      private ModelFinder finder;
45      private String directory;
46      private String className;
47      private XmlType xmlType;
48      private XmlWriter gwriter;
49      private XmlWriter mwriter;
50      private List<MessageStructure> messageStructures;
51      private boolean writeManual;
52      private boolean writeGenerated;
53  
54      public KradDictionaryCreator(String directory,
55              ServiceContractModel model, String className, boolean writeManual, boolean writeGenerated) {
56          this.directory = directory;
57          this.model = model;
58          this.finder = new ModelFinder(this.model);
59          this.className = className;
60          this.xmlType = this.finder.findXmlType(className);
61          if (xmlType == null) {
62              throw new IllegalArgumentException(className);
63          }
64          this.messageStructures = this.finder.findMessageStructures(className);
65          this.writeManual = writeManual;
66          this.writeGenerated = writeGenerated;
67  //        if (this.messageStructures.isEmpty()) {
68  //            throw new IllegalStateException(className);
69  //        }
70      }
71  
72      public void write() {
73          this.initXmlWriters();
74          if (writeGenerated) {
75              this.writeSpringHeaderOpen(gwriter);
76              this.writeWarning(gwriter);
77              this.writeGeneratedImports(gwriter);
78              this.writeGeneratedObjectStructure(gwriter);
79              this.writeSpringHeaderClose(gwriter);
80          }
81          if (this.writeManual) {
82              this.writeSpringHeaderOpen(mwriter);
83              this.writeNote(mwriter);
84              this.writeManualImports(mwriter);
85              this.writeManualObjectStructure(mwriter);
86              this.writeSpringHeaderClose(mwriter);
87          }
88      }
89  
90      private void initXmlWriters() {
91          String generatedFileName = "/ks-" + initUpper(className) + "-dictionary-generated.xml";
92          String manualFileName = "/ks-" + initUpper(className) + "-dictionary.xml";
93  
94          File dir = new File(this.directory);
95          //System.out.indentPrintln ("Writing java class: " + fileName + " to " + dir.getAbsolutePath ());
96  
97          if (!dir.exists()) {
98              if (!dir.mkdirs()) {
99                  throw new IllegalStateException("Could not create directory "
100                         + this.directory);
101             }
102         }
103 
104         if (writeGenerated) {
105             try {
106                 PrintStream out = new PrintStream(new FileOutputStream(this.directory + "/" + generatedFileName, false));
107                 this.gwriter = new XmlWriter(out, 0);
108             } catch (FileNotFoundException ex) {
109                 throw new IllegalStateException(ex);
110             }
111         }
112         if (this.writeManual) {
113             try {
114                 PrintStream out = new PrintStream(new FileOutputStream(this.directory + "/" + manualFileName, false));
115                 this.mwriter = new XmlWriter(out, 0);
116             } catch (FileNotFoundException ex) {
117                 throw new IllegalStateException(ex);
118             }
119         }
120     }
121 
122     private static String initLower(String str) {
123         if (str == null) {
124             return null;
125         }
126         if (str.length() == 0) {
127             return str;
128         }
129         if (str.length() == 1) {
130             return str.toLowerCase();
131         }
132         return str.substring(0, 1).toLowerCase() + str.substring(1);
133     }
134 
135     private static String initUpper(String str) {
136         if (str == null) {
137             return null;
138         }
139         if (str.length() == 0) {
140             return str;
141         }
142         if (str.length() == 1) {
143             return str.toUpperCase();
144         }
145         return str.substring(0, 1).toUpperCase() + str.substring(1);
146     }
147 
148     private void writeSpringHeaderClose(XmlWriter out) {
149         out.decrementIndent();
150         out.indentPrintln("</beans>");
151     }
152 
153     private void writeSpringHeaderOpen(XmlWriter out) {
154         out.indentPrintln("<!--");
155         out.indentPrintln(" Copyright 2011 The Kuali Foundation");
156         out.println("");
157         out.indentPrintln(" Licensed under the Educational Community License, Version 2.0 (the \"License\");");
158         out.indentPrintln(" you may not use this file except in compliance with the License.");
159         out.indentPrintln(" You may obtain a copy of the License at");
160         out.indentPrintln("");
161         out.indentPrintln(" http://www.opensource.org/licenses/ecl2.php");
162         out.println("");
163         out.indentPrintln(" Unless required by applicable law or agreed to in writing, software");
164         out.indentPrintln(" distributed under the License is distributed on an \"AS IS\" BASIS,");
165         out.indentPrintln(" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.");
166         out.indentPrintln(" See the License for the specific language governing permissions and");
167         out.indentPrintln(" limitations under the License.");
168         out.indentPrintln("-->");
169         out.indentPrintln("<beans xmlns=\"http://www.springframework.org/schema/beans\"");
170         out.indentPrintln("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
171         out.indentPrintln("xsi:schemaLocation=\""
172                 + "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" + "\">");
173         out.println("");
174         out.incrementIndent();
175     }
176 
177     private void writeWarning(XmlWriter out) {
178         out.println("");
179         out.indentPrintln("<!-- ********************************************************");
180         out.incrementIndent();
181         out.indentPrintln("                       WARNING ");
182         out.indentPrintln("             DO NOT UPDATE THIS FILE MANUALLY");
183         out.indentPrintln("This dictionary file was automatically generated on " + new Date());
184         out.indentPrintln("The DictionaryGeneratorMojo reads the service contract ");
185         out.indentPrintln("and creates these ks-XXXX-dictionary-generated.xml files.");
186         out.println("");
187         out.indentPrintln("If this file is out of sync with the contract re-run the mojo.");
188         out.println("");
189         out.indentPrintln("To add additional constraints or change these default values (perhaps");
190         out.indentPrintln("because the generator is not perfect) please update the corresponding ");
191         out.indentPrintln("ks-XXXX-dictionary.xml instead of this one.");
192         out.decrementIndent();
193         out.indentPrintln("************************************************************* -->");
194     }
195 
196     private void writeNote(XmlWriter out) {
197         out.println("");
198         out.indentPrintln("<!-- ********************************************************");
199         out.incrementIndent();
200         out.indentPrintln("                       NOTE");
201         out.indentPrintln("          THIS FILE WAS INTENDED TO BE MODIFIED");
202         out.println("");
203         out.indentPrintln("While this file was originally generated on " + new Date() + ", it");
204         out.indentPrintln("was intended to be subsequently modified by hand.");
205         out.indentPrintln("It imports a corresponding ks-XXXX-dictionary-generated.xml file, ");
206         out.indentPrintln("that was also automatically generated by the ContractDocMojo.");
207         out.indentPrintln("This file gives you the ability to layer on addiditional definitions and constrints");
208         out.indentPrintln("that are not/cannot be generated simply by reading the service contract.");
209         out.println("");
210         out.indentPrintln("The goal of this file is to be able to re-generate the corresponding");
211         out.indentPrintln("ks-XXXX-dictionary-generated.xml file without affecting these manually entered additions");
212         out.indentPrintln("that are encoded here.");
213         out.decrementIndent();
214         out.indentPrintln("************************************************************* -->");
215     }
216 
217     private void writeGeneratedImports(XmlWriter out) {
218         out.indentPrintln("<import resource=\"classpath:ks-base-dictionary.xml\"/>");
219         // TODO: only write out the ones that are used in this structure
220 //        out.indentPrintln("<import resource=\"classpath:ks-RichTextInfo-dictionary.xml\"/>");
221 //        out.indentPrintln("<import resource=\"classpath:ks-MetaInfo-dictionary.xml\"/>");
222     }
223 
224     private void writeManualImports(XmlWriter out) {
225         out.writeComment("This file gets generated during the build and put into the target/classes directory");
226         out.indentPrintln("<import resource=\"classpath:ks-" + initUpper(className) + "-dictionary-generated.xml\"/>");
227     }
228 
229     private String stripListOffEnd(String name) {
230         if (name.endsWith("List")) {
231             return name.substring(0, name.length() - "List".length());
232         }
233         return name;
234     }
235 
236     private void writeSubStructures(XmlType type, Stack<String> stack) {
237         boolean first = true;
238         for (MessageStructure ms : finder.findMessageStructures(type.getName())) {
239             // skip dynamic attributes
240             if (ms.getShortName().equals("attributes")) {
241                 continue;
242             }
243             XmlType st = finder.findXmlType(this.stripListOffEnd(ms.getType()));
244             if (st == null) {
245                 throw new NullPointerException(ms.getType() + " does not exist in list of types with parents " + calcParents(stack));
246             }
247             if (!st.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
248                 continue;
249             }
250             if (first) {
251                 first = false;
252                 gwriter.indentPrintln("<ul>");
253             }
254             if (!stack.contains(st.getName())) {
255                 stack.push(st.getName());
256                 this.writeSubStructures(st, stack);
257                 stack.pop();
258             }
259         }
260         if (!first) {
261             gwriter.indentPrintln("</ul>");
262         }
263     }
264 
265     private String calcParents(Stack<String> stack) {
266         StringBuilder sb = new StringBuilder();
267         String dot = "";
268         Enumeration<String> en = stack.elements();
269         while (en.hasMoreElements()) {
270             sb.append(dot);
271             dot = ".";
272             sb.append(en.nextElement());
273         }
274         return sb.toString();
275     }
276 
277     private void writeGeneratedObjectStructure(XmlWriter out) {
278         //Step 1, create the abstract structure
279         out.println("");
280         out.indentPrintln("<!-- " + className + "-->");
281         out.indentPrintln("<bean id=\"" + initUpper(className) + "-generated\" abstract=\"true\" parent=\"DataObjectEntry\">");
282         out.incrementIndent();
283         writeProperty("name", initLower(className), out);
284         writeProperty("objectClass", xmlType.getJavaPackage() + "." + initUpper(className), out);
285         writeProperty("objectLabel", calcObjectLabel(), out);
286         writeProperty("objectDescription", xmlType.getDesc(), out);
287         String titleAttribute = calcTitleAttribute();
288         if (titleAttribute != null) {
289             writeProperty("titleAttribute", titleAttribute, out);
290         }
291         out.indentPrintln("<property name=\"primaryKeys\">");
292         List<String> pks = calcPrimaryKeys();
293         if (pks != null && !pks.isEmpty()) {
294             out.incrementIndent();
295             out.indentPrintln("<list>");
296             out.incrementIndent();
297             for (String pk : pks) {
298                 addValue(pk);
299             }
300             out.decrementIndent();
301             out.indentPrintln("</list>");
302             out.decrementIndent();
303         }
304         out.indentPrintln("</property>");
305         out.indentPrintln("<property name=\"attributes\">");
306         out.incrementIndent();
307         out.indentPrintln("<list>");
308         out.incrementIndent();
309         this.writeGeneratedAttributeRefBeans(className, null, new Stack<String>(), this.messageStructures, out);
310         out.decrementIndent();
311         out.indentPrintln("</list>");
312         out.decrementIndent();
313         out.indentPrintln("</property>");
314         out.decrementIndent();
315         out.indentPrintln("</bean>");
316 
317         //Step 2, loop through attributes
318         this.writeGeneratedAttributeDefinitions(className, null, new Stack<String>(), this.messageStructures, out);
319 
320     }
321 
322     private void addValue(String value) {
323         gwriter.indentPrintln("<value>" + value + "</value>");
324     }
325 
326     private String calcObjectLabel() {
327         String label = this.className;
328         if (label.endsWith("Info")) {
329             label = label.substring(0, label.length() - "Info".length());
330         }
331         label = initUpper(label);
332         return splitCamelCase(label);
333     }
334 
335     // got this from http://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java
336     private static String splitCamelCase(String s) {
337         if (s == null) {
338             return null;
339         }
340         return s.replaceAll(
341                 String.format("%s|%s|%s",
342                 "(?<=[A-Z])(?=[A-Z][a-z])",
343                 "(?<=[^A-Z])(?=[A-Z])",
344                 "(?<=[A-Za-z])(?=[^A-Za-z])"),
345                 " ");
346     }
347 
348     private void writeGeneratedAttributeRefBeans(String currentClass, String parentName,
349             Stack<String> parents, List<MessageStructure> fields, XmlWriter out) {
350         if (parents.contains(currentClass)) {
351             return;
352         }
353         for (MessageStructure ms : fields) {
354             // skip dynamic attributes
355             if (ms.getShortName().equals("attributes")) {
356                 continue;
357             }
358             String name = calcName(parentName, ms);
359             String beanName = calcBeanName(name);
360             out.indentPrintln("<ref bean=\"" + beanName + "\"/>");
361             // Add complex sub-types fields
362             String childXmlTypeName = this.stripListOffEnd(ms.getType());
363             XmlType childXmlType = this.finder.findXmlType(childXmlTypeName);
364             if (childXmlType == null) {
365                 throw new IllegalStateException(childXmlTypeName);
366             }
367             if (childXmlType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
368                 parents.push(currentClass);
369                 List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName);
370 //                if (childFields.isEmpty()) {
371 //                    throw new IllegalStateException(childXmlTypeName);
372 //                }
373                 writeGeneratedAttributeRefBeans(childXmlTypeName, name, parents, childFields, out);
374                 parents.pop();
375             }
376         }
377     }
378 
379     private void writeGeneratedAttributeDefinitions(String currentClassName, String parentName,
380             Stack<String> parents, List<MessageStructure> fields, XmlWriter out) {
381         if (parents.contains(currentClassName)) {
382             return;
383         }
384         for (MessageStructure ms : fields) {
385             // skip dynamic attributes
386             if (ms.getShortName().equals("attributes")) {
387                 continue;
388             }
389             String name = calcName(parentName, ms);
390             String beanName = calcBeanName(name);
391             String childXmlTypeName = this.stripListOffEnd(ms.getType());
392             XmlType childXmlType = this.finder.findXmlType(childXmlTypeName);
393             if (childXmlType == null) {
394                 throw new IllegalStateException(childXmlTypeName);
395             }
396             writeGeneratedAttributeDefinition(currentClassName, parentName, ms, out);
397 
398             // Add complex sub-types fields
399             if (childXmlType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
400                 parents.push(currentClassName);
401                 List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName);
402 //                if (childFields.isEmpty()) {
403 //                    throw new IllegalStateException(childXmlTypeName);
404 //                }
405                 writeGeneratedAttributeDefinitions(childXmlTypeName, name, parents, childFields, out);
406                 parents.pop();
407             }
408         }
409     }
410 
411     private boolean shouldWriteDetails(MessageStructure ms) {
412         if (predefinedFieldMap.get(ms.getShortName().toLowerCase()) == null) {
413             return true;
414         }
415         if (ms.isOverriden()) {
416             return true;
417         }
418         // don't write out details for predefined fields that have not been overridden
419         return false;
420     }
421 
422     private void writeGeneratedAttributeDefinition(String currentClassName, String parentName, MessageStructure ms, XmlWriter out) {
423 
424         //Create the abstract field
425         String name = calcName(parentName, ms);
426         String beanName = calcBeanName(name);
427         String baseKualiType = this.calcBaseKualiType(ms);
428         out.println("");
429         out.indentPrintln("<bean id=\"" + beanName + "-generated\" abstract=\"true\" parent=\"" + baseKualiType + "\">");
430         out.incrementIndent();
431         writeProperty("name", name, out);
432         writeProperty("childEntryName", calcChildEntryName(ms), out);
433         if (this.shouldWriteDetails(ms)) {
434             writeProperty("required", calcRequired(ms), out);
435             writeProperty("shortLabel", calcShortLabel(ms), out);
436             writePropertyValue("summary", calcSummary(ms), out);
437             writeProperty("label", calcLabel(ms), out);
438             writePropertyValue("description", calcDescription(ms), out);
439             if (this.calcReadOnly(ms)) {
440                 this.writeReadOnlyAttributeSecurity(out);
441             }
442         }
443         out.decrementIndent();
444         // TODO: implement maxoccurs
445 //        if (isList(pd)) {
446 //            addProperty("maxOccurs", "" + DictionaryConstants.UNBOUNDED, s);
447 //        }
448         out.indentPrintln("</bean>");
449     }
450 
451     private String calcBeanName(String name) {
452         return initUpper(className) + "." + name;
453     }
454 
455     private String calcName(String parentName, MessageStructure ms) {
456         String name = this.calcChildEntryName(ms);
457         if (parentName == null) {
458             return name;
459         }
460         return parentName + "." + name;
461     }
462 
463     private String calcChildEntryName(MessageStructure ms) {
464         String name = initLower(ms.getShortName());
465         return name;
466     }
467 
468     private boolean calcReadOnly(MessageStructure ms) {
469         if (ms.getReadOnly() == null) {
470             return false;
471         }
472         return true;
473     }
474 
475     private void writeReadOnlyAttributeSecurity(XmlWriter out) {
476         out.println("<property name=\"attributeSecurity\">");
477         out.println("<ref bean=\"BaseKuali.readOnlyAttributeSecurity\"/>");
478         out.println("</property>");
479     }
480 
481     private String calcShortLabel(MessageStructure ms) {
482         return this.splitCamelCase(initUpper(ms.getShortName()));
483     }
484 
485     private String calcLabel(MessageStructure ms) {
486         return ms.getName();
487     }
488 
489     private String calcSummary(MessageStructure ms) {
490         BreakIterator bi = BreakIterator.getSentenceInstance();
491         String description = ms.getDescription();
492         if (description == null) {
493             return "???";
494         }
495         bi.setText(ms.getDescription());
496         // one big sentence
497         if (bi.next() == BreakIterator.DONE) {
498             return ms.getDescription();
499         }
500         String firstSentence = description.substring(0, bi.current());
501         return firstSentence;
502     }
503 
504     private String calcDescription(MessageStructure ms) {
505         return ms.getDescription();
506     }
507 
508     private String calcRequired(MessageStructure ms) {
509         if (ms.getRequired() == null) {
510             return "false";
511         }
512         if (ms.getRequired().equalsIgnoreCase("Required")) {
513             return "true";
514         }
515         // TODO: figure out what to do if it is qualified like "required on update"
516         return "false";
517     }
518 
519     private void writeManualObjectStructure(XmlWriter out) {
520         //Step 1, create the parent bean
521         out.println("");
522         out.indentPrintln("<!-- " + className + "-->");
523         out.indentPrintln("<bean id=\"" + initUpper(className) + "-parent\" abstract=\"true\" parent=\"" + initUpper(className) + "-generated\">");
524         out.writeComment("insert any overrides to the generated object definitions here");
525         out.indentPrintln("</bean>");
526 
527         //Create the actual instance of the bean
528         out.indentPrintln("<bean id=\"" + initUpper(className) + "\" parent=\"" + initUpper(className) + "-parent\"/>");
529         out.println("");
530 
531         //Step 2, loop through attributes
532         this.writeManualAttributeDefinitions(className, null, new Stack<String>(), this.messageStructures, out);
533 
534     }
535 
536     private void writeManualAttributeDefinitions(String currentClass, String parentName,
537             Stack<String> parents, List<MessageStructure> fields, XmlWriter out) {
538         if (parents.contains(currentClass)) {
539             return;
540         }
541         for (MessageStructure ms : fields) {
542             // skip dynamic attributes
543             if (ms.getShortName().equals("attributes")) {
544                 continue;
545             }
546             String name = calcName(parentName, ms);
547             String beanName = calcBeanName(name);
548             String childXmlTypeName = this.stripListOffEnd(ms.getType());
549             XmlType childXmlType = this.finder.findXmlType(childXmlTypeName);
550             if (childXmlType == null) {
551                 throw new IllegalStateException(childXmlTypeName);
552             }
553             writeManualAttributeDefinition(currentClass, parentName, ms, out);
554 
555             // Add complex sub-types fields
556             if (childXmlType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
557                 parents.push(currentClass);
558                 List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName);
559 //                if (childFields.isEmpty()) {
560 //                    throw new IllegalStateException(childXmlTypeName);
561 //                }
562                 writeManualAttributeDefinitions(childXmlTypeName, name, parents, childFields, out);
563                 parents.pop();
564             }
565         }
566     }
567 
568     private void writeManualAttributeDefinition(String currentClass, String parentName, MessageStructure ms, XmlWriter out) {
569 
570         //Create the abstract field
571         String name = calcName(parentName, ms);
572         String beanName = calcBeanName(name);
573 //        String baseKualiType = this.calcBaseKualiType(ms);
574         out.println("");
575         out.indentPrintln("<bean id=\"" + beanName + "-parent\" abstract=\"true\" parent=\"" + beanName + "-generated\">");
576         out.writeComment("insert any overrides to the generated attribute definitions here");
577         out.indentPrintln("</bean>");
578 
579         //Create the actual bean instance
580         out.indentPrintln("<bean id=\"" + beanName + "\" parent=\"" + beanName + "-parent\"/>");
581     }
582     /**
583      * list of predefined fields that should map to entries in ks-base-dictionary.xml
584      */
585     private static Map<String, String> predefinedFieldMap = null;
586 
587     {
588         Map<String, String> map = new HashMap<String, String>();
589         map.put("id", "BaseKuali.id");
590         map.put("key", "BaseKuali.key");
591         map.put("name", "BaseKuali.name");
592         map.put("descr", "BaseKuali.descr");
593         map.put("plain", "BaseKuali.descr.plain");
594         map.put("formatted", "BaseKuali.descr.formatted");        
595         map.put("desc", "BaseKuali.desc"); // r1 compatibility
596         map.put("typeKey", "BaseKuali.typeKey");
597         map.put("stateKey", "BaseKuali.stateKey");
598         map.put("type", "BaseKuali.type"); // r1 compatibility
599         map.put("state", "BaseKuali.state"); // r1 compatibility
600         map.put("effectiveDate", "BaseKuali.effectiveDate");
601         map.put("expirationDate", "BaseKuali.expirationDate");       
602         map.put("meta", "BaseKuali.meta");        
603         map.put("createTime", "BaseKuali.meta.createTime");
604         map.put("updateTime", "BaseKuali.meta.updateTime");
605         map.put("createId", "BaseKuali.meta.createId");
606         map.put("updateId", "BaseKuali.meta.updateId");
607         map.put("versionInd", "BaseKuali.meta.versionInd");
608         // convert to lower case
609         predefinedFieldMap = new HashMap(map.size());
610         for (String key : map.keySet()) {
611             predefinedFieldMap.put(key.toLowerCase(), map.get(key));
612         }        
613     }
614     /**
615      * list of fields that if they end with the key the should be based on the entry
616      * in ks-base-dictionary.xml
617      */
618     private static Map<String, String> endsWithMap = null;
619 
620     {
621         Map<String, String> map = new HashMap<String, String>();
622         map.put("startDate", "BaseKuali.startDate");
623         map.put("endDate", "BaseKuali.endDate");
624         map.put("OrgId", "BaseKuali.orgId"); 
625         map.put("OrgIds", "BaseKuali.orgId");         
626         map.put("PersonId", "BaseKuali.personId");
627         map.put("PersonIds", "BaseKuali.personId");
628         map.put("PrincipalId", "BaseKuali.principalId");
629         map.put("PrincipalIds", "BaseKuali.principalId");        
630         map.put("CluId", "BaseKuali.cluId");
631         map.put("CluIds", "BaseKuali.cluId");        
632         map.put("LuiId", "BaseKuali.luiId");
633         map.put("LuiIds", "BaseKuali.luiId");        
634         map.put("AtpKey", "BaseKuali.atpKey");        
635         map.put("AtpKeys", "BaseKuali.atpKey");        
636         map.put("TermKey", "BaseKuali.termKey");
637         map.put("TermKeys", "BaseKuali.termKey");         
638         map.put("CampusCalendarKey", "BaseKuali.campusCalendarKey");               
639         map.put("CampusCalendarKeys", "BaseKuali.campusCalendarKey");               
640         map.put("Code", "BaseKuali.code");
641         // convert to lower case
642         endsWithMap = new HashMap(map.size());
643         for (String key : map.keySet()) {
644             endsWithMap.put(key.toLowerCase(), map.get(key));
645         }
646     }
647     /**
648      * list of types that if the type matches this key then 
649      * it should be based on that type entry as defined in the 
650      * ks-base-dictionary.xml
651      */
652     private static Map<String, String> typeMap = null;
653 
654     {
655         Map<String, String> map = new HashMap<String, String>();
656         map.put("String", "BaseKuali.string");
657         map.put("DateTime", "BaseKuali.dateTime");
658         map.put("Date", "BaseKuali.date");
659         map.put("Boolean", "BaseKuali.boolean");
660         map.put("Integer", "BaseKuali.integer");
661         map.put("Complex", "BaseKuali.complex");
662         // convert to lower case
663         typeMap = new HashMap(map.size());
664         for (String key : map.keySet()) {
665             typeMap.put(key.toLowerCase (), map.get(key));
666         }
667     }
668 
669     private String calcBaseKualiType(MessageStructure ms) {
670 
671         String name = ms.getShortName();
672         String baseKualiType = predefinedFieldMap.get(name.toLowerCase());
673         if (baseKualiType != null) {
674             return baseKualiType;
675         }
676 
677         // check ends with
678         for (String key : endsWithMap.keySet()) {
679             if (name.toLowerCase ().endsWith(key)) {
680                 return endsWithMap.get(key);
681             }
682         }
683 
684         // now key off the type
685         String type = this.stripListOffEnd(ms.getType());
686         baseKualiType = typeMap.get(type.toLowerCase());
687         if (baseKualiType != null) {
688             return baseKualiType;
689         }
690 
691         // all else fails call it a string??
692         return "BaseKuali.string";
693 
694     }
695 
696     private String calcTitleAttribute() {
697         MessageStructure ms = null;
698         ms = this.findMessageStructure("name");
699         if (ms != null) {
700             return initLower(ms.getShortName());
701         }
702         ms = this.findMessageStructure("title");
703         if (ms != null) {
704             return initLower(ms.getShortName());
705         }
706         ms = this.findMessageStructureEndsWith("name");
707         if (ms != null) {
708             return initLower(ms.getShortName());
709         }
710         ms = this.findMessageStructureEndsWith("title");
711         if (ms != null) {
712             return initLower(ms.getShortName());
713         }
714         ms = this.findMessageStructure("key");
715         if (ms != null) {
716             return initLower(ms.getShortName());
717         }
718         // TODO: consider checking for ID and just returning null
719         System.out.println("XmlKradBaseDictionaryCreator: could not find a title attribute for " + this.className);
720 //        ms = this.findMessageStructure("id");
721 //        if (ms != null) {
722 //            return initLower(ms.getShortName());
723 //        }
724         return null;
725     }
726 
727     private MessageStructure findMessageStructureEndsWith(String shortNameEndsWith) {
728         shortNameEndsWith = shortNameEndsWith.toLowerCase();
729         for (MessageStructure ms : this.messageStructures) {
730             if (ms.getShortName().toLowerCase().endsWith(shortNameEndsWith)) {
731                 return ms;
732             }
733         }
734         return null;
735     }
736 
737     private MessageStructure findMessageStructure(String shortName) {
738         for (MessageStructure ms : this.messageStructures) {
739             if (ms.getShortName().equalsIgnoreCase(shortName)) {
740                 return ms;
741             }
742         }
743         return null;
744     }
745 
746     private MessageStructure getMessageStructure(String shortName) {
747         MessageStructure ms = this.findMessageStructure(shortName);
748         if (ms == null) {
749             throw new IllegalArgumentException(shortName);
750         }
751         return ms;
752     }
753 
754     private List<String> calcPrimaryKeys() {
755         MessageStructure ms = null;
756         ms = this.findMessageStructure("id");
757         if (ms != null) {
758             return Collections.singletonList(initLower(ms.getShortName()));
759         }
760         ms = this.findMessageStructure("key");
761         if (ms != null) {
762             return Collections.singletonList(initLower(ms.getShortName()));
763         }
764         // just use the first field
765         if (this.messageStructures.size() > 0) {
766             ms = this.messageStructures.get(0);
767             return Collections.singletonList(ms.getShortName());
768         }
769         return Collections.EMPTY_LIST;
770     }
771 
772     private void writeProperty(String propertyName, String propertyValue, XmlWriter out) {
773         out.indentPrintln("<property name=\"" + propertyName + "\" value=\"" + replaceDoubleQuotes(propertyValue) + "\"/>");
774     }
775 
776     private void writePropertyValue(String propertyName, String propertyValue, XmlWriter out) {
777         out.indentPrintln("<property name=\"" + propertyName + "\">");
778         out.incrementIndent();
779         out.indentPrintln("<value>");
780         // TODO: worry about the value starting on a new line i.e. is it trimmed when loaded?
781         out.println(escapeHtml(propertyValue));
782         out.indentPrintln("</value>");
783         out.decrementIndent();
784         out.indentPrintln("</property>");
785     }
786 
787     private String escapeHtml(String str) {
788         if (str == null) {
789             return null;
790         }
791         return StringEscapeUtils.escapeHtml(str);
792     }
793 
794     private String replaceDoubleQuotes(String str) {
795         if (str == null) {
796             return null;
797         }
798         return str.replace("\"", "'");
799     }
800 }