2 * @(#)Validator.java $Rev: 3 $ $Release: 0.5.1 $
4 * copyright(c) 2005 kuwata-lab all rights reserved.
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;
24 * // load YAML document
25 * String str = Util.readFile("document.yaml");
26 * YamlParser parser = new YamlParser(str);
27 * Object document = parser.parse();
30 * Object schema = YamlUtil.loadFile("schema.yaml");
32 * // generate validator and validate document
33 * Validator validator = new Validator(shema);
34 * List errors = validator.validate(document);
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);
52 * @release $Release: 0.5.1 $
54 public class Validator {
57 public Validator(Map schema) throws SchemaException {
58 _rule = new Rule(schema);
61 public Validator(Object schema) throws SchemaException {
62 _rule = new Rule(schema);
65 public Rule getRule() { return _rule; }
66 //public void setRule(Rule rule) { _rule = rule; }
68 public List validate(Object value) {
69 ValidationContext vctx = new ValidationContext();
70 _validateRule(value, _rule, vctx);
71 return vctx.getErrors();
74 protected boolean preValidationHook(Object value, Rule rule, ValidationContext context) {
79 protected void postValidationHook(Object value, Rule rule, ValidationContext context) {
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))
88 if (rule.isRequired() && value == null) {
89 Object[] args = new Object[] { Types.typeName(rule.getType()) };
90 context.addError("required.novalue", rule, value, args);
94 if (preValidationHook(value, rule, context)) {
95 /* a 'higher power says is ok */
96 postValidationHook(value, rule, context);
100 //Class klass = rule.getTypeClass();
101 //if (klass != null && value != null && !klass.isInstance(value)) {
103 int n = context.errorCount();
104 validateRule(value, rule, context);
105 if (context.errorCount() != n) {
109 postValidationHook(value, rule, context);
112 /* this is the default validation process */
113 protected void validateRule(Object value, Rule rule, ValidationContext context) {
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);
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);
128 validateScalar(value, rule, context);
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());
138 // errors.add("asset.failed", rule, path, value, new Object[] { rule.getAssert() });
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() });
152 if (rule.getPattern() != null) {
153 if (! Util.matches(value.toString(), rule.getPatternRegexp())) {
154 context.addError("pattern.unmatch", rule, value, new Object[] { rule.getPattern() });
157 if (rule.getRange() != null) {
158 assert Types.isScalar(value);
159 Map range = rule.getRange();
161 if ((v = range.get("max")) != null && Util.compareValues(v, value) < 0) {
162 context.addError("range.toolarge", rule, value, new Object[] { v.toString() });
164 if ((v = range.get("min")) != null && Util.compareValues(v, value) > 0) {
165 context.addError("range.toosmall", rule, value, new Object[] { v.toString() });
167 if ((v = range.get("max-ex")) != null && Util.compareValues(v, value) <= 0) {
168 context.addError("range.toolargeex", rule, value, new Object[] { v.toString() });
170 if ((v = range.get("min-ex")) != null && Util.compareValues(v, value) >= 0) {
171 context.addError("range.toosmallex", rule, value, new Object[] { v.toString() });
174 if (rule.getLength() != null) {
175 assert value instanceof String;
176 Map length = rule.getLength();
177 int len = value.toString().length();
179 if ((v = (Integer)length.get("max")) != null && v.intValue() < len) {
180 context.addError("length.toolong", rule, value, new Object[] { new Integer(len), v });
182 if ((v = (Integer)length.get("min")) != null && v.intValue() > len) {
183 context.addError("length.tooshort", rule, value, new Object[] { new Integer(len), v });
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 });
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 });
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) {
201 Rule rule = (Rule)seq_rule.getSequence().get(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();
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);
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
225 for (Iterator it2 = sequence.iterator(); it2.hasNext(); j++) {
226 Map map = (Map)it2.next();
227 Object val = map.get(key);
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();
240 table.put(val, new Integer(j));
245 } else if (rule.isUnique()) {
246 Map table = new HashMap(); // val => index
248 for (Iterator it = sequence.iterator(); it.hasNext(); j++) {
249 Object val = it.next();
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();
260 table.put(val, new Integer(j));
267 private void validateMapping(Map mapping, Rule map_rule, ValidationContext context) {
268 assert map_rule.getMapping() instanceof Map;
269 if (mapping == null) {
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 });
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());
286 context.addError("key.undefined", rule, mapping, new Object[] { key.toString() + ":", map_rule.getName() + m.keySet().toString() });
288 _validateRule(val, rule, context); // validate recursively
290 context.removePathElement();
295 public class ValidationContext {
297 private StringBuilder path = new StringBuilder("");
298 private List errors = new LinkedList();
299 private Map done = new IdentityHashMap(); //completion tracker
301 private ValidationContext() {
304 public String getPath() {
305 return this.path.toString();
308 public Validator getValidator() {
309 return Validator.this;
312 public ValidationContext addPathElement(String theElement) {
313 this.path.append("/")
318 public String getPathElement() {
319 int index = this.path.lastIndexOf("/");
320 return index >= 0 ? this.path.substring(index + 1) : this.path.toString();
323 public ValidationContext removePathElement() {
324 int index = this.path.lastIndexOf("/");
326 this.path.delete(index, this.path.length());
330 protected ValidationContext addError(String error_symbol, Rule rule, Object value, Object[] args) {
332 new ValidationException(
333 Messages.buildMessage(error_symbol, value, args), getPath(), value, rule));
337 protected ValidationContext addError(String error_symbol, Rule rule, String relpath, Object value, Object[] args) {
339 new ValidationException(
340 Messages.buildMessage(error_symbol, value, args), getPath()+"/"+relpath, value, rule));
344 public ValidationContext addError(String message, Rule rule, Object value, Throwable cause) {
346 new ValidationException(
347 message + ((cause == null) ? "" : ", cause " + cause), getPath(), value, rule));
351 public ValidationContext addError(ValidationException theError) {
352 this.errors.add(theError);
357 public List getErrors() {
358 return Collections.unmodifiableList(this.errors);
361 public boolean hasErrors() {
362 return this.errors.isEmpty();
365 public int errorCount() {
366 return this.errors.size();
369 private boolean done(Object theTarget) {
370 if (this.done.get(theTarget) != null) {
373 this.done.put(theTarget, Boolean.TRUE);
377 private boolean isDone(Object theTarget) {
378 return this.done.get(theTarget) != null;