DCAE-D be initial commit
[sdc/dcae-d/dt-be-main.git] / dcaedt_validator / kwalify / src / main / java / kwalify / Rule.java
1 /*
2  * copyright(c) 2005 kuwata-lab all rights reserved.
3  */
4
5 package kwalify;
6
7 import java.io.Serializable;
8 import java.util.List;
9 import java.util.ArrayList;
10 import java.util.Map;
11 import java.util.HashMap;
12 import java.util.IdentityHashMap;
13 import java.util.Iterator;
14 import java.util.regex.Pattern;
15 import java.util.regex.Matcher;
16 import java.util.regex.PatternSyntaxException;
17
18 import org.onap.sdc.common.onaplog.Enums.LogLevel;
19 import org.onap.sdc.common.onaplog.OnapLoggerDebug;
20
21 /**
22  *  rule for validation.
23  *  Validator class generates rule instances.
24  *
25  */
26 public class Rule implements Serializable{
27     private static final String RANGE1 = "/range";
28     private static final String RANGE2 = "range:";
29     private static final String ENUM_CONFLICT = "enum.conflict";
30     private static final String MAP_CONFLICT = "map.conflict";
31     private static final String LENGTH1 = "/length";
32     private static final String LENGTH2 = "length:";
33     private static final String LENGTH3 = "/length/";
34     private static final String SEQ_CONFLICT = "seq.conflict";
35     private static final String PATTERN1 = "pattern:";
36     private static final String MAPPING1 = "mapping:";
37     private static final String SEQUENCE1 = "/sequence";
38     private static final String MAX_EX = "max-ex";
39     private static final String MIN_EX = "min-ex";
40     private static final String TYPE1 = "/type";
41     private static final String TYPE_NOTSTR = "type.notstr";
42     private static final String TYPE_UNKNOWN = "type.unknown";
43     private static final String IDENT1 = "ident:";
44     private static final String UNIQUE1 = "unique:";
45     private static final String MAPPING2 = "/mapping";
46     private static final String MAPPING3 = "/mapping/=";
47     private static final String MAPPING4 = "/mapping/";
48     private static final String SEQUENCE2 = "sequence:";
49     private static final String SCALAR_CONFLICT = "scalar.conflict";
50     private static final String UNIQUE_NOTBOOL = "unique.notbool";
51     private static final String UNIQUE_NOTSCALAR = "unique.notscalar";
52     private static final String UNIQUE_ONROOT = "unique.onroot";
53     private static final String UNIQUE2 = "/unique";
54     private static final String IDENT_ONROOT = "ident.onroot";
55     private static final String IDENT_NOTSCALAR = "ident.notscalar";
56     private static final String IDENT_NOTMAP = "ident.notmap";
57     private static final String MAP = "map";
58     private static final String EMPTY_STRING = "";
59     private static final String SLASH = "/";
60     private static final String SCHEMA_NOTMAP = "schema.notmap";
61     private static final String SCHEMA_NOTMAP1 = "schema.notmap: {}";
62     private static final String PATTERN2 = "/pattern";
63     private static final String PATTERN_NOTSTR = "pattern.notstr";
64     private static final String PATTERN_NOTMATCH = "pattern.notmatch";
65     private static final String REQUIRED_NOTBOOL = "required.notbool";
66     private static final String REQUIRED1 = "/required";
67     private static final String PATTERN_SYNTAXERR = "pattern.syntaxerr";
68     private static final String PATTERN_SYNTAX_EXCEPTION = "PatternSyntaxException: {}";
69     private static final String SEQUENCE_NOTSEQ = "sequence.notseq";
70     private static final String SEQUENCE_NOELEM = "sequence.noelem";
71     private static final String SEQUENCE_TOOMANY = "sequence.toomany";
72     private static final String SEQUENCE3 = "/sequence/";
73     private static final String MAPPING_NOTMAP = "mapping.notmap";
74     private static final String MAPPING_NOELEM = "mapping.noelem";
75     private static final String IDENT2 = "/ident";
76     private static final String IDENT_NOTBOOL = "ident.notbool";
77     private static final String LENGTH_MAXEXLEMINEX = "length.maxexleminex";
78     private static final String LENGTH_MAXEXLEMIN = "length.maxexlemin";
79     private static final String TWO_SPACES = "  ";
80     private static final String NAME1 = "name:     ";
81     private static final String DESC1 = "desc:     ";
82     private static final String TYPE2 = "type:     ";
83     private static final String REQUIRED2 = "required: ";
84     private static final String PATTERN3 = "pattern:  ";
85     private static final String REGEXP = "regexp:   ";
86     private static final String ASSERT1 = "assert:   ";
87     private static final String IDENT3 = "ident:    ";
88     private static final String UNIQUE3 = "unique:   ";
89     private static final String ENUM2 = "enum:\n";
90     private static final String RANGE3 = "range:     { ";
91     private static final String NAME = "name";
92     private static final String DESC = "desc";
93     private static final String SHORT = "short";
94     private static final String REQUIRED = "required";
95     private static final String TYPE = "type";
96     private static final String PATTERN = "pattern";
97     private static final String SEQUENCE = "sequence";
98     private static final String MAPPING = "mapping";
99     private static final String ASSERT = "assert";
100     private static final String RANGE = "range";
101     private static final String LENGTH = "length";
102     private static final String IDENT = "ident";
103     private static final String UNIQUE = "unique";
104     private static final String ENUM = "enum:";
105     private static final String ENUM1 = "/enum";
106     public static final String MAX = "max";
107     public static final String MIN = "min";
108
109     private static OnapLoggerDebug debugLogger = OnapLoggerDebug.getInstance();
110
111     private Rule parent;
112     private String name = null;
113     private String desc = null;
114     private String  _short = null; //added by jora: only used for map types
115     private boolean required = false;
116     private String _type = null;
117     private Class typeClass = null;
118     private String pattern = null;
119     private Pattern patternRegexp = null;
120     private List enumList = null;
121     private List sequence = null;
122     private DefaultableHashMap _mapping = null;
123     private String _assert = null;
124     private Map<String,Object> range = null;
125     private Map<String,Integer> length = null;
126     private boolean ident = false;
127     private boolean unique = false;
128
129     private static final int CODE_NAME     = NAME.hashCode();
130     private static final int CODE_DESC     = DESC.hashCode();
131     private static final int CODE_SHORT    = SHORT.hashCode();
132     private static final int CODE_REQUIRED = REQUIRED.hashCode();
133     private static final int CODE_TYPE     = TYPE.hashCode();
134     private static final int CODE_PATTERN  = PATTERN.hashCode();
135     private static final int CODE_LENGTH   = LENGTH.hashCode();
136     private static final int CODE_RANGE    = RANGE.hashCode();
137     private static final int CODE_ASSERT   = ASSERT.hashCode();
138     private static final int CODE_IDENT    = IDENT.hashCode();
139     private static final int CODE_UNIQUE   = UNIQUE.hashCode();
140     private static final int CODE_ENUM     = ENUM.hashCode();
141     private static final int CODE_MAPPING  = MAPPING.hashCode();
142     private static final int CODE_SEQUENCE = SEQUENCE.hashCode();
143
144     public Rule(Object schema, Rule parent) {
145         if (schema != null) {
146             if (! (schema instanceof Map)) {
147                 throw schemaError(SCHEMA_NOTMAP, null, SLASH, null, null);
148             }
149             Map ruleTable = new IdentityHashMap();
150             init((Map)schema, EMPTY_STRING, ruleTable);
151         }
152         this.parent = parent;
153     }
154
155     public Rule(Object schema) {
156         this(schema, null);
157     }
158
159     public Rule(Map schema, Rule parent) {
160         if (schema != null) {
161             Map ruleTable = new IdentityHashMap();
162             init(schema, EMPTY_STRING, ruleTable);
163         }
164         this.parent = parent;
165     }
166
167     public Rule(Map schema) {
168         this(schema, null);
169     }
170
171     public Rule() {
172         this(null, null);
173     }
174
175     public String getName() { return name; }
176     public void setName(String name) { this.name = name; }
177
178     public String getShort() { return _short; }
179     public void setShort(String key) { _short = key; }
180
181     public boolean isRequired() { return required; }
182     public void setRequired(boolean required) { this.required = required; }
183
184     public String getType() { return _type; }
185     public void setType(String type) { this._type = type; }
186
187     public String getPattern() { return pattern; }
188     public void setPattern(String pattern) { this.pattern = pattern; }
189
190     public Pattern getPatternRegexp() { return patternRegexp; }
191
192     public List getEnum() { return enumList; }
193     public void setEnum(List enumList) { this.enumList = enumList; }
194
195     public List getSequence() { return sequence; }
196     public void setSequence(List sequence) { this.sequence = sequence; }
197
198     public DefaultableHashMap getMapping() { return _mapping; }
199     public void setMapping(DefaultableHashMap mapping) { _mapping = mapping; }
200
201     public String getAssert() { return _assert; }
202     public void setAssert(String assertString) { _assert = assertString; }
203
204     public Map getRange() { return range; }
205     public void setRange(Map range) { this.range = range; }
206
207     public Map getLength() { return length; }
208     public void setLength(Map length) { this.length = length; }
209
210     public boolean isIdent() { return ident; }
211
212     public boolean isUnique() { return unique; }
213     public void setUnique(boolean unique) { this.unique = unique; }
214
215     private static SchemaException schemaError(String errorSymbol, Rule rule, String path, Object value, Object[] args) {
216         String msg = Messages.buildMessage(errorSymbol, value, args);
217         return new SchemaException(msg, path, value, rule);
218     }
219
220     private void init(Object elem, String path, Map ruleTable) {
221         assert elem != null;
222         if (! (elem instanceof Map)) {
223             if (path == null || path.isEmpty()) {
224                 path = SLASH;
225             }
226             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), SCHEMA_NOTMAP1, elem);
227             throw schemaError(SCHEMA_NOTMAP, null, path, null, null);
228         }
229         init((Map)elem, path, ruleTable);
230     }
231
232     private void init(Map hash, String path, Map ruleTable) {
233         Rule rule = this;
234         ruleTable.put(hash, rule);
235
236         // 'type:' entry
237         Object type = hash.get(TYPE);
238         initTypeValue(type, rule, path);
239
240         // other entries
241         for (Iterator it = hash.keySet().iterator(); it.hasNext(); ) {
242             Object key = it.next();
243             Object value = hash.get(key);
244             int code = key.hashCode();
245
246             if (code == CODE_TYPE && key.equals(TYPE)) {
247                 // done
248             } else if (code == CODE_NAME && key.equals(NAME)) {
249                 initNameValue(value);
250             } else if (code == CODE_DESC && key.equals(DESC)) {
251                 initDescValue(value);
252             } else if (code == CODE_SHORT && key.equals(SHORT)) {
253                 initShortValue(value, rule, path);
254             } else if (code == CODE_REQUIRED && key.equals(REQUIRED)) {
255                 initRequiredValue(value, rule, path);
256             } else if (code == CODE_PATTERN && key.equals(PATTERN)) {
257                 initPatternValue(value, rule, path);
258             } else if (code == CODE_ENUM && key.equals(ENUM)) {
259                 initEnumValue(value, rule, path);
260             } else if (code == CODE_ASSERT && key.equals(ASSERT)) {
261                 initAssertValue(value, rule, path);
262             } else if (code == CODE_RANGE && key.equals(RANGE)) {
263                 initRangeValue(value, rule, path);
264             } else if (code == CODE_LENGTH && key.equals(LENGTH)) {
265                 initLengthValue(value, rule, path);
266             } else if (code == CODE_IDENT && key.equals(IDENT)) {
267                 initIdentValue(value, rule, path);
268             } else if (code == CODE_UNIQUE && key.equals(UNIQUE)) {
269                 initUniqueValue(value, rule, path);
270             } else if (code == CODE_SEQUENCE && key.equals(SEQUENCE)) {
271                 rule = initSequenceValue(value, rule, path, ruleTable);
272             } else if (code == CODE_MAPPING && key.equals(MAPPING)) {
273                 rule = initMappingValue(value, rule, path, ruleTable);
274             }
275         }
276
277         checkConfliction(hash, rule, path);
278     }
279
280     private void initTypeValue(Object value, Rule rule, String path) {
281         if (value == null) {
282             value = Types.getDefaultType();
283         }
284         if (! (value instanceof String)) {
285             throw schemaError(TYPE_NOTSTR, rule, path + TYPE1, _type, null);
286         }
287         _type = (String)value;
288         typeClass = Types.typeClass(_type);
289         if (! Types.isBuiltinType(_type)) {
290             throw schemaError(TYPE_UNKNOWN, rule, path + TYPE1, _type, null);
291         }
292     }
293
294
295     private void initNameValue(Object value) {
296         name = value.toString();
297     }
298
299
300     private void initDescValue(Object value) {
301         desc = value.toString();
302     }
303
304     private void initShortValue(Object value, Rule rule, String path) {
305
306         //the short form specification is to be interpreted as key if the type is a map or as an
307         //index if the target is a sequence (as index 0 actually)  
308         if (!Types.isCollectionType(_type)) {
309             throw schemaError("range.notcollection", rule, path + "/short", value, null);
310         }
311         //we should also verify that it points to a declared key of the mapping .. not really, as it would
312                 //fail the overall grammar
313         _short = value.toString();
314     }
315
316     private void initRequiredValue(Object value, Rule rule, String path) {
317         if (! (value instanceof Boolean)) {
318             throw schemaError(REQUIRED_NOTBOOL, rule, path + REQUIRED1, value, null);
319         }
320         required = (Boolean) value;
321     }
322
323
324     private void initPatternValue(Object value, Rule rule, String path) {
325         if (! (value instanceof String)) {
326             throw schemaError(PATTERN_NOTSTR, rule, path + PATTERN2, value, null);
327         }
328         pattern = (String)value;
329         Matcher m = Util.matcher(pattern, "\\A/(.*)/([mi]?[mi]?)\\z");
330         if (! m.find()) {
331             throw schemaError(PATTERN_NOTMATCH, rule, path + PATTERN2, value, null);
332         }
333         String pat = m.group(1);
334         String opt = m.group(2);
335         int flag = 0;
336         if (opt.indexOf('i') >= 0) {
337             flag += Pattern.CASE_INSENSITIVE;
338         }
339         if (opt.indexOf('m') >= 0) {
340             flag += Pattern.DOTALL;   // not MULTILINE
341         }
342         try {
343             patternRegexp = Pattern.compile(pat, flag);
344         } catch (PatternSyntaxException ex) {
345             debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), PATTERN_SYNTAX_EXCEPTION, ex);
346             throw schemaError(PATTERN_SYNTAXERR, rule, path + PATTERN2, value, null);
347         }
348     }
349
350
351     private void initEnumValue(Object value, Rule rule, String path) {
352         if (! (value instanceof List)) {
353             throw schemaError("enum.notseq", rule, path + ENUM1, value, null);
354         }
355         enumList = (List)value;
356         if (Types.isCollectionType(_type)) {
357             throw schemaError("enum.notscalar", rule, path, ENUM, null);
358         }
359         Map elemTable = new HashMap();
360         for (Iterator it = enumList.iterator(); it.hasNext(); ) {
361             Object elem = it.next();
362             if (! Util.isInstanceOf(elem, typeClass)) {
363                 throw schemaError("enum.type.unmatch", rule, path + ENUM1, elem, new Object[] { Types.typeName(_type) });
364             }
365             if (elemTable.containsKey(elem)) {
366                 throw schemaError("enum.duplicate", rule, path + ENUM1, elem, null);
367             }
368             elemTable.put(elem, Boolean.TRUE);
369         }
370     }
371
372
373     private void initAssertValue(Object value, Rule rule, String path) {
374         if (! (value instanceof String)) {
375             throw schemaError("assert.notstr", rule, path + "/assert", value, null);
376         }
377         _assert = (String)value;
378         if (! Util.matches(_assert, "\\bval\\b")) {
379             throw schemaError("assert.noval", rule, path + "/assert", value, null);
380         }
381     }
382
383
384     private void initRangeValue(Object value, Rule rule, String path) {
385         if (! (value instanceof Map)) {
386             throw schemaError("range.notmap", rule, path + RANGE1, value, null);
387         }
388         if (Types.isCollectionType(_type) || "bool".equals(_type)) {
389             throw schemaError("range.notscalar", rule, path, RANGE2, null);
390         }
391         range = (Map)value;
392         for (Iterator it = range.keySet().iterator(); it.hasNext(); ) {
393             Object rkey = it.next();
394             Object rval = range.get(rkey);
395             if (MAX.equals(rkey) || MIN.equals(rkey) || rkey.equals(MAX_EX) || rkey.equals(MIN_EX)) {
396                 if (! Util.isInstanceOf(rval, typeClass)) {
397                     String typename = Types.typeName(_type);
398                     throw schemaError("range.type.unmatch", rule, path + "/range/" + rkey, rval, new Object[] { typename });
399                 }
400             } else {
401                 throw schemaError("range.undefined", rule, path + "/range/" + rkey, rkey.toString() + ":", null);
402             }
403         }
404         if (range.containsKey(MAX) && range.containsKey(MAX_EX)) {
405             throw schemaError("range.twomax", rule, path + RANGE1, null, null);
406         }
407         if (range.containsKey(MIN) && range.containsKey(MIN_EX)) {
408             throw schemaError("range.twomin", rule, path + RANGE1, null, null);
409         }
410         //
411         Object max    = range.get(MAX);
412         Object min    = range.get(MIN);
413         Object maxEx = range.get(MAX_EX);
414         Object minEx = range.get(MIN_EX);
415         Object[] args;
416
417         if (max != null) {
418             if (min != null && Util.compareValues(max, min) < 0) {
419                 args = new Object[] { max, min };
420                 throw schemaError("range.maxltmin", rule, path + RANGE1, null, args);
421             } else if (minEx != null && Util.compareValues(max, minEx) <= 0) {
422                 args = new Object[] { max, minEx };
423                 throw schemaError("range.maxleminex", rule, path + RANGE1, null, args);
424             }
425         } else if (maxEx != null) {
426             if (min != null && Util.compareValues(maxEx, min) <= 0) {
427                 args = new Object[] { maxEx, min };
428                 throw schemaError("range.maxexlemin", rule, path + RANGE1, null, args);
429             } else if (minEx != null && Util.compareValues(maxEx, minEx) <= 0) {
430                 args = new Object[] { maxEx, minEx };
431                 throw schemaError("range.maxexleminex", rule, path + RANGE1, null, args);
432             }
433         }
434     }
435
436
437     private void initLengthValue(Object value, Rule rule, String path) {
438         if (! (value instanceof Map)) {
439             throw schemaError("length.notmap", rule, path + LENGTH1, value, null);
440         }
441         length = (Map)value;
442         if (! ("str".equals(_type) || "text".equals(_type))) {
443             throw schemaError("length.nottext", rule, path, LENGTH2, null);
444         }
445         for (String k : length.keySet()) {
446             Integer v = length.get(k);
447             if (MAX.equals(k) || MIN.equals(k) || k.equals(MAX_EX) || k.equals(MIN_EX)) {
448                 if (v != null) {
449                     throw schemaError("length.notint", rule, path + LENGTH3 + k, v, null);
450                 }
451             } else {
452                 throw schemaError("length.undefined", rule, path + LENGTH3 + k, k + ":", null);
453             }
454         }
455         if (length.containsKey(MAX) && length.containsKey(MAX_EX)) {
456             throw schemaError("length.twomax", rule, path + LENGTH1, null, null);
457         }
458         if (length.containsKey(MIN) && length.containsKey(MIN_EX)) {
459             throw schemaError("length.twomin", rule, path + LENGTH1, null, null);
460         }
461        
462         Integer max    =  length.get(MAX);
463         Integer min    =  length.get(MIN);
464         Integer maxEx = length.get(MAX_EX);
465         Integer minEx = length.get(MIN_EX);
466         Object[] args;
467
468         if (max != null) {
469             if (min != null && max.compareTo(min) < 0) {
470                 args = new Object[] { max, min };
471                 throw schemaError("length.maxltmin", rule, path + LENGTH1, null, args);
472             } else if (minEx != null && max.compareTo(minEx) <= 0) {
473                 args = new Object[] { max, minEx };
474                 throw schemaError("length.maxleminex", rule, path + LENGTH1, null, args);
475             }
476         } else if (maxEx != null) {
477             if (min != null && maxEx.compareTo(min) <= 0) {
478                 args = new Object[] { maxEx, min };
479                 throw schemaError(LENGTH_MAXEXLEMIN, rule, path + LENGTH1, null, args);
480             } else if (minEx != null && maxEx.compareTo(minEx) <= 0) {
481                 args = new Object[] { maxEx, minEx };
482                 throw schemaError(LENGTH_MAXEXLEMINEX, rule, path + LENGTH1, null, args);
483             }
484         }
485     }
486
487     private void initIdentValue(Object value, Rule rule, String path) {
488         if (value == null || ! (value instanceof Boolean)) {
489             throw schemaError(IDENT_NOTBOOL, rule, path + IDENT2, value, null);
490         }
491         ident = (Boolean) value;
492         required = true;
493         if (Types.isCollectionType(_type)) {
494             throw schemaError(IDENT_NOTSCALAR, rule, path, IDENT1, null);
495         }
496         if (EMPTY_STRING.equals(path)) {
497             throw schemaError(IDENT_ONROOT, rule, SLASH, IDENT1, null);
498         }
499         if (parent == null || ! MAP.equals(parent.getType())) {
500             throw schemaError(IDENT_NOTMAP, rule, path, IDENT1, null);
501         }
502     }
503
504
505     private void initUniqueValue(Object value, Rule rule, String path) {
506         if (! (value instanceof Boolean)) {
507             throw schemaError(UNIQUE_NOTBOOL, rule, path + UNIQUE2, value, null);
508         }
509         unique = (Boolean) value;
510         if (Types.isCollectionType(_type)) {
511             throw schemaError(UNIQUE_NOTSCALAR, rule, path, UNIQUE1, null);
512         }
513         if (path.equals(EMPTY_STRING)) {
514             throw schemaError(UNIQUE_ONROOT, rule, SLASH, UNIQUE1, null);
515         }
516     }
517
518
519     private Rule initSequenceValue(Object value, Rule rule, String path, Map ruleTable) {
520         if (value != null && ! (value instanceof List)) {
521             throw schemaError(SEQUENCE_NOTSEQ, rule, path + SEQUENCE1, value.toString(), null);
522         }
523         sequence = (List)value;
524         if (sequence == null || sequence.isEmpty()) {
525             throw schemaError(SEQUENCE_NOELEM, rule, path + SEQUENCE1, value, null);
526         }
527         if (sequence.size() > 1) {
528             throw schemaError(SEQUENCE_TOOMANY, rule, path + SEQUENCE1, value, null);
529         }
530         Object elem = sequence.get(0);
531         if (elem == null) {
532             elem = new HashMap();
533         }
534         int i = 0;
535         rule = (Rule)ruleTable.get(elem);
536         if (rule == null) {
537             rule = new Rule(null, this);
538             rule.init(elem, path + SEQUENCE3 + i, ruleTable);
539         }
540         sequence = new ArrayList();
541         sequence.add(rule);
542         return rule;
543     }
544
545
546     private Rule initMappingValue(Object value, Rule rule, String path, Map ruleTable) {
547         // error check
548         if (value != null && !(value instanceof Map)) {
549             throw schemaError(MAPPING_NOTMAP, rule, path + MAPPING2, value.toString(), null);
550         }
551         Object defaultValue = null;
552         if (value instanceof Defaultable) {
553             defaultValue = ((Defaultable)value).getDefault();
554         }
555         if (value == null || ((Map)value).size() == 0 && defaultValue == null) {
556             throw schemaError(MAPPING_NOELEM, rule, path + MAPPING2, value, null);
557         }
558         // create hash of rule
559         _mapping = new DefaultableHashMap();
560         if (defaultValue != null) {
561             rule = (Rule)ruleTable.get(defaultValue);
562             if (rule == null) {
563                 rule = new Rule(null, this);
564                 rule.init(defaultValue, path + MAPPING3, ruleTable);
565             }
566             _mapping.setDefault(rule);
567         }
568         // put rules into _mapping
569         Map map = (Map)value;
570         for (Iterator it = map.keySet().iterator(); it.hasNext(); ) {
571             Object k  = it.next();
572             Object v = map.get(k);  // DefaultableHashMap
573             if (v == null) {
574                 v = new DefaultableHashMap();
575             }
576             rule = (Rule)ruleTable.get(v);
577             if (rule == null) {
578                 rule = new Rule(null, this);
579                 rule.init(v, path + MAPPING4 + k, ruleTable);
580             }
581             if ("=".equals(k)) {
582                 _mapping.setDefault(rule);
583             } else {
584                 _mapping.put(k, rule);
585             }
586         }
587         return rule;
588     }
589
590
591     private void checkConfliction(Map hash, Rule rule, String path) {
592         if ("seq".equals(_type)) {
593             if (! hash.containsKey(SEQUENCE)) {
594                 throw schemaError("seq.nosequence", rule, path, null, null);
595             }
596             if (enumList != null) {
597                 throw schemaError(SEQ_CONFLICT, rule, path, ENUM,    null);
598             }
599             if (pattern != null) {
600                 throw schemaError(SEQ_CONFLICT, rule, path, PATTERN1, null);
601             }
602             if (_mapping != null) {
603                 throw schemaError(SEQ_CONFLICT, rule, path, MAPPING1, null);
604             }
605             if (range != null) {
606                 throw schemaError(SEQ_CONFLICT, rule, path, RANGE2,   null);
607             }
608             if (length != null) {
609                 throw schemaError(SEQ_CONFLICT, rule, path, LENGTH2,  null);
610             }
611         } else if (_type.equals(MAP)) {
612             if (! hash.containsKey(MAPPING)) {
613                 throw schemaError("map.nomapping", rule, path, null, null);
614             }
615             if (enumList != null) {
616                 throw schemaError(MAP_CONFLICT, rule, path, ENUM,     null);
617             }
618             if (pattern != null) {
619                 throw schemaError(MAP_CONFLICT, rule, path, PATTERN1,  null);
620             }
621             if (sequence != null) {
622                 throw schemaError(MAP_CONFLICT, rule, path, SEQUENCE2, null);
623             }
624             if (range != null) {
625                 throw schemaError(MAP_CONFLICT, rule, path, RANGE2,    null);
626             }
627             if (length != null) {
628                 throw schemaError(MAP_CONFLICT, rule, path, LENGTH2,   null);
629             }
630         } else {
631             if (sequence != null) {
632                 throw schemaError(SCALAR_CONFLICT, rule, path, SEQUENCE2, null);
633             }
634             if (_mapping  != null) {
635                 throw schemaError(SCALAR_CONFLICT, rule, path, MAPPING1,  null);
636             }
637             if (enumList != null) {
638                 if (range != null) {
639                     throw schemaError(ENUM_CONFLICT, rule, path, RANGE2,   null);
640                 }
641                 if (length != null) {
642                     throw schemaError(ENUM_CONFLICT, rule, path, LENGTH2,  null);
643                 }
644                 if (pattern != null) {
645                     throw schemaError(ENUM_CONFLICT, rule, path, PATTERN1, null);
646                 }
647             }
648         }
649     }
650
651     public String inspect() {
652         StringBuilder sb = new StringBuilder();
653         int level = 0;
654         Map done = new IdentityHashMap();
655         inspect(sb, level, done);
656         return sb.toString();
657     }
658
659     private void inspect(StringBuilder sb, int level, Map done) {
660         done.put(this, Boolean.TRUE);
661         String indent = Util.repeatString(TWO_SPACES, level);
662         if (name != null) {
663             sb.append(indent).append(NAME1).append(name).append("\n");
664         }
665         if (desc != null) {
666             sb.append(indent).append(DESC1).append(desc).append("\n");
667         }
668         if (_type != null) {
669             sb.append(indent).append(TYPE2).append(_type).append("\n");
670         }
671         if (required) {
672             sb.append(indent).append(REQUIRED2).append(required).append("\n");
673         }
674         if (pattern != null) {
675             sb.append(indent).append(PATTERN3).append(pattern).append("\n");
676         }
677         if (patternRegexp != null) {
678             sb.append(indent).append(REGEXP).append(patternRegexp).append("\n");
679         }
680         if (_assert != null) {
681             sb.append(indent).append(ASSERT1).append(_assert).append("\n");
682         }
683         if (ident) {
684             sb.append(indent).append(IDENT3).append(ident).append("\n");
685         }
686         if (unique) {
687             sb.append(indent).append(UNIQUE3).append(unique).append("\n");
688         }
689         if (enumList != null) {
690             appendEnums(sb, indent);
691         }
692         if (range != null) {
693             appendRange(sb, indent);
694         }
695         if (sequence != null) {
696             appendSequence(sb, level, done, indent);
697         }
698         if (_mapping != null) {
699             appendMapping(sb, level, done, indent);
700         }
701     }
702
703     private void appendEnums(StringBuilder sb, String indent) {
704         sb.append(indent).append(ENUM2);
705         for (Object anEnumList : enumList) {
706             sb.append(indent).append("  - ").append(anEnumList.toString()).append("\n");
707         }
708     }
709
710     private void appendMapping(StringBuilder sb, int level, Map done, String indent) {
711         for (Object o : _mapping.entrySet()) {
712             Map.Entry entry = (Map.Entry) o;
713             Object key = entry.getKey();
714             Rule rule = (Rule) entry.getValue();
715             sb.append(indent).append("  ").append(Util.inspect(key));
716             if (done.containsKey(rule)) {
717                 sb.append(": ...\n");
718             } else {
719                 sb.append(":\n");
720                 rule.inspect(sb, level + 2, done);
721             }
722         }
723     }
724
725     private void appendSequence(StringBuilder sb, int level, Map done, String indent) {
726         for (Object aSequence : sequence) {
727             Rule rule = (Rule) aSequence;
728             if (done.containsKey(rule)) {
729                 sb.append(indent).append("  ").append("- ...\n");
730             } else {
731                 sb.append(indent).append("  ").append("- \n");
732                 rule.inspect(sb, level + 2, done);
733             }
734         }
735     }
736
737     private void appendRange(StringBuilder sb, String indent) {
738         sb.append(indent).append(RANGE3);
739         String[] keys = new String[] {MAX, MAX_EX, MIN, MIN_EX, };
740         String colon = EMPTY_STRING;
741         for (String key : keys) {
742             Object val = range.get(key);
743             if (val != null) {
744                 sb.append(colon).append(key).append(": ").append(val);
745                 colon = ", ";
746             }
747         }
748         sb.append(" }\n");
749     }
750 }