001package org.hl7.fhir.dstu3.validation;
002
003/*
004Copyright (c) 2011+, HL7, Inc
005All rights reserved.
006
007Redistribution and use in source and binary forms, with or without modification, 
008are permitted provided that the following conditions are met:
009
010 * Redistributions of source code must retain the above copyright notice, this 
011   list of conditions and the following disclaimer.
012 * Redistributions in binary form must reproduce the above copyright notice, 
013   this list of conditions and the following disclaimer in the documentation 
014   and/or other materials provided with the distribution.
015 * Neither the name of HL7 nor the names of its contributors may be used to 
016   endorse or promote products derived from this software without specific 
017   prior written permission.
018
019THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028POSSIBILITY OF SUCH DAMAGE.
029
030*/
031
032import java.text.MessageFormat;
033import java.util.List;
034
035import org.apache.commons.lang3.StringUtils;
036import org.hl7.fhir.utilities.validation.ValidationMessage;
037import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
038import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
039import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
040
041public class BaseValidator {
042
043  protected Source source;
044  
045  /**
046   * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
047   * 
048   * @param thePass
049   *          Set this parameter to <code>false</code> if the validation does not pass
050   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
051   */
052  protected boolean fail(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) {
053    if (!thePass) {
054      errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.FATAL));
055    }
056    return thePass;
057  }
058
059  /**
060   * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
061   * 
062   * @param thePass
063   *          Set this parameter to <code>false</code> if the validation does not pass
064   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
065   */
066  protected boolean fail(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String msg) {
067    if (!thePass) {
068      String path = toPath(pathParts);
069      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.FATAL));
070    }
071    return thePass;
072  }
073  
074  /**
075   * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
076   * 
077   * @param thePass
078   *          Set this parameter to <code>false</code> if the validation does not pass
079   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
080   */
081  protected boolean fail(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
082    if (!thePass) {
083      String path = toPath(pathParts);
084      errors.add(new ValidationMessage(source, type, -1, -1, path, formatMessage(theMessage, theMessageArguments), IssueSeverity.FATAL));
085    }
086    return thePass;
087  }
088
089  /**
090   * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
091   * 
092   * @param thePass
093   *          Set this parameter to <code>false</code> if the validation does not pass
094   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
095   */
096  protected boolean fail(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
097    if (!thePass) {
098      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.FATAL));
099    }
100    return thePass;
101  }
102
103    
104  private String formatMessage(String theMessage, Object... theMessageArguments) {
105    String message;
106    if (theMessageArguments != null && theMessageArguments.length > 0) {
107      message = MessageFormat.format(theMessage, theMessageArguments);
108    } else { 
109      message = theMessage;
110    }
111    return message;
112  }
113
114  protected boolean grammarWord(String w) {
115    return w.equals("and") || w.equals("or") || w.equals("a") || w.equals("the") || w.equals("for") || w.equals("this") || w.equals("that") || w.equals("of");
116  }
117
118  /**
119   * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
120   * 
121   * @param thePass
122   *          Set this parameter to <code>false</code> if the validation does not pass
123   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
124   */
125  protected boolean hint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) {
126    if (!thePass) {
127      errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.INFORMATION));
128    }
129    return thePass;
130  }
131  
132  /**
133   * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
134   * 
135   * @param thePass
136   *          Set this parameter to <code>false</code> if the validation does not pass
137   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
138   */
139  protected boolean hint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
140    if (!thePass) {
141      String message = formatMessage(theMessage, theMessageArguments);
142      errors.add(new ValidationMessage(source, type, line, col, path, message, IssueSeverity.INFORMATION));
143    }
144    return thePass;
145  }
146
147  /**
148   * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
149   * 
150   * @param thePass
151   *          Set this parameter to <code>false</code> if the validation does not pass
152   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
153   */
154  protected boolean hint(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
155    if (!thePass) {
156      String path = toPath(pathParts);
157      String message = formatMessage(theMessage, theMessageArguments);
158      errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.INFORMATION));
159    }
160    return thePass;
161  }
162
163  /**
164   * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
165   * 
166   * @param thePass
167   *          Set this parameter to <code>false</code> if the validation does not pass
168   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
169   */
170  protected boolean hint(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
171    if (!thePass) {
172      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.INFORMATION));
173    }
174    return thePass;
175  }
176
177  /**
178   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
179   * 
180   * @param thePass
181   *          Set this parameter to <code>false</code> if the validation does not pass
182   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
183   */
184  protected boolean rule(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
185    if (!thePass) {
186      String message = formatMessage(theMessage, theMessageArguments);
187      errors.add(new ValidationMessage(source, type, line, col, path, message, IssueSeverity.ERROR));
188    }
189    return thePass;
190  }
191
192  /**
193   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
194   * 
195   * @param thePass
196   *          Set this parameter to <code>false</code> if the validation does not pass
197   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
198   */
199  protected boolean rule(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String msg) {
200    if (!thePass) {
201      String path = toPath(pathParts);
202      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.ERROR));
203    }
204    return thePass;
205  }
206
207  /**
208   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
209   * 
210   * @param thePass
211   *          Set this parameter to <code>false</code> if the validation does not pass
212   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
213   */
214  protected boolean rule(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
215    if (!thePass) {
216      String path = toPath(pathParts);
217      String message = formatMessage(theMessage, theMessageArguments);
218      errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.ERROR));
219    }
220    return thePass;
221  }
222  
223  /**
224   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
225   * 
226   * @param thePass
227   *          Set this parameter to <code>false</code> if the validation does not pass
228   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
229   */
230  protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
231    if (!thePass) {
232      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.ERROR));
233    }
234    return thePass;
235  }
236
237  static public boolean rule(List<ValidationMessage> errors, Source source, IssueType type, String path, boolean thePass, String msg) {
238    if (!thePass) {
239      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.ERROR));
240    }
241    return thePass;
242  }
243
244  /**
245   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
246   * 
247   * @param thePass
248   *          Set this parameter to <code>false</code> if the validation does not pass
249   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
250   */
251  protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html) {
252    if (!thePass) {
253      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.ERROR));
254    }
255    return thePass;
256  }
257
258  protected String splitByCamelCase(String s) {
259    StringBuilder b = new StringBuilder();
260    for (int i = 0; i < s.length(); i++) {
261      char c = s.charAt(i);
262      if (Character.isUpperCase(c) && !(i == 0 || Character.isUpperCase(s.charAt(i-1))))
263        b.append(' ');
264      b.append(c);
265    }
266    return b.toString();
267  }
268
269  protected String stripPunctuation(String s, boolean numbers) {
270    StringBuilder b = new StringBuilder();
271    for (char c : s.toCharArray()) {
272      int t = Character.getType(c);
273      if (t == Character.UPPERCASE_LETTER || t == Character.LOWERCASE_LETTER || t == Character.TITLECASE_LETTER || t == Character.MODIFIER_LETTER || t == Character.OTHER_LETTER || (t == Character.DECIMAL_DIGIT_NUMBER && numbers) || (t == Character.LETTER_NUMBER && numbers) || c == ' ')
274        b.append(c);
275    }
276    return b.toString();
277  }
278
279  private String toPath(List<String> pathParts) {
280    if (pathParts == null || pathParts.isEmpty()) {
281      return "";
282    }
283    return "//" + StringUtils.join(pathParts, '/');
284  }
285
286  /**
287   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
288   * 
289   * @param thePass
290   *          Set this parameter to <code>false</code> if the validation does not pass
291   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
292   */
293  protected boolean warning(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
294    if (!thePass) {
295      msg = formatMessage(msg, theMessageArguments);
296      errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.WARNING));
297    }
298    return thePass;
299
300  }
301
302  protected boolean warningOrError(boolean isError, List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
303    if (!thePass) {
304      msg = formatMessage(msg, theMessageArguments);
305      errors.add(new ValidationMessage(source, type, line, col, path, msg, isError ? IssueSeverity.ERROR : IssueSeverity.WARNING));
306    }
307    return thePass;
308
309  }
310
311  /**
312   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
313   * 
314   * @param thePass
315   *          Set this parameter to <code>false</code> if the validation does not pass
316   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
317   */
318  protected boolean warning(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
319    if (!thePass) {
320      String path = toPath(pathParts);
321      String message = formatMessage(theMessage, theMessageArguments);
322      errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.WARNING));
323    }
324    return thePass;
325  }
326
327  /**
328   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
329   * 
330   * @param thePass
331   *          Set this parameter to <code>false</code> if the validation does not pass
332   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
333   */
334  protected boolean warning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
335    if (!thePass) {
336      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.WARNING));
337    }
338    return thePass;
339  }
340
341  /**
342   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
343   * 
344   * @param thePass
345   *          Set this parameter to <code>false</code> if the validation does not pass
346   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
347   */
348  protected boolean warning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html) {
349    if (!thePass) {
350      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.WARNING));
351    }
352    return thePass;
353  }
354
355  /**
356   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
357   * 
358   * @param thePass
359   *          Set this parameter to <code>false</code> if the validation does not pass
360   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
361   */
362  protected boolean warning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) {
363    if (!thePass) {
364      msg = formatMessage(msg, theMessageArguments);
365      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.WARNING));
366    }
367    return thePass;
368  }
369
370  //---------
371  /**
372   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
373   * 
374   * @param thePass
375   *          Set this parameter to <code>false</code> if the validation does not pass
376   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
377   */
378  protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
379    if (!thePass) { 
380      msg = formatMessage(msg, theMessageArguments);
381      errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.INFORMATION));
382    }
383    return thePass;
384
385  }
386
387  /**
388   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
389   * 
390   * @param thePass
391   *          Set this parameter to <code>false</code> if the validation does not pass
392   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
393   */
394  protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
395    if (!thePass) {
396      String path = toPath(pathParts);
397      String message = formatMessage(theMessage, theMessageArguments);
398      errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.INFORMATION));
399    }
400    return thePass;
401  }
402
403  /**
404   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
405   * 
406   * @param thePass
407   *          Set this parameter to <code>false</code> if the validation does not pass
408   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
409   */
410  protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
411    if (!thePass) {
412      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.INFORMATION));
413    }
414    return thePass;
415  }
416
417  /**
418   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
419   * 
420   * @param thePass
421   *          Set this parameter to <code>false</code> if the validation does not pass
422   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
423   */
424  protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html) {
425    if (!thePass) {
426      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.INFORMATION));
427    }
428    return thePass;
429  }
430
431  /**
432   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
433   * 
434   * @param thePass
435   *          Set this parameter to <code>false</code> if the validation does not pass
436   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
437   */
438  protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) {
439    if (!thePass) {
440      msg = formatMessage(msg, theMessageArguments);
441      errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.INFORMATION));
442    }
443    return thePass;
444  }
445
446}