001/** 002The contents of this file are subject to the Mozilla Public License Version 1.1 003(the "License"); you may not use this file except in compliance with the License. 004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005Software distributed under the License is distributed on an "AS IS" basis, 006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007specific language governing rights and limitations under the License. 008 009The Original Code is "BuilderSupport.java". Description: 010"Abstract base class for Validation Rules" 011 012The Initial Developer of the Original Code is University Health Network. Copyright (C) 0132012. All Rights Reserved. 014 015Contributor(s): ______________________________________. 016 017Alternatively, the contents of this file may be used under the terms of the 018GNU General Public License (the "GPL"), in which case the provisions of the GPL are 019applicable instead of those above. If you wish to allow use of your version of this 020file only under the terms of the GPL and not to allow others to use your version 021of this file under the MPL, indicate your decision by deleting the provisions above 022and replace them with the notice and other provisions required by the GPL License. 023If you do not delete the provisions above, a recipient may use your version of 024this file under either the MPL or the GPL. 025 */ 026package ca.uhn.hl7v2.validation.builder; 027 028import java.io.Serializable; 029import java.util.Arrays; 030import java.util.Collection; 031import java.util.regex.Pattern; 032 033import ca.uhn.hl7v2.validation.ValidationException; 034 035/** 036 * Abstract base class for Validation Rule building that provides factory methods for 037 * {@link Predicate}s. 038 * 039 * @author Christian Ohr 040 */ 041@SuppressWarnings("serial") 042public abstract class BuilderSupport implements Serializable { 043 044 protected BuilderSupport() { 045 } 046 047 /** 048 * @param expected expected value 049 * @return a predicate that evaluates to <code>true</code> if the expected value equals the 050 * actual value 051 */ 052 public Predicate isEqual(Object expected) { 053 return new EqualsPredicate(expected); 054 } 055 056 /** 057 * @param expected expected value 058 * @return a predicate that evaluates to <code>true</code> if the expected value 059 * case-insensitively equals the actual value 060 */ 061 public Predicate isEqualIgnoreCase(Object expected) { 062 return new EqualsPredicate(expected, true); 063 } 064 065 /** 066 * @return a predicate that evaluates to <code>true</code> if the actual value is null, has zero 067 * length or is explicitly "empty" as HL7 defines it (""). 068 */ 069 public Predicate empty() { 070 return new EmptyPredicate(); 071 } 072 073 /** 074 * @param predicate the predicate to evaluate if not empty 075 * @return a predicate that evaluates to <code>true</code> if the actual value is empty or the 076 * passed predicate evaluates to true. 077 */ 078 public Predicate emptyOr(Predicate predicate) { 079 return anyOf(empty(), predicate); 080 } 081 082 /** 083 * @param regex regular expression 084 * @return a predicate that evaluates to <code>true</code> if the actual value matches the 085 * regular expression 086 */ 087 public Predicate matches(String regex) { 088 return new MatchesPredicate(regex); 089 } 090 091 /** 092 * @param regex regular expression 093 * @param description custom descriptiom for this regex 094 * @return a predicate that evaluates to <code>true</code> if the actual value matches the 095 * regular expression 096 */ 097 public Predicate matches(String regex, String description) { 098 return new MatchesPredicate(regex, description); 099 } 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 static 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 static 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 static 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 static 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 static 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 static 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 static 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 static 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 static 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 static 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}