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}