View Javadoc

1   /**
2   The contents of this file are subject to the Mozilla Public License Version 1.1 
3   (the "License"); you may not use this file except in compliance with the License. 
4   You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
5   Software distributed under the License is distributed on an "AS IS" basis, 
6   WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
7   specific language governing rights and limitations under the License. 
8   
9   The Original Code is "BuilderSupport.java".  Description: 
10  "Abstract base class for Validation Rules" 
11  
12  The Initial Developer of the Original Code is University Health Network. Copyright (C) 
13  2012.  All Rights Reserved. 
14  
15  Contributor(s): ______________________________________. 
16  
17  Alternatively, the contents of this file may be used under the terms of the 
18  GNU General Public License (the "GPL"), in which case the provisions of the GPL are 
19  applicable instead of those above.  If you wish to allow use of your version of this 
20  file only under the terms of the GPL and not to allow others to use your version 
21  of this file under the MPL, indicate your decision by deleting  the provisions above 
22  and replace  them with the notice and other provisions required by the GPL License.  
23  If you do not delete the provisions above, a recipient may use your version of 
24  this file under either the MPL or the GPL. 
25   */
26  package ca.uhn.hl7v2.validation.builder;
27  
28  import java.io.Serializable;
29  import java.util.Arrays;
30  import java.util.Collection;
31  import java.util.regex.Pattern;
32  
33  import ca.uhn.hl7v2.validation.ValidationException;
34  
35  /** 
36   * Abstract base class for Validation Rule building that provides factory methods for
37   * {@link Predicate}s.
38   * 
39   * @author Christian Ohr
40   */
41  @SuppressWarnings("serial")
42  public abstract class BuilderSupport implements Serializable {
43  
44  	protected BuilderSupport() {	
45  	}
46  
47  	/**
48  	 * @param expected expected value
49  	 * @return a predicate that evaluates to <code>true</code> if the expected value equals the
50  	 *         actual value
51  	 */
52  	public Predicate isEqual(Object expected) {
53  		return new EqualsPredicate(expected);
54  	}
55  
56  	/**
57  	 * @param expected expected value
58  	 * @return a predicate that evaluates to <code>true</code> if the expected value
59  	 *         case-insensitively equals the actual value
60  	 */
61  	public Predicate isEqualIgnoreCase(Object expected) {
62  		return new EqualsPredicate(expected, true);
63  	}
64  
65  	/**
66  	 * @return a predicate that evaluates to <code>true</code> if the actual value is null, has zero
67  	 *         length or is explicitly "empty" as HL7 defines it ("").
68  	 */
69  	public Predicate empty() {
70  		return new EmptyPredicate();
71  	}
72  
73  	/**
74  	 * @param predicate the predicate to evaluate if not empty
75  	 * @return a predicate that evaluates to <code>true</code> if the actual value is empty or the
76  	 *         passed predicate evaluates to true.
77  	 */
78  	public Predicate emptyOr(Predicate predicate) {
79  		return anyOf(empty(), predicate);
80  	}
81  
82  	/**
83  	 * @param regex regular expression
84  	 * @return a predicate that evaluates to <code>true</code> if the actual value matches the
85  	 *         regular expression
86  	 */
87  	public Predicate matches(String regex) {
88  		return new MatchesPredicate(regex);
89  	}
90  
91      /**
92       * @param regex regular expression
93       * @param description custom descriptiom for this regex
94       * @return a predicate that evaluates to <code>true</code> if the actual value matches the
95       *         regular expression
96       */
97      public Predicate matches(String regex, String description) {
98          return new MatchesPredicate(regex, description);
99      }	
100 
101 	/**
102 	 * @param prefix prefix string
103 	 * @return a predicate that evaluates to <code>true</code> if the actual value starts with the
104 	 *         specified prefix.
105 	 */
106 	public Predicate startsWith(String prefix) {
107 		return matches("^" + prefix + ".*", "starts with " + prefix);
108 	}
109 
110 	/**
111 	 * @return a predicate that evaluates to <code>true</code> if the actual value can be parsed
112 	 *         into a non-negative integer.
113 	 */
114 	public Predicate nonNegativeInteger() {
115 		return matches("\\d*", "a non-negative integer (0,1,2,...)");
116 	}
117 
118 	/**
119 	 * @return a predicate that evaluates to <code>true</code> if the actual value can be parsed
120 	 *         into a number with optional decimal digits.
121 	 */
122 	public Predicate number() {
123 		return matches("(\\+|\\-)?\\d*\\.?\\d*", "a number with optional decimal digits");
124 	}
125 
126 	/**
127 	 * @return a predicate that evaluates to <code>true</code> if the actual value matches a HL7
128 	 *         date pattern (YYYY[MM[DD]])
129 	 */
130 	public Predicate date() {
131 		return matches("(\\d{4}([01]\\d(\\d{2})?)?)?", "a date string (YYYY[MM[DD]])");
132 	}
133 
134 	/**
135 	 * @return a predicate that evaluates to <code>true</code> if the actual value matches a HL7
136 	 *         time pattern
137 	 */
138 	public Predicate time() {
139 		return matches("([012]\\d([0-5]\\d([0-5]\\d(\\.\\d(\\d(\\d(\\d)?)?)?)?)?)?)?([\\+\\-]\\d{4})?",
140 		        "a HL7 time string");
141 	}
142 
143 	/**
144 	 * @return a predicate that evaluates to <code>true</code> if the actual value matches a HL7
145 	 *         datetime pattern
146 	 */
147 	public Predicate dateTime() {
148 		return matches("(\\d{4}([01]\\d(\\d{2}([012]\\d[0-5]\\d([0-5]\\d(\\.\\d(\\d(\\d(\\d)?)?)?)?)?)?)?)?)?([\\+\\-]\\d{4})?",
149 		        "a HL7 datetime string");
150 	}
151 
152 	/**
153 	 * @return a predicate that evaluates to <code>true</code> if the actual value matches a HL7
154 	 *         datetime pattern
155 	 */
156 	public Predicate dateTime25() {
157 		return matches("(\\d{4}([01]\\d(\\d{2}([012]\\d([0-5]\\d([0-5]\\d(\\.\\d(\\d(\\d(\\d)?)?)?)?)?)?)?)?)?)?([\\+\\-]\\d{4})?",
158 		        "a HL7 datetime string");
159 	}
160 
161 	/**
162 	 * @return a predicate that evaluates to <code>true</code> if the actual value matches a US
163 	 *         phone number pattern
164 	 */
165 	public Predicate usPhoneNumber() {
166 		return matches("(\\d{1,2} )?(\\(\\d{3}\\))?\\d{3}-\\d{4}(X\\d{1,5})?(B\\d{1,5})?(C.*)?",
167 		        "a US phone number");
168 	}
169 
170 	/**
171 	 * @return a predicate that evaluates to <code>true</code> if the actual value matches an ISO
172 	 *         OID pattern
173 	 */
174 	public Predicate oid() {
175 		return matches("[0-2](\\.(0|([1-9][0-9]*)))+",
176 		        "an Object Identifier (OID)");
177 	}
178 
179 	/**
180 	 * @return a predicate that evaluates to <code>true</code> if the actual value matches a UUID
181 	 *         pattern
182 	 */
183 	public Predicate uuid() {
184 		return matches("\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}",
185 		        "a Unique Universal Identifier (UUID)");
186 	}
187 
188 	/**
189 	 * @param regex regular expression
190 	 * @param flags regular expression flags
191 	 * @return a predicate that evaluates to <code>true</code> if the actual value matches the
192 	 *         regular expression
193 	 */
194 	public Predicate matches(String regex, int flags) {
195 		return new MatchesPredicate(regex, flags);
196 	}
197 
198 	/**
199 	 * Equivalent with allOf(isEqual(allowed[0]), ..., isEqual(allowed[n-1])
200 	 * 
201 	 * @param allowed allowed values
202 	 * @return a predicate that evaluates to <code>true</code> if the actual value occurs in he
203 	 *         specified array of objects
204 	 */
205 	public Predicate in(Object... allowed) {
206 		return new InPredicate(Arrays.asList(allowed));
207 	}
208 
209 	/**
210 	 * @param allowed allowed values
211 	 * @return a predicate that evaluates to <code>true</code> if the actual value occurs in he
212 	 *         specified collection of objects
213 	 */
214 	public Predicate in(Collection<?> allowed) {
215 		return new InPredicate(allowed);
216 	}
217 
218 	/**
219 	 * @param predicates predicates of which one shall evaluate to true
220 	 * @return a predicate that evaluates to <code>true</code> if any of the specified predicates
221 	 *         evaluates to <code>true</code>
222 	 */
223 	public Predicate anyOf(Iterable<Predicate> predicates) {
224 		return new AnyOfPredicate(predicates);
225 	}
226 
227 	/**
228 	 * @param predicates predicates of which all shall evaluate to true
229 	 * @return a predicate that evaluates to <code>true</code> if all of the specified predicates
230 	 *         evaluate to <code>true</code>
231 	 */
232 	public Predicate allOf(Iterable<Predicate> predicates) {
233 		return new AllOfPredicate(predicates);
234 	}
235 
236 	/**
237 	 * @param predicates predicates of which one shall evaluate to true
238 	 * @return a predicate that evaluates to <code>true</code> if any of the specified predicates
239 	 *         evaluates to <code>true</code>
240 	 */
241 	public Predicate anyOf(Predicate... predicates) {
242 		return anyOf(Arrays.asList(predicates));
243 	}
244 
245 	/**
246 	 * @param predicates predicates of which all shall evaluate to true
247 	 * @return a predicate that evaluates to <code>true</code> if all of the specified predicates
248 	 *         evaluate to <code>true</code>
249 	 */
250 	public Predicate allOf(Predicate... predicates) {
251 		return allOf(Arrays.asList(predicates));
252 	}
253 
254 	/**
255 	 * @param predicate predicate to be negated
256 	 * @return a predicate that evaluates to <code>true</code> if the specified predicate evaluate
257 	 *         to <code>false</code>
258 	 */
259 	public Predicate not(Predicate predicate) {
260 		return new NotPredicate(predicate);
261 	}
262 
263 	/**
264 	 * @param maxSize maximal length of the value
265 	 * @return a predicate that evaluates to <code>true</code> if the length of the actual value is
266 	 *         equal or shorter than the specified length
267 	 */
268 	public Predicate maxLength(int maxSize) {
269 		return new MaxLengthPredicate(maxSize);
270 	}
271 
272 	/**
273 	 * @return a predicate that evaluates to <code>false</code> giving the reason that the message
274 	 *         element has been withdrawn and should not be used anymore.
275 	 */
276 	public Predicate withdrawn() {
277 		return new WithdrawnPredicate();
278 	}
279 
280 	/**
281 	 * @return a predicate that evaluates to the specified boolean value
282 	 */
283 	public Predicate always(boolean b) {
284 		return new AlwaysPredicate(b);
285 	}
286 
287 	/**
288 	 * @return a predicate that evaluates to <code>false</code>
289 	 */
290 	public Predicate alwaysFails() {
291 		return always(false);
292 	}
293 
294 	static private String join(Iterable<?> list, String conjunction) {
295 		StringBuilder sb = new StringBuilder();
296 		boolean first = true;
297 		for (Object item : list) {
298 			if (first)
299 				first = false;
300 			else
301 				sb.append(conjunction);
302 			sb.append(item);
303 		}
304 		return sb.toString();
305 	}
306 
307 	private class AlwaysPredicate implements Predicate {
308 
309 		private boolean b;
310 
311 		AlwaysPredicate(boolean b) {
312 			this.b = b;
313 		}
314 
315 		public boolean evaluate(Object data) throws ValidationException {
316 			return b;
317 		}
318 
319 		public String getDescription() {
320 			return b ? "anything" : "nothing";
321 		}
322 
323 	}
324 
325 	private class MaxLengthPredicate implements Predicate {
326 
327 		private int maxLength = Integer.MAX_VALUE;
328 
329 		public MaxLengthPredicate(int maxSize) {
330 			this.maxLength = maxSize;
331 		}
332 
333 		public boolean evaluate(Object data) throws ValidationException {
334 			return (data == null || data.toString().length() <= maxLength);
335 		}
336 
337 		public String getDescription() {
338 			return "shorter than " + maxLength + " characters";
339 		}
340 
341 	}
342 
343 	private class InPredicate implements Predicate {
344 
345 		private Collection<?> allowed;
346 
347 		InPredicate(Collection<?> allowed) {
348 			this.allowed = allowed;
349 		}
350 
351 		public boolean evaluate(Object data) throws ValidationException {
352 			return allowed.contains(data);
353 		}
354 
355 		public String getDescription() {
356 			return "in [" + join(allowed, ",") + "]";
357 		}
358 
359 	}
360 
361 	private class WithdrawnPredicate extends MaxLengthPredicate {
362 
363 		public WithdrawnPredicate() {
364 			super(0);
365 		}
366 
367 		@Override
368 		public String getDescription() {
369 			return "empty because it is withdrawn from the current HL7 version and should not be used";
370 		}
371 
372 	}
373 
374 	private class NotPredicate implements Predicate {
375 
376 		private Predicate delegate;
377 
378 		public NotPredicate(Predicate delegate) {
379 			this.delegate = delegate;
380 		}
381 
382 		public boolean evaluate(Object data) throws ValidationException {
383 			try {
384 				return !delegate.evaluate(data);
385 			} catch (ValidationException e) {
386 				return true;
387 			}
388 		}
389 
390 		public String getDescription() {
391 			return "not " + delegate.getDescription();
392 		}
393 
394 	}
395 
396 	private class EqualsPredicate implements Predicate {
397 
398 		private Object expected;
399 		private boolean ignoresCase;
400 
401 		public EqualsPredicate(Object expected) {
402 			this(expected, false);
403 		}
404 
405 		EqualsPredicate(Object expected, boolean ignoresCase) {
406 			super();
407 			this.expected = expected;
408 			this.ignoresCase = ignoresCase;
409 		}
410 
411 		public boolean evaluate(Object data) throws ValidationException {
412 			if (ignoresCase)
413 				return (data == null && expected == null)
414 						|| (data != null && data.toString().equalsIgnoreCase(expected.toString()));
415 			return (data == null && expected == null) || (data != null && data.equals(expected));
416 		}
417 
418 		public String getDescription() {
419 			return "equal to " + String.valueOf(expected);
420 		}
421 
422 	}
423 
424 	private class EmptyPredicate implements Predicate {
425 
426 		public EmptyPredicate() {
427 		}
428 
429 		public boolean evaluate(Object data) throws ValidationException {
430 			return data == null || "".equals(data) || "\"\"".equals(data);
431 		}
432 
433 		public String getDescription() {
434 			return "empty";
435 		}
436 
437 	}
438 
439 	private class MatchesPredicate implements Predicate {
440 
441 		private Pattern p;
442 		private String description;
443 
444 		public MatchesPredicate(String regex) {
445 			this(regex, "matching with " + regex);
446 		}
447 		
448         public MatchesPredicate(String regex, String description) {
449             p = Pattern.compile(regex);
450             this.description = description;
451         }		
452 
453 		public MatchesPredicate(String regex, int flags) {
454 			this(regex, flags, "matching with " + regex);
455 		}
456 		
457         public MatchesPredicate(String regex, int flags, String description) {
458             p = Pattern.compile(regex);
459             this.description = description;
460         }		
461 
462 		public boolean evaluate(Object data) throws ValidationException {
463 			return (data != null && p.matcher(data.toString()).matches());
464 		}
465 
466 		public String getDescription() {
467 			return description;
468 		}
469 
470 	}
471 
472 	private class AnyOfPredicate implements Predicate {
473 
474 		private Iterable<Predicate> predicates;
475 
476 		public AnyOfPredicate(Iterable<Predicate> predicates) {
477 			super();
478 			this.predicates = predicates;
479 		}
480 
481 		public boolean evaluate(Object data) throws ValidationException {
482 			for (Predicate p : predicates) {
483 				if (p.evaluate(data)) {
484 					return true;
485 				}
486 			}
487 			return false;
488 		}
489 
490 		public String getDescription() {
491 			String or = " or ";
492 			StringBuilder b = new StringBuilder();
493 			for (Predicate p : predicates) {
494 				b.append(p.getDescription()).append(or);
495 			}
496 			return b.substring(0, b.length() - or.length());
497 		}
498 
499 	}
500 
501 	private class AllOfPredicate implements Predicate {
502 
503 		private Iterable<Predicate> predicates;
504 
505 		public AllOfPredicate(Iterable<Predicate> predicates) {
506 			super();
507 			this.predicates = predicates;
508 		}
509 
510 		public boolean evaluate(Object data) throws ValidationException {
511 			for (Predicate p : predicates) {
512 				if (!p.evaluate(data)) {
513 					return false;
514 				}
515 			}
516 			return true;
517 		}
518 
519 		public String getDescription() {
520 			String and = " and ";
521             StringBuilder b = new StringBuilder();
522 			for (Predicate p : predicates) {
523 				b.append(p.getDescription()).append(and);
524 			}
525 			return b.substring(0, b.length() - and.length());
526 		}
527 
528 	}
529 
530 }