Coverage Report - org.kuali.student.datadictionary.util.KradDictionaryCreator
 
Classes in this File Line Coverage Branch Coverage Complexity
KradDictionaryCreator
0%
0/542
0%
0/172
3.75
KradDictionaryCreator$1
0%
0/1
N/A
3.75
KradDictionaryCreator$Category
0%
0/2
N/A
3.75
 
 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.datadictionary.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.ArrayList;
 24  
 import java.util.Collections;
 25  
 import java.util.Date;
 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.model.util.ModelFinder;
 36  
 import org.kuali.student.contract.writer.XmlWriter;
 37  
 
 38  
 /**
 39  
  *
 40  
  * @author nwright
 41  
  */
 42  
 public class KradDictionaryCreator {
 43  
 
 44  
     private ServiceContractModel model;
 45  
     private ModelFinder finder;
 46  
     private String directory;
 47  
     private String className;
 48  
     private XmlType xmlType;
 49  
     private XmlWriter gwriter;
 50  
     private XmlWriter mwriter;
 51  
     private List<MessageStructure> messageStructures;
 52  
     private boolean writeManual;
 53  
     private boolean writeGenerated;
 54  
 
 55  
     public KradDictionaryCreator(String directory,
 56  0
             ServiceContractModel model, String className, boolean writeManual, boolean writeGenerated) {
 57  0
         this.directory = directory;
 58  0
         this.model = model;
 59  0
         this.finder = new ModelFinder(this.model);
 60  0
         this.className = className;
 61  0
         this.xmlType = this.finder.findXmlType(className);
 62  0
         if (xmlType == null) {
 63  0
             throw new IllegalArgumentException(className);
 64  
         }
 65  0
         this.messageStructures = this.finder.findMessageStructures(className);
 66  0
         this.writeManual = writeManual;
 67  0
         this.writeGenerated = writeGenerated;
 68  
 //        if (this.messageStructures.isEmpty()) {
 69  
 //            throw new IllegalStateException(className);
 70  
 //        }
 71  0
     }
 72  
 
 73  
     public void write() {
 74  0
         this.initXmlWriters();
 75  0
         if (writeGenerated) {
 76  0
             this.writeSpringHeaderOpen(gwriter);
 77  0
             this.writeWarning(gwriter);
 78  0
             this.writeGeneratedImports(gwriter);
 79  0
             this.writeGeneratedObjectStructure(gwriter);
 80  0
             this.writeSpringHeaderClose(gwriter);
 81  
         }
 82  0
         if (this.writeManual) {
 83  0
             this.writeSpringHeaderOpen(mwriter);
 84  0
             this.writeNote(mwriter);
 85  0
             this.writeManualImports(mwriter);
 86  0
             this.writeManualObjectStructure(mwriter);
 87  0
             this.writeSpringHeaderClose(mwriter);
 88  
         }
 89  0
     }
 90  
 
 91  
     private void initXmlWriters() {
 92  0
         String generatedFileName = "/ks-" + initUpper(className) + "-dictionary-generated.xml";
 93  0
         String manualFileName = "/ks-" + initUpper(className) + "-dictionary.xml";
 94  
 
 95  0
         File dir = new File(this.directory);
 96  
         //System.out.indentPrintln ("Writing java class: " + fileName + " to " + dir.getAbsolutePath ());
 97  
 
 98  0
         if (!dir.exists()) {
 99  0
             if (!dir.mkdirs()) {
 100  0
                 throw new IllegalStateException("Could not create directory "
 101  
                         + this.directory);
 102  
             }
 103  
         }
 104  
 
 105  0
         if (writeGenerated) {
 106  
             try {
 107  0
                 PrintStream out = new PrintStream(new FileOutputStream(this.directory + "/" + generatedFileName, false));
 108  0
                 this.gwriter = new XmlWriter(out, 0);
 109  0
             } catch (FileNotFoundException ex) {
 110  0
                 throw new IllegalStateException(ex);
 111  0
             }
 112  
         }
 113  0
         if (this.writeManual) {
 114  
             try {
 115  0
                 PrintStream out = new PrintStream(new FileOutputStream(this.directory + "/" + manualFileName, false));
 116  0
                 this.mwriter = new XmlWriter(out, 0);
 117  0
             } catch (FileNotFoundException ex) {
 118  0
                 throw new IllegalStateException(ex);
 119  0
             }
 120  
         }
 121  0
     }
 122  
 
 123  
     private static String initLower(String str) {
 124  0
         if (str == null) {
 125  0
             return null;
 126  
         }
 127  0
         if (str.length() == 0) {
 128  0
             return str;
 129  
         }
 130  0
         if (str.length() == 1) {
 131  0
             return str.toLowerCase();
 132  
         }
 133  0
         return str.substring(0, 1).toLowerCase() + str.substring(1);
 134  
     }
 135  
 
 136  
     private static String initUpper(String str) {
 137  0
         if (str == null) {
 138  0
             return null;
 139  
         }
 140  0
         if (str.length() == 0) {
 141  0
             return str;
 142  
         }
 143  0
         if (str.length() == 1) {
 144  0
             return str.toUpperCase();
 145  
         }
 146  0
         return str.substring(0, 1).toUpperCase() + str.substring(1);
 147  
     }
 148  
 
 149  
     private void writeSpringHeaderClose(XmlWriter out) {
 150  0
         out.decrementIndent();
 151  0
         out.indentPrintln("</beans>");
 152  0
     }
 153  
 
 154  
     private void writeSpringHeaderOpen(XmlWriter out) {
 155  0
         out.indentPrintln("<!--");
 156  0
         out.indentPrintln(" Copyright 2011 The Kuali Foundation");
 157  0
         out.println("");
 158  0
         out.indentPrintln(" Licensed under the Educational Community License, Version 2.0 (the \"License\");");
 159  0
         out.indentPrintln(" you may not use this file except in compliance with the License.");
 160  0
         out.indentPrintln(" You may obtain a copy of the License at");
 161  0
         out.indentPrintln("");
 162  0
         out.indentPrintln(" http://www.opensource.org/licenses/ecl2.php");
 163  0
         out.println("");
 164  0
         out.indentPrintln(" Unless required by applicable law or agreed to in writing, software");
 165  0
         out.indentPrintln(" distributed under the License is distributed on an \"AS IS\" BASIS,");
 166  0
         out.indentPrintln(" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.");
 167  0
         out.indentPrintln(" See the License for the specific language governing permissions and");
 168  0
         out.indentPrintln(" limitations under the License.");
 169  0
         out.indentPrintln("-->");
 170  0
         out.indentPrintln("<beans xmlns=\"http://www.springframework.org/schema/beans\"");
 171  0
         out.indentPrintln("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
 172  0
         out.indentPrintln("xsi:schemaLocation=\""
 173  
                 + "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" + "\">");
 174  0
         out.println("");
 175  0
         out.incrementIndent();
 176  0
     }
 177  
 
 178  
     private void writeWarning(XmlWriter out) {
 179  0
         out.println("");
 180  0
         out.indentPrintln("<!-- ********************************************************");
 181  0
         out.incrementIndent();
 182  0
         out.indentPrintln("                       WARNING ");
 183  0
         out.indentPrintln("             DO NOT UPDATE THIS FILE MANUALLY");
 184  0
         out.indentPrintln("This dictionary file was automatically generated on " + new Date());
 185  0
         out.indentPrintln("The DictionaryGeneratorMojo reads the service contract ");
 186  0
         out.indentPrintln("and creates these ks-XXXX-dictionary-generated.xml files.");
 187  0
         out.println("");
 188  0
         out.indentPrintln("If this file is out of sync with the contract re-run the mojo.");
 189  0
         out.println("");
 190  0
         out.indentPrintln("To add additional constraints or change these default values (perhaps");
 191  0
         out.indentPrintln("because the generator is not perfect) please update the corresponding ");
 192  0
         out.indentPrintln("ks-XXXX-dictionary.xml instead of this one.");
 193  0
         out.decrementIndent();
 194  0
         out.indentPrintln("************************************************************* -->");
 195  0
     }
 196  
 
 197  
     private void writeNote(XmlWriter out) {
 198  0
         out.println("");
 199  0
         out.indentPrintln("<!-- ********************************************************");
 200  0
         out.incrementIndent();
 201  0
         out.indentPrintln("                       NOTE");
 202  0
         out.indentPrintln("          THIS FILE WAS INTENDED TO BE MODIFIED");
 203  0
         out.println("");
 204  0
         out.indentPrintln("While this file was originally generated on " + new Date() + ", it");
 205  0
         out.indentPrintln("was intended to be subsequently modified by hand.");
 206  0
         out.indentPrintln("It imports a corresponding ks-XXXX-dictionary-generated.xml file, ");
 207  0
         out.indentPrintln("that was also automatically generated by the ContractDocMojo.");
 208  0
         out.indentPrintln("This file gives you the ability to layer on addiditional definitions and constrints");
 209  0
         out.indentPrintln("that are not/cannot be generated simply by reading the service contract.");
 210  0
         out.println("");
 211  0
         out.indentPrintln("The goal of this file is to be able to re-generate the corresponding");
 212  0
         out.indentPrintln("ks-XXXX-dictionary-generated.xml file without affecting these manually entered additions");
 213  0
         out.indentPrintln("that are encoded here.");
 214  0
         out.decrementIndent();
 215  0
         out.indentPrintln("************************************************************* -->");
 216  0
     }
 217  
 
 218  
     private void writeGeneratedImports(XmlWriter out) {
 219  
         // don't actually generate imports because it slows down the springbean generation
 220  0
         out.writeCommentBox("The following file is required for this file to load:\n ks-base-dictionary.xml\nplus any of its dependencies");
 221  0
         out.indentPrintln("<import resource=\"classpath:ks-base-dictionary.xml\"/>");
 222  
         // TODO: only write out the ones that are used in this structure
 223  
 //        out.indentPrintln("<import resource=\"classpath:ks-RichTextInfo-dictionary.xml\"/>");
 224  
 //        out.indentPrintln("<import resource=\"classpath:ks-MetaInfo-dictionary.xml\"/>");
 225  0
     }
 226  
 
 227  
     private void writeManualImports(XmlWriter out) {
 228  0
         out.writeComment("The following file gets generated during the build and gets put into the target/classes directory");
 229  0
         out.indentPrintln("<import resource=\"classpath:ks-" + initUpper(className) + "-dictionary-generated.xml\"/>");
 230  0
         List<String> imports = this.getComplexSubObjectsThatAreLists();
 231  0
         if (!imports.isEmpty()) {
 232  0
             out.writeComment("TODO: remove these once the jira about lists of complex objects gets fixed");
 233  0
             for (String impName : imports) {
 234  0
                 out.indentPrintln("<import resource=\"classpath:ks-" + initUpper(impName) + "-dictionary.xml\"/>");
 235  
             }
 236  
         }
 237  0
     }
 238  
 
 239  
     private List<String> getComplexSubObjectsThatAreLists() {
 240  0
         List<String> list = new ArrayList();
 241  0
         for (MessageStructure ms : this.messageStructures) {
 242  0
             switch (this.calculateCategory(ms)) {
 243  
                 case LIST_OF_COMPLEX:
 244  0
                     list.add(this.stripListOffEnd(ms.getType()));
 245  
             }
 246  
         }
 247  0
         return list;
 248  
     }
 249  
 
 250  
     private String stripListOffEnd(String name) {
 251  0
         if (name.endsWith("List")) {
 252  0
             return name.substring(0, name.length() - "List".length());
 253  
         }
 254  0
         return name;
 255  
     }
 256  
 
 257  
     private String calcDataObjectClass(XmlType xmlType) {
 258  
         // this is those packages that are not included in the sources for Enroll-API for the model
 259  
         // so the package is null but the name is the full package spec
 260  0
         if (xmlType.getJavaPackage() == null || xmlType.getJavaPackage().isEmpty()) {
 261  0
             return xmlType.getName();
 262  
         }
 263  0
         return xmlType.getJavaPackage() + "." + initUpper(xmlType.getName());
 264  
     }
 265  
 
 266  
     private void writeGeneratedObjectStructure(XmlWriter out) {
 267  
         //Step 1, create the abstract structure
 268  0
         out.println("");
 269  0
         out.indentPrintln("<!-- " + className + "-->");
 270  0
         out.indentPrintln("<bean id=\"" + initUpper(className) + "-generated\" abstract=\"true\" parent=\"DataObjectEntry\">");
 271  0
         out.incrementIndent();
 272  0
         writeProperty("name", initLower(className), out);
 273  0
         writeProperty("dataObjectClass", calcDataObjectClass(xmlType), out);
 274  0
         writeProperty("objectLabel", calcObjectLabel(), out);
 275  0
         writePropertyValue("objectDescription", xmlType.getDesc(), out);
 276  0
         String titleAttribute = calcTitleAttribute();
 277  0
         if (titleAttribute != null) {
 278  0
             writeProperty("titleAttribute", titleAttribute, out);
 279  
         }
 280  0
         out.indentPrintln("<property name=\"primaryKeys\">");
 281  0
         List<String> pks = calcPrimaryKeys();
 282  0
         if (pks != null && !pks.isEmpty()) {
 283  0
             out.incrementIndent();
 284  0
             out.indentPrintln("<list>");
 285  0
             out.incrementIndent();
 286  0
             for (String pk : pks) {
 287  0
                 addValue(pk);
 288  
             }
 289  0
             out.decrementIndent();
 290  0
             out.indentPrintln("</list>");
 291  0
             out.decrementIndent();
 292  
         }
 293  0
         out.indentPrintln("</property>");
 294  
 
 295  0
         this.writeAllGeneratedAttributeRefBeans(className, null, new Stack<String>(), this.messageStructures, out);
 296  
 
 297  0
         out.indentPrintln("</bean>");
 298  
 
 299  
         //Step 2, loop through attributes
 300  0
         this.writeGeneratedAttributeDefinitions(className, null, new Stack<String>(), this.messageStructures, out);
 301  0
     }
 302  
 
 303  
     private void writeAllGeneratedAttributeRefBeans(String currentClassName,
 304  
             String parentName,
 305  
             Stack<String> parents,
 306  
             List<MessageStructure> fields,
 307  
             XmlWriter out) {
 308  0
         if (parents.contains(currentClassName)) {
 309  0
             return;
 310  
         }
 311  0
         out.println("");
 312  0
         out.indentPrintln("<property name=\"attributes\">");
 313  0
         out.incrementIndent();
 314  0
         out.indentPrintln("<list>");
 315  0
         out.incrementIndent();
 316  0
         this.writeGeneratedAttributeRefBeans(currentClassName, parentName, parents, fields, out, Category.PRIMITIVE);
 317  0
         out.decrementIndent();
 318  0
         out.indentPrintln("</list>");
 319  0
         out.decrementIndent();
 320  0
         out.indentPrintln("</property>");
 321  
 
 322  0
         out.println("");
 323  0
         out.indentPrintln("<property name=\"complexAttributes\">");
 324  0
         out.incrementIndent();
 325  0
         out.indentPrintln("<list>");
 326  0
         out.incrementIndent();
 327  0
         this.writeGeneratedAttributeRefBeans(currentClassName, parentName, parents, fields, out, Category.COMPLEX);
 328  0
         out.decrementIndent();
 329  0
         out.indentPrintln("</list>");
 330  0
         out.decrementIndent();
 331  0
         out.indentPrintln("</property>");
 332  
 
 333  0
         out.println("");
 334  0
         out.indentPrintln("<property name=\"collections\">");
 335  0
         out.incrementIndent();
 336  0
         out.indentPrintln("<list>");
 337  0
         out.incrementIndent();
 338  0
         this.writeGeneratedAttributeRefBeans(currentClassName, parentName, parents, fields, out, Category.LIST_OF_COMPLEX);
 339  0
         out.decrementIndent();
 340  0
         out.indentPrintln("</list>");
 341  0
         out.decrementIndent();
 342  0
         out.indentPrintln("</property>");
 343  0
         out.decrementIndent();
 344  0
     }
 345  
 
 346  
     private void addValue(String value) {
 347  0
         gwriter.indentPrintln("<value>" + value + "</value>");
 348  0
     }
 349  
 
 350  
     private String calcObjectLabel() {
 351  0
         String label = this.className;
 352  0
         if (label.endsWith("Info")) {
 353  0
             label = label.substring(0, label.length() - "Info".length());
 354  
         }
 355  0
         label = initUpper(label);
 356  0
         return splitCamelCase(label);
 357  
     }
 358  
 
 359  
     // got this from http://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java
 360  
     private static String splitCamelCase(String s) {
 361  0
         if (s == null) {
 362  0
             return null;
 363  
         }
 364  0
         return s.replaceAll(
 365  
                 String.format("%s|%s|%s",
 366  
                 "(?<=[A-Z])(?=[A-Z][a-z])",
 367  
                 "(?<=[^A-Z])(?=[A-Z])",
 368  
                 "(?<=[A-Za-z])(?=[^A-Za-z])"),
 369  
                 " ");
 370  
     }
 371  
 
 372  0
     private enum Category {
 373  
 
 374  0
         PRIMITIVE, COMPLEX, LIST_OF_COMPLEX, LIST_OF_PRIMITIVE, DYNAMIC_ATTRIBUTE
 375  
     };
 376  
 
 377  
     private Category calculateCategory(MessageStructure ms) {
 378  0
         if (ms.getShortName().equals("attributes")) {
 379  0
             return Category.DYNAMIC_ATTRIBUTE;
 380  
         }
 381  0
         String childXmlTypeName = this.stripListOffEnd(ms.getType());
 382  0
         XmlType childXmlType = this.finder.findXmlType(childXmlTypeName);
 383  0
         if (childXmlType == null) {
 384  0
             throw new IllegalStateException(childXmlTypeName);
 385  
         }
 386  0
         if (ms.getType().endsWith("List")) {
 387  0
             if (childXmlType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
 388  0
                 return Category.LIST_OF_COMPLEX;
 389  
             }
 390  0
             return Category.LIST_OF_PRIMITIVE;
 391  
         }
 392  0
         if (childXmlType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
 393  0
             return Category.COMPLEX;
 394  
         }
 395  0
         return Category.PRIMITIVE;
 396  
     }
 397  
 
 398  
     private void writeGeneratedAttributeRefBeans(String currentClass,
 399  
             String parentName,
 400  
             Stack<String> parents,
 401  
             List<MessageStructure> fields,
 402  
             XmlWriter out,
 403  
             Category filter) {
 404  0
         if (parents.contains(currentClass)) {
 405  0
             return;
 406  
         }
 407  0
         for (MessageStructure ms : fields) {
 408  0
             Category category = this.calculateCategory(ms);
 409  0
             if (!category.equals(filter)) {
 410  0
                 continue;
 411  
             }
 412  0
             String childXmlTypeName = this.stripListOffEnd(ms.getType());
 413  0
             XmlType childXmlType = this.finder.findXmlType(childXmlTypeName);
 414  0
             if (childXmlType == null) {
 415  0
                 throw new IllegalStateException(childXmlTypeName);
 416  
             }
 417  0
             String pathName = calcPathName(parentName, ms);
 418  0
             String beanName = calcBeanName(pathName);
 419  
             // TODO: change this once they fix the list of complex jira
 420  
 //            if (filter.equals(Category.LIST_OF_COMPLEX)) {
 421  
 //                beanName = initUpper(childXmlTypeName);
 422  
 //            }
 423  0
             out.indentPrintln("<ref bean=\"" + beanName + "\"/>");
 424  
 //
 425  
 //            // Add complex sub-types fields
 426  
 //            switch (category) {
 427  
 //                case COMPLEX:
 428  
 //                case LIST_OF_COMPLEX:
 429  
 //                    parents.push(currentClass);
 430  
 //                    List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName);
 431  
 //                    writeGeneratedAttributeRefBeans(childXmlTypeName, pathName, parents, childFields, out, filter);
 432  
 //                    parents.pop();
 433  
 //            }
 434  0
         }
 435  0
     }
 436  
 
 437  
     private void writeGeneratedAttributeDefinitions(String currentClassName,
 438  
             String parentName,
 439  
             Stack<String> parents,
 440  
             List<MessageStructure> fields,
 441  
             XmlWriter out) {
 442  0
         if (parents.contains(currentClassName)) {
 443  0
             return;
 444  
         }
 445  0
         for (MessageStructure ms : fields) {
 446  0
             Category category = this.calculateCategory(ms);
 447  0
             switch (category) {
 448  
                 case DYNAMIC_ATTRIBUTE:
 449  0
                     continue;
 450  
             }
 451  0
             String pathName = calcPathName(parentName, ms);
 452  0
             String beanName = calcBeanName(pathName);
 453  0
             String childXmlTypeName = this.stripListOffEnd(ms.getType());
 454  0
             XmlType childXmlType = this.finder.findXmlType(childXmlTypeName);
 455  0
             if (childXmlType == null) {
 456  0
                 throw new IllegalStateException(childXmlTypeName);
 457  
             }
 458  0
             writeGeneratedAttributeDefinition(currentClassName, parentName, parents, ms, out);
 459  
 
 460  
             // Add complex sub-types fields
 461  0
             switch (category) {
 462  
                 case COMPLEX:
 463  
 //                case LIST_OF_COMPLEX:
 464  0
                     parents.push(currentClassName);
 465  0
                     List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName);
 466  0
                     writeGeneratedAttributeDefinitions(childXmlTypeName, pathName, parents, childFields, out);
 467  0
                     parents.pop();
 468  
             }
 469  0
         }
 470  0
     }
 471  
 
 472  
     private boolean shouldWriteDetails(MessageStructure ms) {
 473  0
         if (predefinedFieldMap.get(ms.getShortName().toLowerCase()) == null) {
 474  0
             return true;
 475  
         }
 476  0
         if (ms.isOverriden()) {
 477  0
             return true;
 478  
         }
 479  
         // don't write out details for predefined fields that have not been overridden
 480  0
         return false;
 481  
     }
 482  
 
 483  
     private void writeGeneratedAttributeDefinition(String currentClassName, String parentName, Stack<String> parents, MessageStructure ms, XmlWriter out) {
 484  
 
 485  
         //Create the abstract field
 486  0
         String pathName = calcPathName(parentName, ms);
 487  0
         String beanName = calcBeanName(pathName);
 488  0
         String baseKualiParentBean = this.calcBaseKualiParentBean(ms);
 489  0
         out.println("");
 490  0
         out.indentPrintln("<bean id=\"" + beanName + "-generated\" abstract=\"true\" parent=\"" + baseKualiParentBean + "\">");
 491  0
         out.incrementIndent();
 492  0
         writeProperty("name", calcSimpleName(ms), out);
 493  0
         switch (this.calculateCategory(ms)) {
 494  
             case PRIMITIVE:
 495  0
                 if (this.shouldWriteDetails(ms)) {
 496  0
                     writeProperty("shortLabel", calcShortLabel(ms), out);
 497  0
                     writePropertyValue("summary", calcSummary(ms), out);
 498  0
                     writeProperty("label", calcLabel(ms), out);
 499  0
                     writePropertyValue("description", calcDescription(ms), out);
 500  0
                     if (this.calcReadOnly(ms)) {
 501  0
                         this.writeReadOnlyAttributeSecurity(out);
 502  
                     }
 503  0
                     writeProperty("required", calcRequired(ms), out);
 504  
                 }
 505  
                 break;
 506  
             case LIST_OF_PRIMITIVE:
 507  
                 // TODO: deal with once https://jira.kuali.org/browse/KULRICE-5439 is fixed                    
 508  
                 // for now treat the same as List of Complex, i.e. CollectionDefinition
 509  0
                 writeProperty("shortLabel", calcShortLabel(ms), out);
 510  0
                 writePropertyValue("summary", calcSummary(ms), out);
 511  0
                 writeProperty("label", calcLabel(ms), out);
 512  0
                 writeProperty("elementLabel", calcElementLabel(ms), out);
 513  0
                 writePropertyValue("description", calcDescription(ms), out);
 514  0
                 writeProperty("minOccurs", calcMinOccurs(ms), out);
 515  0
                 writeProperty("dataObjectClass", calcDataObjectClass(ms), out);
 516  0
                 break;
 517  
             case LIST_OF_COMPLEX:
 518  0
                 writeProperty("shortLabel", calcShortLabel(ms), out);
 519  0
                 writePropertyValue("summary", calcSummary(ms), out);
 520  0
                 writeProperty("label", calcLabel(ms), out);
 521  0
                 writeProperty("elementLabel", calcElementLabel(ms), out);
 522  0
                 writePropertyValue("description", calcDescription(ms), out);
 523  0
                 writeProperty("minOccurs", calcMinOccurs(ms), out);
 524  0
                 writeProperty("dataObjectClass", calcDataObjectClass(ms), out);
 525  0
                 break;
 526  
             case COMPLEX:
 527  0
                 writeProperty("shortLabel", calcShortLabel(ms), out);
 528  0
                 writePropertyValue("summary", calcSummary(ms), out);
 529  0
                 writeProperty("label", calcLabel(ms), out);
 530  0
                 writePropertyValue("description", calcDescription(ms), out);
 531  0
                 writeProperty("required", calcRequired(ms), out);
 532  0
                 writePropertyStart("dataObjectEntry", out);
 533  0
                 out.indentPrintln("<bean parent=\"DataObjectEntry\">");
 534  0
                 out.incrementIndent();
 535  0
                 writeProperty("name", calcSimpleName(ms), out);
 536  0
                 writeProperty("dataObjectClass", calcDataObjectClass(ms), out);
 537  0
                 writeProperty("objectLabel", calcLabel(ms), out);
 538  0
                 writePropertyValue("objectDescription", calcDescription(ms), out);
 539  
 
 540  0
                 String childXmlTypeName = this.stripListOffEnd(ms.getType());
 541  0
                 List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName);
 542  0
                 writeAllGeneratedAttributeRefBeans(childXmlTypeName, pathName, parents, childFields, out);
 543  0
                 out.indentPrintln("</bean>");
 544  0
                 writePropertyEnd(out);
 545  0
                 break;
 546  
             default:
 547  0
                 throw new IllegalStateException("unknown/unhandled type " + ms.getId());
 548  
         }
 549  0
         out.decrementIndent();
 550  
         // TODO: implement maxoccurs
 551  
 //        if (isList(pd)) {
 552  
 //            addProperty("maxOccurs", "" + DictionaryConstants.UNBOUNDED, s);
 553  
 //        }
 554  0
         out.indentPrintln("</bean>");
 555  0
     }
 556  
 
 557  
     private String calcDataObjectClass(MessageStructure ms) {
 558  0
         XmlType msType = this.finder.findXmlType(this.stripListOffEnd(ms.getType()));
 559  0
         return this.calcDataObjectClass(msType);
 560  
     }
 561  
 
 562  
     private String calcBeanName(String pathName) {
 563  0
         return initUpper(className) + "." + pathName;
 564  
     }
 565  
 
 566  
     private String calcPathName(String parentName, MessageStructure ms) {
 567  0
         String name = this.calcSimpleName(ms);
 568  0
         if (parentName == null) {
 569  0
             return name;
 570  
         }
 571  0
         return parentName + "." + name;
 572  
     }
 573  
 
 574  
     private String calcSimpleName(MessageStructure ms) {
 575  0
         String name = initLower(ms.getShortName());
 576  0
         return name;
 577  
     }
 578  
 
 579  
     private boolean calcReadOnly(MessageStructure ms) {
 580  0
         if (ms.getReadOnly() == null) {
 581  0
             return false;
 582  
         }
 583  0
         return true;
 584  
     }
 585  
 
 586  
     private void writeReadOnlyAttributeSecurity(XmlWriter out) {
 587  0
         out.indentPrintln("<!-- commented out until KRAD bug gets fixed that requires mask to also be entered");
 588  0
         out.indentPrintln("<property name=\"attributeSecurity\">");
 589  0
         out.indentPrintln("<ref bean=\"BaseKuali.readOnlyAttributeSecurity\"/>");
 590  0
         out.indentPrintln("</property>");
 591  0
         out.indentPrintln("-->");
 592  0
     }
 593  
 
 594  
     private String calcElementLabel(MessageStructure ms) {
 595  0
         String label = this.calcShortLabel(ms);
 596  0
         if (label.endsWith("s")) {
 597  0
             label = label.substring(0, label.length() - 1);
 598  
         }
 599  0
         return label;
 600  
     }
 601  
 
 602  
     private String calcShortLabel(MessageStructure ms) {
 603  0
         return this.splitCamelCase(initUpper(ms.getShortName()));
 604  
     }
 605  
 
 606  
     private String calcLabel(MessageStructure ms) {
 607  0
         return ms.getName();
 608  
     }
 609  
 
 610  
     private String calcSummary(MessageStructure ms) {
 611  0
         BreakIterator bi = BreakIterator.getSentenceInstance();
 612  0
         String description = ms.getDescription();
 613  0
         if (description == null) {
 614  0
             return "???";
 615  
         }
 616  0
         bi.setText(ms.getDescription());
 617  
         // one big sentence
 618  0
         if (bi.next() == BreakIterator.DONE) {
 619  0
             return ms.getDescription();
 620  
         }
 621  0
         String firstSentence = description.substring(0, bi.current());
 622  0
         return firstSentence;
 623  
     }
 624  
 
 625  
     private String calcDescription(MessageStructure ms) {
 626  0
         return ms.getDescription();
 627  
     }
 628  
 
 629  
     private String calcMinOccurs(MessageStructure ms) {
 630  0
         String required = this.calcRequired(ms);
 631  0
         if ("false".equals(required)) {
 632  0
             return "0";
 633  
         }
 634  0
         return "1";
 635  
     }
 636  
 
 637  
     private String calcRequired(MessageStructure ms) {
 638  0
         if (ms.getRequired() == null) {
 639  0
             return "false";
 640  
         }
 641  0
         if (ms.getRequired().equalsIgnoreCase("Required")) {
 642  0
             return "true";
 643  
         }
 644  
         // TODO: figure out what to do if it is qualified like "required on update"
 645  0
         return "false";
 646  
     }
 647  
 
 648  
     private void writeManualObjectStructure(XmlWriter out) {
 649  
         //Step 1, create the parent bean
 650  0
         out.println("");
 651  0
         out.indentPrintln("<!-- " + className + "-->");
 652  
         //Create the actual instance of the bean
 653  0
         out.indentPrintln("<bean id=\"" + initUpper(className) + "\" parent=\"" + initUpper(className) + "-parent\"/>");
 654  0
         out.indentPrintln("<bean id=\"" + initUpper(className) + "-parent\" abstract=\"true\" parent=\"" + initUpper(className) + "-generated\">");
 655  0
         out.writeComment("insert any overrides to the generated object definitions here");
 656  0
         out.indentPrintln("</bean>");
 657  
 
 658  
         //Step 2, loop through attributes
 659  0
         this.writeManualAttributeDefinitions(className, null, new Stack<String>(), this.messageStructures, out);
 660  
 
 661  0
     }
 662  
 
 663  
     private void writeManualAttributeDefinitions(String currentClass, String parentName,
 664  
             Stack<String> parents, List<MessageStructure> fields, XmlWriter out) {
 665  0
         if (parents.contains(currentClass)) {
 666  0
             return;
 667  
         }
 668  0
         for (MessageStructure ms : fields) {
 669  0
             Category cat = this.calculateCategory(ms);
 670  
             // skip dynamic attributes
 671  0
             switch (cat) {
 672  
                 case DYNAMIC_ATTRIBUTE:
 673  0
                     continue;
 674  
             }
 675  
 
 676  0
             String pathName = calcPathName(parentName, ms);
 677  0
             String beanName = calcBeanName(pathName);
 678  0
             String childXmlTypeName = this.stripListOffEnd(ms.getType());
 679  0
             XmlType childXmlType = this.finder.findXmlType(childXmlTypeName);
 680  0
             if (childXmlType == null) {
 681  0
                 throw new IllegalStateException(childXmlTypeName);
 682  
             }
 683  0
             writeManualAttributeDefinition(currentClass, parentName, ms, out);
 684  
 
 685  
             // Add complex sub-types fields
 686  0
             switch (cat) {
 687  
                 case COMPLEX:
 688  0
                     parents.push(currentClass);
 689  0
                     List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName);
 690  
 //                if (childFields.isEmpty()) {
 691  
 //                    throw new IllegalStateException(childXmlTypeName);
 692  
 //                }
 693  0
                     writeManualAttributeDefinitions(childXmlTypeName, pathName, parents, childFields, out);
 694  0
                     parents.pop();
 695  
             }
 696  0
         }
 697  0
     }
 698  
 
 699  
     private void writeManualAttributeDefinition(String currentClass, String parentName, MessageStructure ms, XmlWriter out) {
 700  
 
 701  
         //Create the abstract field
 702  0
         String pathName = calcPathName(parentName, ms);
 703  0
         String beanName = calcBeanName(pathName);
 704  
 //        String baseKualiType = this.calcBaseKualiType(ms);
 705  
         //Create the actual bean instance
 706  0
         out.println("");
 707  0
         out.indentPrintln("<bean id=\"" + beanName + "\" parent=\"" + beanName + "-parent\"/>");
 708  0
         out.indentPrintln("<bean id=\"" + beanName + "-parent\" abstract=\"true\" parent=\"" + beanName + "-generated\">");
 709  0
         out.writeComment("insert any overrides to the generated attribute definitions here");
 710  0
         out.indentPrintln("</bean>");
 711  0
     }
 712  
     /**
 713  
      * list of predefined fields that should map to entries in ks-base-dictionary.xml
 714  
      */
 715  0
     private static Map<String, String> predefinedFieldMap = null;
 716  
 
 717  
     {
 718  0
         Map<String, String> map = new HashMap<String, String>();
 719  0
         map.put("id", "BaseKuali.id");
 720  0
         map.put("key", "BaseKuali.key");
 721  0
         map.put("name", "BaseKuali.name");
 722  0
         map.put("descr", "BaseKuali.descr");
 723  0
         map.put("plain", "BaseKuali.descr.plain");
 724  0
         map.put("formatted", "BaseKuali.descr.formatted");
 725  0
         map.put("desc", "BaseKuali.desc"); // r1 compatibility
 726  0
         map.put("typeKey", "BaseKuali.typeKey");
 727  0
         map.put("stateKey", "BaseKuali.stateKey");
 728  0
         map.put("type", "BaseKuali.type"); // r1 compatibility
 729  0
         map.put("state", "BaseKuali.state"); // r1 compatibility
 730  0
         map.put("effectiveDate", "BaseKuali.effectiveDate");
 731  0
         map.put("expirationDate", "BaseKuali.expirationDate");
 732  0
         map.put("meta", "BaseKuali.meta");
 733  0
         map.put("createTime", "BaseKuali.meta.createTime");
 734  0
         map.put("updateTime", "BaseKuali.meta.updateTime");
 735  0
         map.put("createId", "BaseKuali.meta.createId");
 736  0
         map.put("updateId", "BaseKuali.meta.updateId");
 737  0
         map.put("versionInd", "BaseKuali.meta.versionInd");
 738  
         // convert to lower case
 739  0
         predefinedFieldMap = new HashMap(map.size());
 740  0
         for (String key : map.keySet()) {
 741  0
             predefinedFieldMap.put(key.toLowerCase(), map.get(key));
 742  
         }
 743  
     }
 744  
     /**
 745  
      * list of fields that if they end with the key the should be based on the entry
 746  
      * in ks-base-dictionary.xml
 747  
      */
 748  0
     private static Map<String, String> endsWithMap = null;
 749  
 
 750  
     {
 751  0
         Map<String, String> map = new HashMap<String, String>();
 752  0
         map.put("startDate", "BaseKuali.startDate");
 753  0
         map.put("endDate", "BaseKuali.endDate");
 754  0
         map.put("start", "BaseKuali.start");
 755  0
         map.put("end", "BaseKuali.end");
 756  0
         map.put("OrgId", "BaseKuali.orgId");
 757  0
         map.put("OrgIds", "BaseKuali.orgId");
 758  0
         map.put("PersonId", "BaseKuali.personId");
 759  0
         map.put("PersonIds", "BaseKuali.personId");
 760  0
         map.put("PrincipalId", "BaseKuali.principalId");
 761  0
         map.put("PrincipalIds", "BaseKuali.principalId");
 762  0
         map.put("CluId", "BaseKuali.cluId");
 763  0
         map.put("CluIds", "BaseKuali.cluId");
 764  0
         map.put("LuiId", "BaseKuali.luiId");
 765  0
         map.put("LuiIds", "BaseKuali.luiId");
 766  0
         map.put("AtpId", "BaseKuali.atpId");
 767  0
         map.put("AtpIds", "BaseKuali.atpId");
 768  0
         map.put("TermId", "BaseKuali.termId");
 769  0
         map.put("TermIds", "BaseKuali.termId");
 770  0
         map.put("HolidayCalendarId", "BaseKuali.holidayCalendarId");
 771  0
         map.put("HolidayCalendarIds", "BaseKuali.holidayCalendarId");
 772  0
         map.put("Code", "BaseKuali.code");
 773  
         // convert to lower case
 774  0
         endsWithMap = new HashMap(map.size());
 775  0
         for (String key : map.keySet()) {
 776  0
             endsWithMap.put(key.toLowerCase(), map.get(key));
 777  
         }
 778  
     }
 779  
     /**
 780  
      * list of types that if the type matches this key then 
 781  
      * it should be based on that type entry as defined in the 
 782  
      * ks-base-dictionary.xml
 783  
      */
 784  0
     private static Map<String, String> typeMap = null;
 785  
 
 786  
     {
 787  0
         Map<String, String> map = new HashMap<String, String>();
 788  0
         map.put("String", "BaseKuali.string");
 789  0
         map.put("DateTime", "BaseKuali.dateTime");
 790  0
         map.put("Date", "BaseKuali.date");
 791  0
         map.put("Boolean", "BaseKuali.boolean");
 792  0
         map.put("Integer", "BaseKuali.integer");
 793  0
         map.put("Long", "BaseKuali.long");
 794  0
         map.put("Float", "BaseKuali.float");
 795  0
         map.put("Double", "BaseKuali.double");
 796  
         // convert to lower case
 797  0
         typeMap = new HashMap(map.size());
 798  0
         for (String key : map.keySet()) {
 799  0
             typeMap.put(key.toLowerCase(), map.get(key));
 800  
         }
 801  
     }
 802  
 
 803  
     private String calcBaseKualiParentBean(MessageStructure ms) {
 804  0
         switch (this.calculateCategory(ms)) {
 805  
             case COMPLEX:
 806  0
                 return "ComplexAttributeDefinition";
 807  
             case LIST_OF_COMPLEX:
 808  0
                 return "CollectionDefinition";
 809  
             case LIST_OF_PRIMITIVE:
 810  
                 // TODO: deal with once https://jira.kuali.org/browse/KULRICE-5439 is fixed
 811  0
                 System.out.println("Treating list of primitives same as collection defintion: " + ms.getId());
 812  0
                 return "CollectionDefinition";
 813  
             case PRIMITIVE:
 814  0
                 break;
 815  
             default:
 816  0
                 throw new IllegalStateException("unknown/uhandled category " + ms.getId());
 817  
         }
 818  0
         String name = ms.getShortName();
 819  0
         String baseKualiType = predefinedFieldMap.get(name.toLowerCase());
 820  0
         if (baseKualiType != null) {
 821  0
             return baseKualiType;
 822  
         }
 823  
 
 824  
         // check ends with
 825  0
         for (String key : endsWithMap.keySet()) {
 826  0
             if (name.toLowerCase().endsWith(key)) {
 827  0
                 return endsWithMap.get(key);
 828  
             }
 829  
         }
 830  
 
 831  
         // now key off the type
 832  0
         String type = this.stripListOffEnd(ms.getType());
 833  0
         baseKualiType = typeMap.get(type.toLowerCase());
 834  0
         if (baseKualiType != null) {
 835  0
             return baseKualiType;
 836  
         }
 837  0
         throw new IllegalStateException("All primitives are supposed to be handled by a predefined type " + ms.getId());
 838  
     }
 839  
 
 840  
     private String calcTitleAttribute() {
 841  0
         MessageStructure ms = null;
 842  0
         ms = this.findMessageStructure("name");
 843  0
         if (ms != null) {
 844  0
             return initLower(ms.getShortName());
 845  
         }
 846  0
         ms = this.findMessageStructure("title");
 847  0
         if (ms != null) {
 848  0
             return initLower(ms.getShortName());
 849  
         }
 850  0
         ms = this.findMessageStructureEndsWith("name");
 851  0
         if (ms != null) {
 852  0
             return initLower(ms.getShortName());
 853  
         }
 854  0
         ms = this.findMessageStructureEndsWith("title");
 855  0
         if (ms != null) {
 856  0
             return initLower(ms.getShortName());
 857  
         }
 858  0
         ms = this.findMessageStructure("key");
 859  0
         if (ms != null) {
 860  0
             return initLower(ms.getShortName());
 861  
         }
 862  
         // TODO: consider checking for ID and just returning null
 863  0
         System.out.println("XmlKradBaseDictionaryCreator: could not find a title attribute for " + this.className);
 864  
 //        ms = this.findMessageStructure("id");
 865  
 //        if (ms != null) {
 866  
 //            return initLower(ms.getShortName());
 867  
 //        }
 868  0
         return null;
 869  
     }
 870  
 
 871  
     private MessageStructure findMessageStructureEndsWith(String shortNameEndsWith) {
 872  0
         shortNameEndsWith = shortNameEndsWith.toLowerCase();
 873  0
         for (MessageStructure ms : this.messageStructures) {
 874  0
             if (ms.getShortName().toLowerCase().endsWith(shortNameEndsWith)) {
 875  0
                 return ms;
 876  
             }
 877  
         }
 878  0
         return null;
 879  
     }
 880  
 
 881  
     private MessageStructure findMessageStructure(String shortName) {
 882  0
         for (MessageStructure ms : this.messageStructures) {
 883  0
             if (ms.getShortName().equalsIgnoreCase(shortName)) {
 884  0
                 return ms;
 885  
             }
 886  
         }
 887  0
         return null;
 888  
     }
 889  
 
 890  
     private MessageStructure getMessageStructure(String shortName) {
 891  0
         MessageStructure ms = this.findMessageStructure(shortName);
 892  0
         if (ms == null) {
 893  0
             throw new IllegalArgumentException(shortName);
 894  
         }
 895  0
         return ms;
 896  
     }
 897  
 
 898  
     private List<String> calcPrimaryKeys() {
 899  0
         MessageStructure ms = null;
 900  0
         ms = this.findMessageStructure("id");
 901  0
         if (ms != null) {
 902  0
             return Collections.singletonList(initLower(ms.getShortName()));
 903  
         }
 904  0
         ms = this.findMessageStructure("key");
 905  0
         if (ms != null) {
 906  0
             return Collections.singletonList(initLower(ms.getShortName()));
 907  
         }
 908  
         // just use the first field
 909  0
         if (this.messageStructures.size() > 0) {
 910  0
             ms = this.messageStructures.get(0);
 911  0
             return Collections.singletonList(ms.getShortName());
 912  
         }
 913  0
         return Collections.EMPTY_LIST;
 914  
     }
 915  
 
 916  
     private void writePropertyStart(String propertyName, XmlWriter out) {
 917  0
         out.indentPrintln("<property name=\"" + propertyName + "\">");
 918  0
         out.incrementIndent();
 919  0
     }
 920  
 
 921  
     private void writePropertyEnd(XmlWriter out) {
 922  0
         out.decrementIndent();
 923  0
         out.indentPrintln("</property>");
 924  0
     }
 925  
 
 926  
     private void writeProperty(String propertyName, String propertyValue, XmlWriter out) {
 927  0
         out.indentPrintln("<property name=\"" + propertyName + "\" value=\"" + replaceDoubleQuotes(propertyValue) + "\"/>");
 928  0
     }
 929  
 
 930  
     private void writePropertyValue(String propertyName, String propertyValue, XmlWriter out) {
 931  0
         writePropertyStart(propertyName, out);
 932  0
         out.indentPrintln("<value>");
 933  
         // TODO: worry about the value starting on a new line i.e. is it trimmed when loaded?
 934  0
         out.println(escapeHtml(propertyValue));
 935  0
         out.indentPrintln("</value>");
 936  0
         writePropertyEnd(out);
 937  0
     }
 938  
 
 939  
     private String escapeHtml(String str) {
 940  0
         if (str == null) {
 941  0
             return null;
 942  
         }
 943  0
         return StringEscapeUtils.escapeHtml(str);
 944  
     }
 945  
 
 946  
     private String replaceDoubleQuotes(String str) {
 947  0
         if (str == null) {
 948  0
             return null;
 949  
         }
 950  0
         return str.replace("\"", "'");
 951  
     }
 952  
 }