DCAE-D be initial commit
[sdc/dcae-d/dt-be-main.git] / dcaedt_validator / kwalify / src / main / java / kwalify / Validator.java
1 /*
2  * @(#)Validator.java   $Rev: 3 $ $Release: 0.5.1 $
3  *
4  * copyright(c) 2005 kuwata-lab all rights reserved.
5  */
6
7 package kwalify;
8
9 import java.util.Map;
10 import java.util.HashMap;
11 import java.util.IdentityHashMap;
12 import java.util.List;
13 import java.util.LinkedList;
14 import java.util.ArrayList;
15 import java.util.Iterator;
16 import java.util.Collections;
17
18 /**
19  *  validation engine
20  *
21  *  ex.
22  *  <pre>
23  *
24  *    // load YAML document
25  *    String str = Util.readFile("document.yaml");
26  *    YamlParser parser = new YamlParser(str);
27  *    Object document = parser.parse();
28  *
29  *    // load schema
30  *    Object schema = YamlUtil.loadFile("schema.yaml");
31  *
32  *    // generate validator and validate document
33  *    Validator validator = new Validator(shema);
34  *    List errors = validator.validate(document);
35  *
36  *    // show errors
37  *    if (errors != null && errors.size() > 0) {
38  *        parser.setErrorsLineNumber(errors);
39  *        java.util.Collections.sort(errors);
40  *        for (Iterator it = errors.iterator(); it.hasNext(); ) {
41  *            ValidationError error = (ValidationError)it.next();
42  *            int linenum = error.getLineNumber();
43  *            String path = error.getPath();
44  *            String mesg = error.getMessage();
45  *            String s = "- (" + linenum + ") [" + path + "] " + mesg;
46  *            System.err.println(s);
47  *        }
48  *    }
49  *  </pre>
50  *
51  *  @version   $Rev: 3 $
52  *  @release   $Release: 0.5.1 $
53  */
54 public class Validator {
55     private Rule _rule;
56
57     public Validator(Map schema) throws SchemaException {
58         _rule = new Rule(schema);
59     }
60
61     public Validator(Object schema) throws SchemaException {
62         _rule = new Rule(schema);
63     }
64
65     public Rule getRule() { return _rule; }
66     //public void setRule(Rule rule) { _rule = rule; }
67
68     public List validate(Object value) {
69         ValidationContext vctx = new ValidationContext();
70         _validateRule(value, _rule, vctx);
71         return vctx.getErrors();
72     }
73
74     protected boolean preValidationHook(Object value, Rule rule, ValidationContext context) {
75         // nothing
76         return false;
77     }
78
79     protected void postValidationHook(Object value, Rule rule, ValidationContext context) {
80     }
81
82     private void _validateRule(Object value, Rule rule, ValidationContext context) {
83         //why is done necessary? why would one end up having to validate twice the same collection??
84         if (Types.isCollection(value)) {
85             if (context.done(value))
86               return;
87         }
88         if (rule.isRequired() && value == null) {
89             Object[] args = new Object[] { Types.typeName(rule.getType()) };
90             context.addError("required.novalue", rule, value, args);
91             return;
92         }
93
94         if (preValidationHook(value, rule, context)) {
95           /* a 'higher power says is ok */
96           postValidationHook(value, rule, context);
97           return;
98         }
99
100         //Class klass = rule.getTypeClass();
101         //if (klass != null && value != null && !klass.isInstance(value)) {
102
103         int n = context.errorCount();
104         validateRule(value, rule, context);
105         if (context.errorCount() != n) {
106             return;
107         }
108         //
109         postValidationHook(value, rule, context);
110     }
111
112     /* this is the default validation process */
113     protected void validateRule(Object value, Rule rule, ValidationContext context) {
114
115       if (value != null && ! Types.isCorrectType(value, rule.getType())) {
116           Object[] args = new Object[] { Types.typeName(rule.getType()) };
117           context.addError("type.unmatch", rule, value, args);
118           return;
119       }
120       //
121       if (rule.getSequence() != null) {
122           assert value == null || value instanceof List;
123           validateSequence((List)value, rule, context);
124       } else if (rule.getMapping() != null) {
125           assert value == null || value instanceof Map;
126           validateMapping((Map)value, rule, context);
127       } else {
128           validateScalar(value, rule, context);
129       }
130     }
131
132     private void validateScalar(Object value, Rule rule, ValidationContext context) {
133         assert rule.getSequence() == null;
134         assert rule.getMapping() == null;
135         if (rule.getAssert() != null) {
136             //boolean result = evaluate(rule.getAssert());
137             //if (! result) {
138             //    errors.add("asset.failed", rule, path, value, new Object[] { rule.getAssert() });
139             //}
140         }
141         if (rule.getEnum() != null) {
142             if (! rule.getEnum().contains(value)) {
143                 //if (Util.matches(keyname, "\\A\\d+\\z") keyname = "enum";
144                 context.addError("enum.notexist", rule, value, new Object[] { context.getPathElement() });
145             }
146         }
147         //
148         if (value == null) {
149             return;
150         }
151         //
152         if (rule.getPattern() != null) {
153             if (! Util.matches(value.toString(), rule.getPatternRegexp())) {
154                 context.addError("pattern.unmatch", rule, value, new Object[] { rule.getPattern() });
155             }
156         }
157         if (rule.getRange() != null) {
158             assert Types.isScalar(value);
159             Map range = rule.getRange();
160             Object v;
161             if ((v = range.get("max")) != null && Util.compareValues(v, value) < 0) {
162                 context.addError("range.toolarge", rule, value, new Object[] { v.toString() });
163             }
164             if ((v = range.get("min")) != null && Util.compareValues(v, value) > 0) {
165                 context.addError("range.toosmall", rule, value, new Object[] { v.toString() });
166             }
167             if ((v = range.get("max-ex")) != null && Util.compareValues(v, value) <= 0) {
168                 context.addError("range.toolargeex", rule, value, new Object[] { v.toString() });
169             }
170             if ((v = range.get("min-ex")) != null && Util.compareValues(v, value) >= 0) {
171                 context.addError("range.toosmallex", rule, value, new Object[] { v.toString() });
172             }
173         }
174         if (rule.getLength() != null) {
175             assert value instanceof String;
176             Map length = rule.getLength();
177             int len = value.toString().length();
178             Integer v;
179             if ((v = (Integer)length.get("max")) != null && v.intValue() < len) {
180                 context.addError("length.toolong", rule, value, new Object[] { new Integer(len), v });
181             }
182             if ((v = (Integer)length.get("min")) != null && v.intValue() > len) {
183                 context.addError("length.tooshort", rule, value, new Object[] { new Integer(len), v });
184             }
185             if ((v = (Integer)length.get("max-ex")) != null && v.intValue() <= len) {
186                 context.addError("length.toolongex", rule, value, new Object[] { new Integer(len), v });
187             }
188             if ((v = (Integer)length.get("min-ex")) != null && v.intValue() >= len) {
189                 context.addError("length.tooshortex", rule, value, new Object[] { new Integer(len), v });
190             }
191         }
192     }
193
194
195     private void validateSequence(List sequence, Rule seq_rule, ValidationContext context) {
196         assert seq_rule.getSequence() instanceof List;
197         assert seq_rule.getSequence().size() == 1;
198         if (sequence == null) {
199             return;
200         }
201         Rule rule = (Rule)seq_rule.getSequence().get(0);
202         int i = 0;
203         for (Iterator it = sequence.iterator(); it.hasNext(); i++) {
204             Object val = it.next();
205             context.addPathElement(String.valueOf(i));
206             _validateRule(val, rule, context);  // validate recursively
207             context.removePathElement();
208         }
209         if (rule.getType().equals("map")) {
210             Map mapping = rule.getMapping();
211             List unique_keys = new ArrayList();
212             for (Iterator it = mapping.keySet().iterator(); it.hasNext(); ) {
213                 Object key = it.next();
214                 Rule map_rule = (Rule)mapping.get(key);
215                 if (map_rule.isUnique() || map_rule.isIdent()) {
216                     unique_keys.add(key);
217                 }
218             }
219             //
220             if (unique_keys.size() > 0) {
221                 for (Iterator it = unique_keys.iterator(); it.hasNext(); ) {
222                     Object key = it.next();
223                     Map table = new HashMap();  // val => index
224                     int j = 0;
225                     for (Iterator it2 = sequence.iterator(); it2.hasNext(); j++) {
226                         Map map = (Map)it2.next();
227                         Object val = map.get(key);
228                         if (val == null) {
229                             continue;
230                         }
231                         if (table.containsKey(val)) {
232                             String path = context.getPath();
233                             String prev_path = path + "/" + table.get(val) + "/" + key;
234                             context.addPathElement(String.valueOf(j))
235                                    .addPathElement(key.toString());
236                             context.addError("value.notunique", rule, val, new Object[] { prev_path });
237                             context.removePathElement()
238                                    .removePathElement();
239                         } else {
240                             table.put(val, new Integer(j));
241                         }
242                     }
243                 }
244             }
245         } else if (rule.isUnique()) {
246             Map table = new HashMap();  // val => index
247             int j = 0;
248             for (Iterator it = sequence.iterator(); it.hasNext(); j++) {
249                 Object val = it.next();
250                 if (val == null) {
251                     continue;
252                 }
253                 if (table.containsKey(val)) {
254                     String path = context.getPath();
255                     String prev_path = path + "/" + table.get(val);
256                     context.addPathElement(String.valueOf(j))
257                            .addError("value.notunique", rule, val, new Object[] { prev_path })
258                            .removePathElement();
259                 } else {
260                     table.put(val, new Integer(j));
261                 }
262             }
263         }
264     }
265
266
267     private void validateMapping(Map mapping, Rule map_rule, ValidationContext context) {
268         assert map_rule.getMapping() instanceof Map;
269         if (mapping == null) {
270             return;
271         }
272         Map m = map_rule.getMapping();
273         for (Iterator it = m.keySet().iterator(); it.hasNext(); ) {
274             Object key = it.next();
275             Rule rule = (Rule)m.get(key);
276             if (rule.isRequired() && !mapping.containsKey(key)) {
277                 context.addError("required.nokey", rule, mapping, new Object[] { key });
278             }
279         }
280         for (Iterator it = mapping.keySet().iterator(); it.hasNext(); ) {
281             Object key = it.next();
282             Object val = mapping.get(key);
283             Rule rule = (Rule)m.get(key);
284             context.addPathElement(key.toString());
285             if (rule == null) {
286                 context.addError("key.undefined", rule, mapping, new Object[] { key.toString() + ":", map_rule.getName() + m.keySet().toString() });
287             } else {
288                 _validateRule(val, rule, context);  // validate recursively
289             }
290             context.removePathElement();
291         }
292     }
293
294
295     public class ValidationContext {
296
297        private StringBuilder  path = new StringBuilder("");
298        private List           errors = new LinkedList();
299        private Map            done = new IdentityHashMap(); //completion tracker
300
301        private ValidationContext() {
302        }
303
304        public String getPath() {
305          return this.path.toString();
306        }
307
308        public Validator getValidator() {
309          return Validator.this;
310        }
311
312        public ValidationContext addPathElement(String theElement) {
313          this.path.append("/")
314                   .append(theElement);
315          return this;
316        }
317
318        public String getPathElement() {
319          int index = this.path.lastIndexOf("/");
320          return index >= 0 ? this.path.substring(index + 1) : this.path.toString();
321        }
322
323        public ValidationContext removePathElement() {
324          int index = this.path.lastIndexOf("/");
325          if (index >= 0)
326            this.path.delete(index, this.path.length());
327          return this;
328        }
329
330        protected ValidationContext addError(String error_symbol, Rule rule, Object value, Object[] args) {
331          addError(
332            new ValidationException(
333              Messages.buildMessage(error_symbol, value, args), getPath(), value, rule));
334          return this;
335        }
336
337        protected ValidationContext addError(String error_symbol, Rule rule, String relpath, Object value, Object[] args) {
338          addError(
339            new ValidationException(
340              Messages.buildMessage(error_symbol, value, args), getPath()+"/"+relpath, value, rule));
341          return this;
342        }
343
344        public ValidationContext addError(String message, Rule rule, Object value, Throwable cause) {
345          addError(
346            new ValidationException(
347              message + ((cause == null) ? "" : ", cause " + cause), getPath(), value, rule));
348          return this;
349        }
350
351        public ValidationContext addError(ValidationException theError) {
352          this.errors.add(theError);
353          return this;
354        }
355
356
357        public List getErrors() {
358          return Collections.unmodifiableList(this.errors);
359        }
360
361        public boolean hasErrors() {
362          return this.errors.isEmpty();
363        }
364
365        public int errorCount() {
366          return this.errors.size();
367        }
368
369        private boolean done(Object theTarget) {
370           if (this.done.get(theTarget) != null) {
371                 return true;
372           }
373           this.done.put(theTarget, Boolean.TRUE);
374           return false;
375        }
376
377        private boolean isDone(Object theTarget) {
378           return this.done.get(theTarget) != null;
379        } 
380     }
381
382 }