001package org.hl7.fhir.utilities.validation;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023
024/*
025 Copyright (c) 2011+, HL7, Inc
026 All rights reserved.
027
028 Redistribution and use in source and binary forms, with or without modification, 
029 are permitted provided that the following conditions are met:
030
031 * Redistributions of source code must retain the above copyright notice, this 
032 list of conditions and the following disclaimer.
033 * Redistributions in binary form must reproduce the above copyright notice, 
034 this list of conditions and the following disclaimer in the documentation 
035 and/or other materials provided with the distribution.
036 * Neither the name of HL7 nor the names of its contributors may be used to 
037 endorse or promote products derived from this software without specific 
038 prior written permission.
039
040 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
041 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
042 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
043 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
044 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
045 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
046 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
047 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
048 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
049 POSSIBILITY OF SUCH DAMAGE.
050
051 */
052
053import java.util.Comparator;
054
055import org.apache.commons.lang3.builder.ToStringBuilder;
056import org.apache.commons.lang3.builder.ToStringStyle;
057import org.hl7.fhir.exceptions.FHIRException;
058import org.hl7.fhir.utilities.Utilities;
059
060public class ValidationMessage implements Comparator<ValidationMessage>, Comparable<ValidationMessage>
061{
062  public enum Source {
063    ExampleValidator, 
064    ProfileValidator, 
065    ResourceValidator, 
066    InstanceValidator, 
067    Schema, 
068    Schematron, 
069    Publisher, 
070    Ontology, 
071    ProfileComparer, 
072    QuestionnaireResponseValidator
073  }
074
075  public enum IssueSeverity {
076    /**
077     * The issue caused the action to fail, and no further checking could be performed.
078     */
079    FATAL, 
080    /**
081     * The issue is sufficiently important to cause the action to fail.
082     */
083    ERROR, 
084    /**
085     * The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.
086     */
087    WARNING, 
088    /**
089     * The issue has no relation to the degree of success of the action.
090     */
091    INFORMATION, 
092    /**
093     * added to help the parsers with the generic types
094     */
095    NULL;
096    public static IssueSeverity fromCode(String codeString) throws FHIRException {
097      if (codeString == null || "".equals(codeString))
098        return null;
099      if ("fatal".equals(codeString))
100        return FATAL;
101      if ("error".equals(codeString))
102        return ERROR;
103      if ("warning".equals(codeString))
104        return WARNING;
105      if ("information".equals(codeString))
106        return INFORMATION;
107      else
108        throw new FHIRException("Unknown IssueSeverity code '"+codeString+"'");
109    }
110    public String toCode() {
111      switch (this) {
112      case FATAL: return "fatal";
113      case ERROR: return "error";
114      case WARNING: return "warning";
115      case INFORMATION: return "information";
116      default: return "?";
117      }
118    }
119    public String getSystem() {
120      switch (this) {
121      case FATAL: return "http://hl7.org/fhir/issue-severity";
122      case ERROR: return "http://hl7.org/fhir/issue-severity";
123      case WARNING: return "http://hl7.org/fhir/issue-severity";
124      case INFORMATION: return "http://hl7.org/fhir/issue-severity";
125      default: return "?";
126      }
127    }
128    public String getDefinition() {
129      switch (this) {
130      case FATAL: return "The issue caused the action to fail, and no further checking could be performed.";
131      case ERROR: return "The issue is sufficiently important to cause the action to fail.";
132      case WARNING: return "The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.";
133      case INFORMATION: return "The issue has no relation to the degree of success of the action.";
134      default: return "?";
135      }
136    }
137    public String getDisplay() {
138      switch (this) {
139      case FATAL: return "Fatal";
140      case ERROR: return "Error";
141      case WARNING: return "Warning";
142      case INFORMATION: return "Information";
143      default: return "?";
144      }
145    }
146  }
147
148  public enum IssueType {
149    /**
150     * Content invalid against the specification or a profile.
151     */
152    INVALID, 
153    /**
154     * A structural issue in the content such as wrong namespace, or unable to parse the content completely, or invalid json syntax.
155     */
156    STRUCTURE, 
157    /**
158     * A required element is missing.
159     */
160    REQUIRED, 
161    /**
162     * An element value is invalid.
163     */
164    VALUE, 
165    /**
166     * A content validation rule failed - e.g. a schematron rule.
167     */
168    INVARIANT, 
169    /**
170     * An authentication/authorization/permissions issue of some kind.
171     */
172    SECURITY, 
173    /**
174     * The client needs to initiate an authentication process.
175     */
176    LOGIN, 
177    /**
178     * The user or system was not able to be authenticated (either there is no process, or the proferred token is unacceptable).
179     */
180    UNKNOWN, 
181    /**
182     * User session expired; a login may be required.
183     */
184    EXPIRED, 
185    /**
186     * The user does not have the rights to perform this action.
187     */
188    FORBIDDEN, 
189    /**
190     * Some information was not or may not have been returned due to business rules, consent or privacy rules, or access permission constraints.  This information may be accessible through alternate processes.
191     */
192    SUPPRESSED, 
193    /**
194     * Processing issues. These are expected to be final e.g. there is no point resubmitting the same content unchanged.
195     */
196    PROCESSING, 
197    /**
198     * The resource or profile is not supported.
199     */
200    NOTSUPPORTED, 
201    /**
202     * An attempt was made to create a duplicate record.
203     */
204    DUPLICATE, 
205    /**
206     * The reference provided was not found. In a pure RESTful environment, this would be an HTTP 404 error, but this code may be used where the content is not found further into the application architecture.
207     */
208    NOTFOUND, 
209    /**
210     * Provided content is too long (typically, this is a denial of service protection type of error).
211     */
212    TOOLONG, 
213    /**
214     * The code or system could not be understood, or it was not valid in the context of a particular ValueSet.code.
215     */
216    CODEINVALID, 
217    /**
218     * An extension was found that was not acceptable, could not be resolved, or a modifierExtension was not recognized.
219     */
220    EXTENSION, 
221    /**
222     * The operation was stopped to protect server resources; e.g. a request for a value set expansion on all of SNOMED CT.
223     */
224    TOOCOSTLY, 
225    /**
226     * The content/operation failed to pass some business rule, and so could not proceed.
227     */
228    BUSINESSRULE, 
229    /**
230     * Content could not be accepted because of an edit conflict (i.e. version aware updates) (In a pure RESTful environment, this would be an HTTP 404 error, but this code may be used where the conflict is discovered further into the application architecture.)
231     */
232    CONFLICT, 
233    /**
234     * Not all data sources typically accessed could be reached, or responded in time, so the returned information may not be complete.
235     */
236    INCOMPLETE, 
237    /**
238     * Transient processing issues. The system receiving the error may be able to resubmit the same content once an underlying issue is resolved.
239     */
240    TRANSIENT, 
241    /**
242     * A resource/record locking failure (usually in an underlying database).
243     */
244    LOCKERROR, 
245    /**
246     * The persistent store is unavailable; e.g. the database is down for maintenance or similar action.
247     */
248    NOSTORE, 
249    /**
250     * An unexpected internal error has occurred.
251     */
252    EXCEPTION, 
253    /**
254     * An internal timeout has occurred.
255     */
256    TIMEOUT, 
257    /**
258     * The system is not prepared to handle this request due to load management.
259     */
260    THROTTLED, 
261    /**
262     * A message unrelated to the processing success of the completed operation (examples of the latter include things like reminders of password expiry, system maintenance times, etc.).
263     */
264    INFORMATIONAL, 
265    /**
266     * added to help the parsers with the generic types
267     */
268    NULL;
269    public static IssueType fromCode(String codeString) throws FHIRException {
270      if (codeString == null || "".equals(codeString))
271        return null;
272      if ("invalid".equals(codeString))
273        return INVALID;
274      if ("structure".equals(codeString))
275        return STRUCTURE;
276      if ("required".equals(codeString))
277        return REQUIRED;
278      if ("value".equals(codeString))
279        return VALUE;
280      if ("invariant".equals(codeString))
281        return INVARIANT;
282      if ("security".equals(codeString))
283        return SECURITY;
284      if ("login".equals(codeString))
285        return LOGIN;
286      if ("unknown".equals(codeString))
287        return UNKNOWN;
288      if ("expired".equals(codeString))
289        return EXPIRED;
290      if ("forbidden".equals(codeString))
291        return FORBIDDEN;
292      if ("suppressed".equals(codeString))
293        return SUPPRESSED;
294      if ("processing".equals(codeString))
295        return PROCESSING;
296      if ("not-supported".equals(codeString))
297        return NOTSUPPORTED;
298      if ("duplicate".equals(codeString))
299        return DUPLICATE;
300      if ("not-found".equals(codeString))
301        return NOTFOUND;
302      if ("too-long".equals(codeString))
303        return TOOLONG;
304      if ("code-invalid".equals(codeString))
305        return CODEINVALID;
306      if ("extension".equals(codeString))
307        return EXTENSION;
308      if ("too-costly".equals(codeString))
309        return TOOCOSTLY;
310      if ("business-rule".equals(codeString))
311        return BUSINESSRULE;
312      if ("conflict".equals(codeString))
313        return CONFLICT;
314      if ("incomplete".equals(codeString))
315        return INCOMPLETE;
316      if ("transient".equals(codeString))
317        return TRANSIENT;
318      if ("lock-error".equals(codeString))
319        return LOCKERROR;
320      if ("no-store".equals(codeString))
321        return NOSTORE;
322      if ("exception".equals(codeString))
323        return EXCEPTION;
324      if ("timeout".equals(codeString))
325        return TIMEOUT;
326      if ("throttled".equals(codeString))
327        return THROTTLED;
328      if ("informational".equals(codeString))
329        return INFORMATIONAL;
330      else
331        throw new FHIRException("Unknown IssueType code '"+codeString+"'");
332    }
333    public String toCode() {
334      switch (this) {
335      case INVALID: return "invalid";
336      case STRUCTURE: return "structure";
337      case REQUIRED: return "required";
338      case VALUE: return "value";
339      case INVARIANT: return "invariant";
340      case SECURITY: return "security";
341      case LOGIN: return "login";
342      case UNKNOWN: return "unknown";
343      case EXPIRED: return "expired";
344      case FORBIDDEN: return "forbidden";
345      case SUPPRESSED: return "suppressed";
346      case PROCESSING: return "processing";
347      case NOTSUPPORTED: return "not-supported";
348      case DUPLICATE: return "duplicate";
349      case NOTFOUND: return "not-found";
350      case TOOLONG: return "too-long";
351      case CODEINVALID: return "code-invalid";
352      case EXTENSION: return "extension";
353      case TOOCOSTLY: return "too-costly";
354      case BUSINESSRULE: return "business-rule";
355      case CONFLICT: return "conflict";
356      case INCOMPLETE: return "incomplete";
357      case TRANSIENT: return "transient";
358      case LOCKERROR: return "lock-error";
359      case NOSTORE: return "no-store";
360      case EXCEPTION: return "exception";
361      case TIMEOUT: return "timeout";
362      case THROTTLED: return "throttled";
363      case INFORMATIONAL: return "informational";
364      default: return "?";
365      }
366    }
367    public String getSystem() {
368      switch (this) {
369      case INVALID: return "http://hl7.org/fhir/issue-type";
370      case STRUCTURE: return "http://hl7.org/fhir/issue-type";
371      case REQUIRED: return "http://hl7.org/fhir/issue-type";
372      case VALUE: return "http://hl7.org/fhir/issue-type";
373      case INVARIANT: return "http://hl7.org/fhir/issue-type";
374      case SECURITY: return "http://hl7.org/fhir/issue-type";
375      case LOGIN: return "http://hl7.org/fhir/issue-type";
376      case UNKNOWN: return "http://hl7.org/fhir/issue-type";
377      case EXPIRED: return "http://hl7.org/fhir/issue-type";
378      case FORBIDDEN: return "http://hl7.org/fhir/issue-type";
379      case SUPPRESSED: return "http://hl7.org/fhir/issue-type";
380      case PROCESSING: return "http://hl7.org/fhir/issue-type";
381      case NOTSUPPORTED: return "http://hl7.org/fhir/issue-type";
382      case DUPLICATE: return "http://hl7.org/fhir/issue-type";
383      case NOTFOUND: return "http://hl7.org/fhir/issue-type";
384      case TOOLONG: return "http://hl7.org/fhir/issue-type";
385      case CODEINVALID: return "http://hl7.org/fhir/issue-type";
386      case EXTENSION: return "http://hl7.org/fhir/issue-type";
387      case TOOCOSTLY: return "http://hl7.org/fhir/issue-type";
388      case BUSINESSRULE: return "http://hl7.org/fhir/issue-type";
389      case CONFLICT: return "http://hl7.org/fhir/issue-type";
390      case INCOMPLETE: return "http://hl7.org/fhir/issue-type";
391      case TRANSIENT: return "http://hl7.org/fhir/issue-type";
392      case LOCKERROR: return "http://hl7.org/fhir/issue-type";
393      case NOSTORE: return "http://hl7.org/fhir/issue-type";
394      case EXCEPTION: return "http://hl7.org/fhir/issue-type";
395      case TIMEOUT: return "http://hl7.org/fhir/issue-type";
396      case THROTTLED: return "http://hl7.org/fhir/issue-type";
397      case INFORMATIONAL: return "http://hl7.org/fhir/issue-type";
398      default: return "?";
399      }
400    }
401    public String getDefinition() {
402      switch (this) {
403      case INVALID: return "Content invalid against the specification or a profile.";
404      case STRUCTURE: return "A structural issue in the content such as wrong namespace, or unable to parse the content completely, or invalid json syntax.";
405      case REQUIRED: return "A required element is missing.";
406      case VALUE: return "An element value is invalid.";
407      case INVARIANT: return "A content validation rule failed - e.g. a schematron rule.";
408      case SECURITY: return "An authentication/authorization/permissions issue of some kind.";
409      case LOGIN: return "The client needs to initiate an authentication process.";
410      case UNKNOWN: return "The user or system was not able to be authenticated (either there is no process, or the proferred token is unacceptable).";
411      case EXPIRED: return "User session expired; a login may be required.";
412      case FORBIDDEN: return "The user does not have the rights to perform this action.";
413      case SUPPRESSED: return "Some information was not or may not have been returned due to business rules, consent or privacy rules, or access permission constraints.  This information may be accessible through alternate processes.";
414      case PROCESSING: return "Processing issues. These are expected to be final e.g. there is no point resubmitting the same content unchanged.";
415      case NOTSUPPORTED: return "The resource or profile is not supported.";
416      case DUPLICATE: return "An attempt was made to create a duplicate record.";
417      case NOTFOUND: return "The reference provided was not found. In a pure RESTful environment, this would be an HTTP 404 error, but this code may be used where the content is not found further into the application architecture.";
418      case TOOLONG: return "Provided content is too long (typically, this is a denial of service protection type of error).";
419      case CODEINVALID: return "The code or system could not be understood, or it was not valid in the context of a particular ValueSet.code.";
420      case EXTENSION: return "An extension was found that was not acceptable, could not be resolved, or a modifierExtension was not recognized.";
421      case TOOCOSTLY: return "The operation was stopped to protect server resources; e.g. a request for a value set expansion on all of SNOMED CT.";
422      case BUSINESSRULE: return "The content/operation failed to pass some business rule, and so could not proceed.";
423      case CONFLICT: return "Content could not be accepted because of an edit conflict (i.e. version aware updates) (In a pure RESTful environment, this would be an HTTP 404 error, but this code may be used where the conflict is discovered further into the application architecture.)";
424      case INCOMPLETE: return "Not all data sources typically accessed could be reached, or responded in time, so the returned information may not be complete.";
425      case TRANSIENT: return "Transient processing issues. The system receiving the error may be able to resubmit the same content once an underlying issue is resolved.";
426      case LOCKERROR: return "A resource/record locking failure (usually in an underlying database).";
427      case NOSTORE: return "The persistent store is unavailable; e.g. the database is down for maintenance or similar action.";
428      case EXCEPTION: return "An unexpected internal error has occurred.";
429      case TIMEOUT: return "An internal timeout has occurred.";
430      case THROTTLED: return "The system is not prepared to handle this request due to load management.";
431      case INFORMATIONAL: return "A message unrelated to the processing success of the completed operation (examples of the latter include things like reminders of password expiry, system maintenance times, etc.).";
432      default: return "?";
433      }
434    }
435    public String getDisplay() {
436      switch (this) {
437      case INVALID: return "Invalid Content";
438      case STRUCTURE: return "Structural Issue";
439      case REQUIRED: return "Required element missing";
440      case VALUE: return "Element value invalid";
441      case INVARIANT: return "Validation rule failed";
442      case SECURITY: return "Security Problem";
443      case LOGIN: return "Login Required";
444      case UNKNOWN: return "Unknown User";
445      case EXPIRED: return "Session Expired";
446      case FORBIDDEN: return "Forbidden";
447      case SUPPRESSED: return "Information  Suppressed";
448      case PROCESSING: return "Processing Failure";
449      case NOTSUPPORTED: return "Content not supported";
450      case DUPLICATE: return "Duplicate";
451      case NOTFOUND: return "Not Found";
452      case TOOLONG: return "Content Too Long";
453      case CODEINVALID: return "Invalid Code";
454      case EXTENSION: return "Unacceptable Extension";
455      case TOOCOSTLY: return "Operation Too Costly";
456      case BUSINESSRULE: return "Business Rule Violation";
457      case CONFLICT: return "Edit Version Conflict";
458      case INCOMPLETE: return "Incomplete Results";
459      case TRANSIENT: return "Transient Issue";
460      case LOCKERROR: return "Lock Error";
461      case NOSTORE: return "No Store Available";
462      case EXCEPTION: return "Exception";
463      case TIMEOUT: return "Timeout";
464      case THROTTLED: return "Throttled";
465      case INFORMATIONAL: return "Informational Note";
466      default: return "?";
467      }
468    }
469  }
470
471
472  private Source source;
473  private int line;
474  private int col;
475  private String location;
476  private String message;
477  private IssueType type;
478  private IssueSeverity level;
479  private String html;
480  private String locationLink;
481
482
483  /**
484   * Constructor
485   */
486  public ValidationMessage() {
487    super();
488  }
489
490  public ValidationMessage(Source source, IssueType type, String path, String message, IssueSeverity level) {
491    super();
492    this.line = -1;
493    this.col = -1;
494    this.location = path;
495    if (message == null)
496      throw new Error("message is null");
497    this.message = message;
498    this.html = Utilities.escapeXml(message);
499    this.level = level;
500    this.source = source;
501    this.type = type;
502    if (level == IssueSeverity.NULL)
503      determineLevel(path);
504    if (type == null)
505      throw new Error("A type must be provided");
506  }
507
508  public ValidationMessage(Source source, IssueType type, int line, int col, String path, String message, IssueSeverity level) {
509    super();
510    this.line = line;
511    this.col = col;
512    this.location = path;
513    this.message = message;
514    this.html = Utilities.escapeXml(message);
515    this.level = level;
516    this.source = source;
517    this.type = type;
518    if (level == IssueSeverity.NULL)
519      determineLevel(path);
520    if (type == null)
521      throw new Error("A type must be provided");
522  }
523
524  public ValidationMessage(Source source, IssueType type, String path, String message, String html, IssueSeverity level) {
525    super();
526    this.line = -1;
527    this.col = -1;
528    this.location = path;
529    if (message == null)
530      throw new Error("message is null");
531    this.message = message;
532    this.html = html;
533    this.level = level;
534    this.source = source;
535    this.type = type;
536    if (level == IssueSeverity.NULL)
537      determineLevel(path);
538    if (type == null)
539      throw new Error("A type must be provided");
540  }
541
542  public ValidationMessage(Source source, IssueType type, int line, int col, String path, String message, String html, IssueSeverity level) {
543    super();
544    this.line = line;
545    this.col = col;
546    this.location = path;
547    if (message == null)
548      throw new Error("message is null");
549    this.message = message;
550    this.html = html;
551    this.level = level;
552    this.source = source;
553    this.type = type;
554    if (level == IssueSeverity.NULL)
555      determineLevel(path);
556    if (type == null)
557      throw new Error("A type must be provided");
558  }
559
560  public ValidationMessage(Source source, IssueType type, String message, IssueSeverity level) {
561    super();
562    this.line = -1;
563    this.col = -1;
564    if (message == null)
565      throw new Error("message is null");
566    this.message = message;
567    this.level = level;
568    this.source = source;
569    this.type = type;
570    if (type == null)
571      throw new Error("A type must be provided");
572  }
573
574  private IssueSeverity determineLevel(String path) {
575    if (isGrandfathered(path))
576      return IssueSeverity.WARNING;
577    else
578      return IssueSeverity.ERROR;
579  }
580
581  private boolean isGrandfathered(String path) {
582    if (path.startsWith("xds-documentmanifest."))
583      return true;
584    if (path.startsWith("observation-device-metric-devicemetricobservation."))
585      return true;
586    if (path.startsWith("medicationadministration-immunization-vaccine."))
587      return true;
588    if (path.startsWith("elementdefinition-de-dataelement."))
589      return true;
590    if (path.startsWith("dataelement-sdc-sdcelement."))
591      return true;
592    if (path.startsWith("questionnaireresponse-sdc-structureddatacaptureanswers."))
593      return true;
594    if (path.startsWith("valueset-sdc-structureddatacapturevalueset."))
595      return true;
596    if (path.startsWith("dataelement-sdc-de-sdcelement."))
597      return true;
598    if (path.startsWith("do-uslab-uslabdo."))
599      return true;
600    if (path.startsWith("."))
601      return true;
602    if (path.startsWith("."))
603      return true;
604    if (path.startsWith("."))
605      return true;
606    if (path.startsWith("."))
607      return true;
608
609    return false;
610  }
611
612  public String getMessage() {
613    return message;
614  }
615  public ValidationMessage setMessage(String message) {
616    this.message = message;
617    return this;
618  }
619
620  public IssueSeverity getLevel() {
621    return level;
622  }
623  public ValidationMessage setLevel(IssueSeverity level) {
624    this.level = level;
625    return this;
626  }
627
628  public Source getSource() {
629    return source;
630  }
631  public ValidationMessage setSource(Source source) {
632    this.source = source;
633    return this;
634  }
635
636  public int getLine() {
637    return line;
638  }
639
640  public void setLine(int theLine) {
641    line = theLine;
642  }
643
644  public int getCol() {
645    return col;
646  }
647
648  public void setCol(int theCol) {
649    col = theCol;
650  }
651
652  public String getLocation() {
653    return location;
654  }
655  public ValidationMessage setLocation(String location) {
656    this.location = location;
657    return this;
658  }
659
660  public IssueType getType() {
661    return type;
662  }
663
664  public ValidationMessage setType(IssueType type) {
665    this.type = type;
666    return this;
667  }
668
669  public String summary() {
670    return level.toString()+" @ "+location+(line>= 0 && col >= 0 ? " (line "+Integer.toString(line)+", col"+Integer.toString(col)+"): " : ": ") +message +(source != null ? " (src = "+source+")" : "");
671  }
672
673
674  public String toXML() {
675    return "<message source=\"" + source + "\" line=\"" + line + "\" col=\"" + col + "\" location=\"" + Utilities.escapeXml(location) + "\" type=\"" + type + "\" level=\"" + level + "\" display=\"" + Utilities.escapeXml(getDisplay()) + "\" ><plain>" + Utilities.escapeXml(message) + "</plain><html>" + html + "</html></message>";
676  }
677
678  public String getHtml() {
679    return html == null ? Utilities.escapeXml(message) : html;
680  }
681
682  public String getDisplay() {
683    return level + ": " + (location.isEmpty() ? "" : (location + ": ")) + message;
684  }
685  
686  /**
687   * Returns a representation of this ValidationMessage suitable for logging. The values of
688   * most of the internal fields are included, so this may not be suitable for display to 
689   * an end user.
690   */
691  @Override
692  public String toString() {
693    ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
694    b.append("level", level);
695    b.append("type", type);
696    b.append("location", location);
697    b.append("message", message);
698    return b.build();
699  }
700
701  @Override
702  public boolean equals(Object o) {
703    return (this.getMessage() != null && this.getMessage().equals(((ValidationMessage)o).getMessage())) && (this.getLocation() != null && this.getLocation().equals(((ValidationMessage)o).getLocation()));
704  }
705
706  @Override
707  public int compare(ValidationMessage x, ValidationMessage y) {
708    String sx = x.getLevel().getDisplay() + x.getType().getDisplay() + String.format("%06d", x.getLine()) + x.getMessage();
709    String sy = y.getLevel().getDisplay() + y.getType().getDisplay() + String.format("%06d", y.getLine()) + y.getMessage();
710    return sx.compareTo(sy);
711  }  
712
713  @Override
714  public int compareTo(ValidationMessage y) {
715    return compare(this, y);
716  }
717
718  public String getLocationLink() {
719    return locationLink;
720  }
721
722  public ValidationMessage setLocationLink(String locationLink) {
723    this.locationLink = locationLink;
724    return this;
725  }
726
727  
728}