001/**
002 * Copyright 2004-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.student.datadictionary.util;
017
018import java.io.File;
019import java.io.FileNotFoundException;
020import java.io.FileOutputStream;
021import java.io.OutputStream;
022import java.io.PrintStream;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.Date;
027import java.util.List;
028import java.util.Map;
029import java.util.Stack;
030
031import org.kuali.rice.krad.datadictionary.AttributeDefinition;
032import org.kuali.rice.krad.datadictionary.AttributeDefinitionBase;
033import org.kuali.rice.krad.datadictionary.CollectionDefinition;
034import org.kuali.rice.krad.datadictionary.ComplexAttributeDefinition;
035import org.kuali.rice.krad.datadictionary.DataObjectEntry;
036import org.kuali.rice.krad.datadictionary.validation.constraint.BaseConstraint;
037import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
038import org.kuali.rice.krad.datadictionary.validation.constraint.CommonLookupParam;
039import org.kuali.rice.krad.datadictionary.validation.constraint.LookupConstraint;
040import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
041import org.kuali.rice.krad.datadictionary.validation.constraint.WhenConstraint;
042import org.kuali.rice.krad.uif.control.Control;
043import org.kuali.rice.krad.uif.control.TextControl;
044import org.kuali.student.contract.model.impl.ServiceContractModelPescXsdLoader;
045import org.kuali.student.contract.model.util.VersionLinesUtility;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049public class DictionaryFormatter {
050
051    private static Logger log = LoggerFactory.getLogger(DictionaryFormatter.class);
052    
053    private DataObjectEntry doe;
054    private Map<String, DataObjectEntry> beansOfType;
055    private String beanId;
056    private String outputFileName;
057
058    public DictionaryFormatter(DataObjectEntry doe, Map<String, DataObjectEntry> beansOfType, String beanId, String outputFileName) {
059        this.doe = doe;
060        this.beansOfType = beansOfType;
061        this.beanId = beanId;
062        this.outputFileName = outputFileName;
063    }
064
065    public void formatForHtml(String projectVersion, String formattedDate) {
066        File file = new File(this.outputFileName);
067        OutputStream outputStream;
068        try {
069            outputStream = new FileOutputStream(file, false);
070        } catch (FileNotFoundException ex) {
071            throw new IllegalArgumentException(this.outputFileName, ex);
072        }
073        PrintStream out = new PrintStream(outputStream);
074        writeHeader(out, beanId);
075        writeBody(out, projectVersion, formattedDate);
076        writeFooter(out);
077        out.close();
078    }
079
080    public static void writeHeader(PrintStream out, String title) {
081        out.println("<html>");
082        out.println("<head>");
083        writeTag(out, "title", title);
084        out.println ("<style>li.invalid { background: red; }</style>");
085        out.println("</head>");
086        out.println("<body bgcolor=\"#ffffff\" topmargin=0 marginheight=0>");
087    }
088
089    public static void writeFooter(PrintStream out) {
090        out.println("</body>");
091        out.println("</html>");
092    }
093
094    private String initUpper(String str) {
095        if (str == null) {
096            return null;
097        }
098        if (str.length() == 0) {
099            return str;
100        }
101        if (str.length() == 1) {
102            return str.toUpperCase();
103        }
104        return str.substring(0, 1).toUpperCase() + str.substring(1);
105    }
106
107    private void writeBody(PrintStream out, String projectVersion, String formattedDate) {
108        
109        VersionLinesUtility.writeVersionTag(out, "<a href=\"index.html\">home</a>", "<a href=\"../contractdocs/" + initUpper(doe.getName()) + ".html\">contract doc</a>", projectVersion, formattedDate);
110//  builder.append ("======= start dump of object structure definition ========");
111        out.println("<h1>" + this.beanId + "</h1>");
112
113        out.println("<br>");
114        out.println("<table border=1>");
115
116        out.println("<tr>");
117        out.println("<th bgcolor=lightblue>");
118        out.println("Name");
119        out.println("</th>");
120        out.println("<td>");
121        out.println(doe.getName());
122        out.println("</td>");
123        out.println("</tr>");
124
125        out.println("<tr>");
126        out.println("<th bgcolor=lightblue>");
127        out.println("Label");
128        out.println("</th>");
129        out.println("<td>");
130        out.println(doe.getObjectLabel());
131        out.println("</td>");
132        out.println("</tr>");
133
134        out.println("<tr>");
135        out.println("<th bgcolor=lightblue>");
136        out.println("JSTL Key");
137        out.println("</th>");
138        out.println("<td>");
139        out.println(doe.getJstlKey());
140        out.println("</td>");
141        out.println("</tr>");
142
143        out.println("<tr>");
144        out.println("<th bgcolor=lightblue>");
145        out.println("Java Class");
146        out.println("</th>");
147        out.println("<td>");
148        out.println(doe.getFullClassName());
149        out.println("</td>");
150        out.println("</tr>");
151        out.println("<tr>");
152
153        if (!doe.getDataObjectClass().getName().equals(doe.getFullClassName())) {
154            out.println("<tr>");
155            out.println("<th bgcolor=lightblue>");
156            out.println("Object Class");
157            out.println("</th>");
158            out.println("<td>");
159            out.println(doe.getDataObjectClass().getName());
160            out.println("</td>");
161            out.println("</tr>");
162            out.println("<tr>");
163        }
164
165        if (!doe.getEntryClass().getName().equals(doe.getFullClassName())) {
166            out.println("<tr>");
167            out.println("<th bgcolor=lightblue>");
168            out.println("Entry Class");
169            out.println("</th>");
170            out.println("<td>");
171            out.println(doe.getEntryClass().getName());
172            out.println("</td>");
173            out.println("</tr>");
174            out.println("<tr>");
175        }
176
177        out.println("<tr>");
178        out.println("<th bgcolor=lightblue>");
179        out.println("Description");
180        out.println("</th>");
181        out.println("<td>");
182        out.println(doe.getObjectDescription());
183        out.println("</td>");
184        out.println("</tr>");
185
186        out.println("<tr>");
187        out.println("<th bgcolor=lightblue>");
188        out.println("Primary Key(s)");
189        out.println("</th>");
190        out.println("<td>");
191        StringBuilder bldr = new StringBuilder();
192        String comma = "";
193        if (doe.getPrimaryKeys() != null) {
194            for (String pk : doe.getPrimaryKeys()) {
195                bldr.append(comma);
196                comma = ", ";
197                bldr.append(pk);
198            }
199        }
200        out.println(bldr.toString());
201        out.println("</td>");
202        out.println("</tr>");
203
204        out.println("<tr>");
205        out.println("<th bgcolor=lightblue>");
206        out.println("Field to use as the title (or name)");
207        out.println("</th>");
208        out.println("<td>");
209        out.println(doe.getTitleAttribute());
210        out.println("</td>");
211        out.println("</tr>");
212
213        out.println("</table>");
214//        out.println("<br>");
215
216        // fields
217        out.println("<h1>Field Definitions</h1>");
218        // check for discrepancies first
219        List<String> discrepancies = new Dictionary2BeanComparer(doe.getFullClassName(), doe).compare();
220        if (discrepancies.isEmpty()) {
221            out.println("No discrepancies were found between the dictionary definition and the java object -- ");
222            out.println("WARNING: take this with a grain of salt - the comparison does not dig into complex sub-objects nor collections so...");
223        } else {
224            out.println("<b>" + discrepancies.size() + " discrepancie(s) were found between the dictionary definition and the java object" + "</b>");
225            out.println("<ol>");
226            for (String discrepancy : discrepancies) {
227                out.println("<li>" + discrepancy);
228            }
229            out.println("</ol>");
230        }
231        // field table
232        out.println("<table border=1>");
233        out.println("<tr bgcolor=lightblue>");
234        out.println("<th>");
235        out.println("Field");
236        out.println("</th>");
237        out.println("<th>");
238        out.println("Required?");
239        out.println("</th>");
240        out.println("<th>");
241        out.println("DataType");
242        out.println("</th>");
243        out.println("<th>");
244        out.println("Length");
245        out.println("</th>");
246        out.println("<th>");
247        out.println("Short Label");
248        out.println("</th>");
249        out.println("<th>");
250        out.println("Summary");
251        out.println("</th>");
252        out.println("<th>");
253        out.println("Label");
254        out.println("</th>");
255        out.println("<th>");
256        out.println("Description");
257        out.println("</th>");
258        out.println("<th>");
259        out.println("Read Only, Dynamic, or Hidden");
260        out.println("</th>");
261        out.println("<th>");
262        out.println("Default");
263        out.println("</th>");
264        out.println("<th>");
265        out.println("Repeats?");
266        out.println("</th>");
267        out.println("<th>");
268        out.println("Valid Characters");
269        out.println("</th>");
270        out.println("<th>");
271        out.println("Lookup");
272        out.println("</th>");
273        out.println("<th>");
274        out.println("Cross Field");
275        out.println("</th>");
276        out.println("<th>");
277        out.println("Default Control");
278        out.println("</th>");
279        out.println("</tr>");
280        this.writeAttributes(out, doe, new Stack<String>(), new Stack<DataObjectEntry>());
281        out.println("</table>");
282        return;
283    }
284
285    private void writeAttributes(PrintStream out, DataObjectEntry ode, Stack<String> parentNames, Stack<DataObjectEntry> parents) {
286        // stop recursion
287        if (parents.contains(ode)) {
288            return;
289        }
290//        for (AttributeDefinition ad : getSortedFields()) {
291        if (ode.getAttributes() != null) {
292            for (AttributeDefinition ad : ode.getAttributes()) {
293                out.println("<tr>");
294                out.println("<td>");
295                out.println(nbsp(calcName(ad.getName(), parentNames)));
296                out.println("</td>");
297                out.println("<td>");
298                out.println(nbsp(calcRequired(ad)));
299                out.println("</td>");
300                out.println("<td>");
301                out.println(nbsp(calcDataType(ad)));
302                out.println("</td>");
303                out.println("<td>");
304                out.println(nbsp(calcLength(ad)));
305                out.println("</td>");
306                out.println("<td>");
307                out.println(nbsp(calcShortLabel(ad)));
308                out.println("</td>");
309                out.println("<td>");
310                out.println(nbsp(calcSummary(ad)));
311                out.println("</td>");
312                out.println("<td>");
313                out.println(nbsp(calcLabel(ad)));
314                out.println("</td>");
315                out.println("<td>");
316                out.println(nbsp(calcDescription(ad)));
317                out.println("</td>");
318                out.println("<td>");
319                out.println(nbsp(calcDynamicHiddenReadOnly(ad)));
320                out.println("</td>");
321                out.println("<td>");
322                out.println(nbsp(calcDefaultValue(ad)));
323                out.println("</td>");
324                out.println("<td>");
325                out.println(nbsp(null));
326                out.println("</td>");
327                out.println("<td>");
328                out.println(nbsp(calcForceUpperValidCharsMinMax(ad)));
329                out.println("</td>");
330                out.println("<td>");
331                out.println(nbsp(calcLookup(ad)));
332                out.println("</td>");
333                out.println("<td>");
334                out.println(nbsp(calcCrossField(ad)));
335                out.println("</td>");
336                out.println("<td>");
337                out.println(nbsp(calcControl(ad)));
338                out.println("</td>");
339                out.println("</tr>");
340            }
341        }
342        if (ode.getComplexAttributes() != null) {
343            for (ComplexAttributeDefinition cad : ode.getComplexAttributes()) {
344                out.println("<tr>");
345                out.println("<td>");
346                out.println(nbsp(calcName(cad.getName(), parentNames)));
347                out.println("</td>");
348                out.println("<td>");
349                out.println(nbsp(calcRequired(cad)));
350                out.println("</td>");
351                out.println("<td>");
352                out.println("Complex");
353                out.println("</td>");
354                out.println("<td>");
355                out.println(nbsp(null));
356                out.println("</td>");
357                out.println("<td>");
358                out.println(nbsp(calcShortLabel(cad)));
359                out.println("</td>");
360                out.println("<td>");
361                out.println(nbsp(calcSummary(cad)));
362                out.println("</td>");
363                out.println("<td>");
364                out.println(nbsp(calcLabel(cad)));
365                out.println("</td>");
366                out.println("<td>");
367                out.println(nbsp(calcDescription(cad)));
368                out.println("</td>");
369                out.println("<td>");
370                out.println(nbsp(null));
371                out.println("</td>");
372                out.println("<td>");
373                out.println(nbsp(null));
374                out.println("</td>");
375                out.println("<td>");
376                out.println(nbsp(null));
377                out.println("</td>");
378                out.println("<td>");
379                out.println(nbsp(null));
380                out.println("</td>");
381                out.println("<td>");
382                out.println(nbsp(null));
383                out.println("</td>");
384                out.println("<td>");
385                out.println(nbsp(null));
386                out.println("</td>");
387                out.println("<td>");
388                out.println(nbsp(null));
389                out.println("</td>");
390                out.println("</tr>");
391                parentNames.push(cad.getName());
392                parents.push(ode);
393                this.writeAttributes(out, (DataObjectEntry) cad.getDataObjectEntry(), parentNames, parents);
394                parentNames.pop();
395                parents.pop();
396            }
397        }
398        if (ode.getCollections() != null) {
399            for (CollectionDefinition cd : ode.getCollections()) {
400                out.println("<tr>");
401                out.println("<td>");
402                out.println(nbsp(calcName(cd.getName(), parentNames)));
403                out.println("</td>");
404                out.println("<td>");
405                out.println(nbsp(calcRequired(cd)));
406                out.println("</td>");
407                out.println("<td>");
408                out.println("Complex");
409                out.println("</td>");
410                out.println("<td>");
411                out.println(nbsp(null));
412                out.println("</td>");
413                out.println("<td>");
414                out.println(nbsp(calcShortLabel(cd)));
415                out.println("</td>");
416                out.println("<td>");
417                out.println(nbsp(calcSummary(cd)));
418                out.println("</td>");
419                out.println("<td>");
420                out.println(nbsp(calcLabel(cd)));
421                out.println("</td>");
422                out.println("<td>");
423                out.println(nbsp(calcDescription(cd)));
424                out.println("</td>");
425                out.println("<td>");
426                out.println(nbsp(null));
427                out.println("</td>");
428                out.println("<td>");
429                out.println(nbsp(null));
430                out.println("</td>");
431                out.println("<td>");
432                out.println(nbsp("Repeating"));
433                out.println("</td>");
434                out.println("<td>");
435                out.println(nbsp(null));
436                out.println("</td>");
437                out.println("<td>");
438                out.println(nbsp(null));
439                out.println("</td>");
440                out.println("<td>");
441                out.println(nbsp(null));
442                out.println("</td>");
443                out.println("<td>");
444                out.println(nbsp(null));
445                out.println("</td>");
446                out.println("</tr>");
447                DataObjectEntry childDoe = this.getDataOjbectEntry(cd.getDataObjectClass());
448                if (childDoe == null) {
449                    // TODO: uncomment this but right now there are xml files that don't have one defined and it seems to work so...
450//                    throw new NullPointerException ("Could not find a data object entry, " + cd.getDataObjectClass() + " for field " + calcName(cd.getName(), parents));
451                    log.warn("Could not find a data object entry, " + cd.getDataObjectClass() + " for field " + calcName(cd.getName(), parentNames));
452                } else {
453                    parentNames.push(cd.getName());
454                    parents.push(ode);
455                    this.writeAttributes(out, (DataObjectEntry) childDoe, parentNames, parents);
456                    parentNames.pop();
457                    parents.pop();
458                }
459            }
460        }
461    }
462
463    private DataObjectEntry getDataOjbectEntry(String className) {
464        for (DataObjectEntry doe : this.beansOfType.values()) {
465            if (doe.getDataObjectClass().getName().equals(className)) {
466                return doe;
467            }
468        }
469        return null;
470    }
471
472    private String calcName(String name, Stack<String> parents) {
473        StringBuilder sb = new StringBuilder();
474        for (String parent : parents) {
475            sb.append(parent);
476            sb.append(".");
477        }
478        sb.append(name);
479        return sb.toString();
480    }
481
482    private String calcShortLabel(CollectionDefinition cd) {
483        return cd.getShortLabel();
484    }
485
486    private String calcShortLabel(AttributeDefinitionBase ad) {
487        return ad.getShortLabel();
488    }
489
490    private String calcLabel(CollectionDefinition cd) {
491        return cd.getLabel();
492    }
493
494    private String calcLabel(AttributeDefinitionBase ad) {
495        return ad.getLabel();
496    }
497
498    private String calcSummary(CollectionDefinition ad) {
499        return ad.getSummary();
500    }
501
502    private String calcSummary(AttributeDefinitionBase ad) {
503        return ad.getSummary();
504    }
505
506    private String calcDescription(CollectionDefinition cd) {
507        return cd.getDescription();
508    }
509
510    private String calcDescription(AttributeDefinitionBase ad) {
511        return ad.getDescription();
512    }
513
514    private List<AttributeDefinition> getSortedFields() {
515        List<AttributeDefinition> fields = doe.getAttributes();
516        Collections.sort(fields, new AttributeDefinitionNameComparator());
517        return fields;
518    }
519
520    private static class AttributeDefinitionNameComparator implements Comparator<AttributeDefinition> {
521
522        @Override
523        public int compare(AttributeDefinition o1, AttributeDefinition o2) {
524            return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase());
525        }
526    }
527
528    private String formatAsString(List<String> discrepancies) {
529        int i = 0;
530        StringBuilder builder = new StringBuilder();
531        for (String discrep : discrepancies) {
532            i++;
533            builder.append(i + ". " + discrep + "\n");
534        }
535        return builder.toString();
536    }
537
538    private String calcDataType(AttributeDefinition ad) {
539//        if (ad.getDataType().equals(DataType.COMPLEX)) {
540//            if (ad.getDataObjectStructure() == null) {
541//                throw new IllegalArgumentException(
542//                        ad.getName() + " is complex but does not have a sub-structure defined");
543//            }
544//            Class subClazz = this.getClass(ad.getDataObjectStructure().getName());
545//            String subStrucName = calcComplexSubStructureName(ad);
546//            // process if explicity asking for substructures OR the field is a freestanding field
547//            // so it won't be processed by just processing all of the DTO's and their sub-objects
548//            if (this.processSubstructures || subClazz == null) {
549//                if (!this.subStructuresAlreadyProcessed.contains(
550//                        ad.getDataObjectStructure())) {
551////     System.out.println ("Adding " + subStrucName + " to set to be processed");
552//                    this.subStructuresToProcess.put(subStrucName, ad.getDataObjectStructure());
553//                }
554//            }
555//            return "[" + calcNotSoSimpleName(subStrucName) + "|#" + subStrucName + "]";
556//        }
557        return ad.getDataType().toString();
558    }
559
560    private String calcDefaultValue(AttributeDefinition ad) {
561//        if (ad.getDefaultValue() != null) {
562//            return ad.getDefaultValue().toString();
563//        }
564        return " ";
565    }
566
567    private String calcDynamicHiddenReadOnly(AttributeDefinition ad) {
568        StringBuilder sb = new StringBuilder();
569        String comma = "";
570        comma = this.appendIfNotNull(sb, this.calcDynamic(ad), comma);
571        comma = this.appendIfNotNull(sb, this.calcHidden(ad), comma);
572        comma = this.appendIfNotNull(sb, this.calcReadOnly(ad), comma);
573        return sb.toString();
574    }
575
576    private String appendIfNotNull(StringBuilder sb, String value, String comma) {
577        if (value == null) {
578            return comma;
579        }
580        sb.append(comma);
581        sb.append(value);
582        return ", ";
583    }
584
585    private String calcDynamic(AttributeDefinition ad) {
586        // TODO: implement once KRAD team implements
587//        if (ad.isDynamic()) {
588//            return "dynamic";
589//        }
590        return null;
591    }
592
593    private String calcHidden(AttributeDefinition ad) {
594        if (ad.getAttributeSecurity() == null) {
595            return null;
596        }
597        if (ad.getAttributeSecurity().isHide()) {
598            return "Hidden";
599        }
600        return null;
601
602    }
603
604    private String calcReadOnly(AttributeDefinition ad) {
605        if (ad.getAttributeSecurity() == null) {
606            return null;
607        }
608        if (ad.getAttributeSecurity().isReadOnly()) {
609            return "Read only";
610        }
611        return null;
612
613    }
614
615    private String calcComplexSubStructureName(AttributeDefinition ad) {
616//        if (this.processSubstructures) {
617//            return name + "." + ad.getName() + "." + calcSimpleName(
618//                    ad.getDataObjectStructure().getName());
619//        }
620//        return calcSimpleName(ad.getDataObjectStructure().getName());
621        return " ";
622    }
623
624    private String calcSimpleName(String simpleName) {
625        if (simpleName.lastIndexOf(".") != -1) {
626            simpleName = simpleName.substring(simpleName.lastIndexOf(".") + 1);
627        }
628        return simpleName;
629    }
630
631    private String calcNotSoSimpleName(String name) {
632        if (name.lastIndexOf(".") == -1) {
633            return name;
634        }
635        String simpleName = calcSimpleName(name);
636        String fieldName = calcSimpleName(name.substring(0, name.length()
637                - simpleName.length()
638                - 1));
639        return fieldName + "." + simpleName;
640    }
641
642    private String calcRequired(CollectionDefinition cd) {
643        if (cd.getMinOccurs() != null) {
644            if (cd.getMinOccurs() >= 1) {
645                return "required";
646            }
647        }
648        // TODO: Deal with collections
649//        if (ad.getMaximumNumberOfElements() != null) {
650//            if (ad.getMaximumNumberOfElements().intValue() == 0) {
651//                return "Not allowed";
652//            }
653//        }
654//
655//        if (ad.getMinimumNumberOfElements() != null) {
656//            if (ad.getMinimumNumberOfElements().intValue() >= 1) {
657//                return "required";
658//            }
659//        }
660        return " ";
661//  return "optional";
662    }
663
664    private String calcRequired(AttributeDefinitionBase ad) {
665        if (ad.isRequired() != null) {
666            if (ad.isRequired()) {
667                return "required";
668            }
669        }
670        // TODO: Deal with collections
671//        if (ad.getMaximumNumberOfElements() != null) {
672//            if (ad.getMaximumNumberOfElements().intValue() == 0) {
673//                return "Not allowed";
674//            }
675//        }
676//
677//        if (ad.getMinimumNumberOfElements() != null) {
678//            if (ad.getMinimumNumberOfElements().intValue() >= 1) {
679//                return "required";
680//            }
681//        }
682        return " ";
683//  return "optional";
684    }
685
686    private String calcForceUpperCase(AttributeDefinition ad) {
687        if (ad.getForceUppercase() != null && ad.getForceUppercase()) {
688            return "FORCE UPPER CASE";
689        }
690        return " ";
691    }
692
693    private String calcValidChars(AttributeDefinition ad) {
694        if (ad.getValidCharactersConstraint() == null) {
695            return " ";
696        }
697        return calcValidChars(ad.getValidCharactersConstraint());
698    }
699
700    private String calcValidChars(ValidCharactersConstraint cons) {
701        String messageKey = cons.getMessageKey();
702        if (messageKey == null) {
703            messageKey = "validation.validChars";
704        }
705        String validChars = escapeXML(cons.getValue());
706        String descr = messageKey + "<br>" + validChars;
707        return descr;
708    }
709
710    private String calcLookup(AttributeDefinition ad) {
711        if (ad.getLookupDefinition() == null) {
712            return " ";
713        }
714        return calcLookup(ad.getLookupDefinition());
715    }
716
717    private String calcLookup(LookupConstraint lc) {
718        StringBuilder bldr = new StringBuilder();
719        bldr.append(lc.getId());
720//  this is the search description not the lookup description
721//  builder.append (" - ");
722//  builder.append (lc.getDesc ());
723        String and = "";
724        bldr.append("<br>");
725        bldr.append("\n");
726        bldr.append("Implemented using search: ");
727        String searchPage = calcWikiSearchPage(lc.getSearchTypeId());
728        bldr.append("[" + lc.getSearchTypeId() + "|" + searchPage + "#"
729                + lc.getSearchTypeId() + "]");
730        List<CommonLookupParam> configuredParameters = filterConfiguredParams(
731                lc.getParams());
732        if (configuredParameters.size() > 0) {
733            bldr.append("<br>");
734            bldr.append("\n");
735            bldr.append(" where ");
736            and = "";
737            for (CommonLookupParam param : configuredParameters) {
738                bldr.append(and);
739                and = " and ";
740                bldr.append(param.getName());
741                bldr.append("=");
742                if (param.getDefaultValueString() != null) {
743                    bldr.append(param.getDefaultValueString());
744                    continue;
745                }
746                if (param.getDefaultValueList() != null) {
747                    String comma = "";
748                    for (String defValue : param.getDefaultValueList()) {
749                        bldr.append(comma);
750                        comma = ", ";
751                        bldr.append(defValue);
752                    }
753                }
754            }
755        }
756        return bldr.toString();
757    }
758
759    private String calcForceUpperValidCharsMinMax(AttributeDefinition ad) {
760        StringBuilder bldr = new StringBuilder();
761        String brk = "";
762        String forceUpper = calcForceUpperCase(ad);
763        if (!forceUpper.trim().isEmpty()) {
764            bldr.append(forceUpper);
765            brk = "<BR>";
766        }
767
768        String validChars = calcValidChars(ad);
769        if (!validChars.trim().isEmpty()) {
770            bldr.append(brk);
771            brk = "<BR>";
772            bldr.append(validChars);
773        }
774
775        String minMax = calcMinMax(ad);
776        if (!minMax.trim().isEmpty()) {
777            bldr.append(brk);
778            brk = "<BR>";
779            bldr.append(minMax);
780        }
781
782        return bldr.toString();
783    }
784
785    private String calcMinMax(AttributeDefinition ad) {
786        if (ad.getExclusiveMin() == null) {
787            if (ad.getInclusiveMax() == null) {
788                return " ";
789            }
790            return "Must be <= " + ad.getInclusiveMax();
791        }
792        if (ad.getInclusiveMax() == null) {
793            return "Must be > " + ad.getExclusiveMin();
794        }
795        return "Must be > " + ad.getExclusiveMin() + " and < "
796                + ad.getInclusiveMax();
797    }
798    private static final String PAGE_PREFIX = "Formatted View of ";
799    private static final String PAGE_SUFFIX = " Searches";
800
801    private String calcWikiSearchPage(String searchType) {
802        return PAGE_PREFIX + calcWikigPageAbbrev(searchType) + PAGE_SUFFIX;
803    }
804
805    private String calcWikigPageAbbrev(String searchType) {
806        if (searchType == null) {
807            return null;
808        }
809        if (searchType.equals("enumeration.management.search")) {
810            return "EM";
811        }
812        if (searchType.startsWith("lu.")) {
813            return "LU";
814        }
815        if (searchType.startsWith("cluset.")) {
816            return "LU";
817        }
818        if (searchType.startsWith("lo.")) {
819            return "LO";
820        }
821        if (searchType.startsWith("lrc.")) {
822            return "LRC";
823        }
824        if (searchType.startsWith("comment.")) {
825            return "Comment";
826        }
827        if (searchType.startsWith("org.")) {
828            return "Organization";
829        }
830        if (searchType.startsWith("atp.")) {
831            return "ATP";
832        }
833        throw new IllegalArgumentException("Unknown type of search: " + searchType);
834    }
835
836    private List<CommonLookupParam> filterConfiguredParams(
837            List<CommonLookupParam> params) {
838        List list = new ArrayList();
839        if (params == null) {
840            return list;
841        }
842        if (params.size() == 0) {
843            return list;
844        }
845        for (CommonLookupParam param : params) {
846            if (param.getDefaultValueString() != null) {
847                list.add(param);
848                continue;
849            }
850            if (param.getDefaultValueList() != null) {
851                list.add(param);
852            }
853        }
854        return list;
855    }
856
857    private String calcLength(AttributeDefinition ad) {
858        if (ad.getMaxLength() != null) {
859            if (ad.getMinLength() != null && ad.getMinLength() != 0) {
860                if (ad.getMaxLength() == ad.getMinLength()) {
861                    return ("must be " + ad.getMaxLength());
862                }
863                return ad.getMinLength() + " to " + ad.getMaxLength();
864            }
865            return "up to " + ad.getMaxLength();
866        }
867        if (ad.getMinLength() != null) {
868            return "at least " + ad.getMinLength();
869        }
870        return " ";
871    }
872
873    private String calcControl(AttributeDefinition ad) {
874        Control control = ad.getControlField();
875        if (control == null) {
876            return " ";
877        }
878        if (control instanceof TextControl) {
879            TextControl textControl = (TextControl) control;
880            if (textControl.getDatePicker() != null) {
881                return "DateControl";
882            }
883            if (!textControl.getStyleClassesAsString().isEmpty()) {
884                if (textControl.getStyleClassesAsString().contains("amount")) {
885                    return "CurrencyControl";
886                }
887            }
888        }
889        return control.getClass().getSimpleName();
890    }
891
892    private String calcCrossField(AttributeDefinition ad) {
893        StringBuilder b = new StringBuilder();
894        String semicolon = "";
895        String cfr = calcCrossFieldRequire(ad);
896        if (cfr != null) {
897            b.append(semicolon);
898            semicolon = "; ";
899            b.append(cfr);
900        }
901        String cfw = calcCrossFieldWhen(ad);
902        if (cfw != null) {
903            b.append(semicolon);
904            semicolon = "; ";
905            b.append(cfw);
906        }
907        if (b.length() == 0) {
908            return " ";
909        }
910        return b.toString();
911    }
912
913    private String calcCrossFieldRequire(AttributeDefinitionBase ad) {
914//        if (ad.getRequireConstraint() == null) {
915//            return null;
916//        }
917//        if (ad.getRequireConstraint().size() == 0) {
918//            return null;
919//        }
920        StringBuilder b = new StringBuilder();
921//        String comma = "";
922//        b.append("if not empty then ");
923//        for (RequiredConstraint rc : ad.getRequireConstraint()) {
924//            b.append(comma);
925//            comma = ", ";
926//            b.append(rc.getPropertyName());
927//        }
928//        if (ad.getRequireConstraint().size() == 1) {
929//            b.append(" is");
930//        } else {
931//            b.append(" are");
932//        }
933//        b.append(" also required");
934        return b.toString();
935    }
936
937    private String calcCrossFieldWhen(AttributeDefinition ad) {
938        if (ad.getCaseConstraint() == null) {
939            return null;
940        }
941        StringBuilder b = new StringBuilder();
942        CaseConstraint cc = ad.getCaseConstraint();
943        for (WhenConstraint wc : cc.getWhenConstraint()) {
944            b.append("\\\\");
945            b.append("\n");
946            b.append("when ");
947            b.append(cc.getPropertyName());
948            b.append(" ");
949            if (!cc.isCaseSensitive()) {
950                b.append("ignoring case ");
951            }
952            b.append(cc.getOperator());
953            b.append(" ");
954
955            b.append("\\\\");
956            b.append("\n");
957            String comma = "";
958            for (Object value : wc.getValues()) {
959                b.append(comma);
960                comma = " or ";
961                b.append(asString(value));
962            }
963            b.append("\\\\");
964            b.append("\n");
965            b.append("then override constraint:"
966                    + calcOverride(ad, (BaseConstraint) wc.getConstraint()));
967        }
968        return b.toString();
969    }
970
971    private String calcOverride(AttributeDefinition ad, BaseConstraint cons) {
972        StringBuilder b = new StringBuilder();
973//        b.append(calcOverride("serviceSide", ad.(),
974//                cons.getApplyClientSide()));
975//        b.append(calcOverride("exclusiveMin", ad.getExclusiveMin(),
976//                cons.getExclusiveMin()));
977//        b.append(calcOverride("inclusiveMax", ad.getInclusiveMax(),
978//                cons.getInclusiveMax()));
979//        String minOccursMessage = calcOverride("minOccurs", ad.getMinimumNumberOfElements(),
980//                cons.getMinimumNumberOfElements());
981//        if (!minOccursMessage.trim().equals("")) {
982//            if (cons.getMinimumNumberOfElements() != null && cons.getMinimumNumberOfElements() == 1) {
983//                minOccursMessage = " REQUIRED";
984//            }
985//        }
986//        b.append(minOccursMessage);
987//        b.append(calcOverride("validchars", ad.getValidCharactersConstraint(),
988//                cons.getValidCharactersConstraint()));
989//        b.append(calcOverride("lookup", ad.getLookupDefinition(),
990//                cons.getLookupDefinition()));
991        //TODO: other more complex constraints
992        return b.toString();
993    }
994
995    private String calcOverride(String attribute, LookupConstraint val1,
996            LookupConstraint val2) {
997        if (val1 == val2) {
998            return "";
999        }
1000        if (val1 == null && val2 != null) {
1001            return " add lookup " + this.calcLookup(val2);
1002        }
1003        if (val1 != null && val2 == null) {
1004            return " remove lookup constraint";
1005        }
1006        return " change lookup to " + calcLookup(val2);
1007    }
1008
1009    private String calcOverride(String attribute, ValidCharactersConstraint val1,
1010            ValidCharactersConstraint val2) {
1011        if (val1 == val2) {
1012            return "";
1013        }
1014        if (val1 == null && val2 != null) {
1015            return " add validchars " + calcValidChars(val2);
1016        }
1017        if (val1 != null && val2 == null) {
1018            return " remove validchars constraint";
1019        }
1020        return " change validchars to " + calcValidChars(val2);
1021    }
1022
1023    private String calcOverride(String attribute, boolean val1, boolean val2) {
1024        if (val1 == val2) {
1025            return "";
1026        }
1027        return " " + attribute + "=" + val2;
1028    }
1029
1030    private String calcOverride(String attribute, String val1, String val2) {
1031        if (val1 == null && val2 == null) {
1032            return "";
1033        }
1034        if (val1 == val2) {
1035            return "";
1036        }
1037        if (val1 == null) {
1038            return " " + attribute + "=" + val2;
1039        }
1040        if (val1.equals(val2)) {
1041            return "";
1042        }
1043        return " " + attribute + "=" + val2;
1044    }
1045
1046    private String calcOverride(String attribute, Object val1, Object val2) {
1047        if (val1 == null && val2 == null) {
1048            return "";
1049        }
1050        if (val1 == val2) {
1051            return "";
1052        }
1053        if (val1 == null) {
1054            return " " + attribute + "=" + val2;
1055        }
1056        if (val1.equals(val2)) {
1057            return "";
1058        }
1059        return " " + attribute + "=" + asString(val2);
1060    }
1061
1062    private String asString(Object value) {
1063        if (value == null) {
1064            return "null";
1065        }
1066        if (value instanceof String) {
1067            return (String) value;
1068        }
1069        return value.toString();
1070    }
1071
1072    private String nbsp(String str) {
1073        if (str == null) {
1074            return "&nbsp;";
1075        }
1076        if (str.trim().isEmpty()) {
1077            return "&nbsp;";
1078        }
1079        return str;
1080    }
1081
1082    public static void writeTag(PrintStream out, String tag, String value) {
1083        writeTag(out, tag, null, value);
1084    }
1085
1086    public static void writeTag(PrintStream out, String tag, String modifiers, String value) {
1087        if (value == null) {
1088            return;
1089        }
1090        if (value.equals("")) {
1091            return;
1092        }
1093        out.print("<" + tag);
1094        if (modifiers != null && !modifiers.isEmpty()) {
1095            out.print(" " + modifiers);
1096        }
1097        out.print(">");
1098        out.print(escapeXML(value));
1099        out.print("</" + tag + ">");
1100        out.println("");
1101    }
1102
1103    public static String escapeXML(String s) {
1104        StringBuffer sb = new StringBuffer();
1105        int n = s.length();
1106        for (int i = 0; i < n; i++) {
1107            // http://www.hdfgroup.org/HDF5/XML/xml_escape_chars.htm
1108            char c = s.charAt(i);
1109            switch (c) {
1110                case '"':
1111                    sb.append("&quot;");
1112                    break;
1113                case '\'':
1114                    sb.append("&apos;");
1115                    break;
1116                case '&':
1117                    sb.append("&amp;");
1118                    break;
1119                case '<':
1120                    sb.append("&lt;");
1121                    break;
1122                case '>':
1123                    sb.append("&gt;");
1124                    break;
1125                //case ' ': sb.append("&nbsp;");break;\
1126                default:
1127                    sb.append(c);
1128                    break;
1129            }
1130        }
1131        return sb.toString();
1132    }
1133
1134    private void writeLink(PrintStream out, String url, String value) {
1135        out.print("<a href=\"" + url + "\">" + value + "</a>");
1136    }
1137}