001package org.hl7.fhir.utilities.validation;
002
003/*-
004 * #%L
005 * org.hl7.fhir.utilities
006 * %%
007 * Copyright (C) 2014 - 2019 Health Level 7
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    Template,
068    Schema, 
069    Schematron, 
070    Publisher, 
071    LinkChecker,
072    Ontology, 
073    ProfileComparer, 
074    TerminologyEngine,
075    QuestionnaireResponseValidator
076  }
077
078  public enum IssueSeverity {
079    /**
080     * The issue caused the action to fail, and no further checking could be performed.
081     */
082    FATAL, 
083    /**
084     * The issue is sufficiently important to cause the action to fail.
085     */
086    ERROR, 
087    /**
088     * 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.
089     */
090    WARNING, 
091    /**
092     * The issue has no relation to the degree of success of the action.
093     */
094    INFORMATION, 
095    /**
096     * added to help the parsers with the generic types
097     */
098    NULL;
099    public static IssueSeverity fromCode(String codeString) throws FHIRException {
100      if (codeString == null || "".equals(codeString))
101        return null;
102      if ("fatal".equals(codeString))
103        return FATAL;
104      if ("error".equals(codeString))
105        return ERROR;
106      if ("warning".equals(codeString))
107        return WARNING;
108      if ("information".equals(codeString))
109        return INFORMATION;
110      else
111        throw new FHIRException("Unknown IssueSeverity code '"+codeString+"'");
112    }
113    public String toCode() {
114      switch (this) {
115      case FATAL: return "fatal";
116      case ERROR: return "error";
117      case WARNING: return "warning";
118      case INFORMATION: return "information";
119      default: return "?";
120      }
121    }
122    public String getSystem() {
123      switch (this) {
124      case FATAL: return "http://hl7.org/fhir/issue-severity";
125      case ERROR: return "http://hl7.org/fhir/issue-severity";
126      case WARNING: return "http://hl7.org/fhir/issue-severity";
127      case INFORMATION: return "http://hl7.org/fhir/issue-severity";
128      default: return "?";
129      }
130    }
131    public String getDefinition() {
132      switch (this) {
133      case FATAL: return "The issue caused the action to fail, and no further checking could be performed.";
134      case ERROR: return "The issue is sufficiently important to cause the action to fail.";
135      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.";
136      case INFORMATION: return "The issue has no relation to the degree of success of the action.";
137      default: return "?";
138      }
139    }
140    public String getDisplay() {
141      switch (this) {
142      case FATAL: return "Fatal";
143      case ERROR: return "Error";
144      case WARNING: return "Warning";
145      case INFORMATION: return "Information";
146      default: return "?";
147      }
148    }
149    public boolean isError() {
150      return this == FATAL || this == ERROR;
151    }
152  }
153
154  public enum IssueType {
155    /**
156     * Content invalid against the specification or a profile.
157     */
158    INVALID, 
159    DELETED,
160    /**
161     * A structural issue in the content such as wrong namespace, or unable to parse the content completely, or invalid json syntax.
162     */
163    STRUCTURE, 
164    /**
165     * A required element is missing.
166     */
167    REQUIRED, 
168    /**
169     * An element value is invalid.
170     */
171    VALUE, 
172    /**
173     * A content validation rule failed - e.g. a schematron rule.
174     */
175    INVARIANT, 
176    /**
177     * An authentication/authorization/permissions issue of some kind.
178     */
179    SECURITY, 
180    /**
181     * The client needs to initiate an authentication process.
182     */
183    LOGIN, 
184    /**
185     * The user or system was not able to be authenticated (either there is no process, or the proferred token is unacceptable).
186     */
187    MULTIPLEMATCHES,
188    UNKNOWN, 
189    /**
190     * User session expired; a login may be required.
191     */
192    EXPIRED, 
193    /**
194     * The user does not have the rights to perform this action.
195     */
196    FORBIDDEN, 
197    /**
198     * 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.
199     */
200    SUPPRESSED, 
201    /**
202     * Processing issues. These are expected to be final e.g. there is no point resubmitting the same content unchanged.
203     */
204    PROCESSING, 
205    /**
206     * The resource or profile is not supported.
207     */
208    NOTSUPPORTED, 
209    /**
210     * An attempt was made to create a duplicate record.
211     */
212    DUPLICATE, 
213    /**
214     * 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.
215     */
216    NOTFOUND, 
217    /**
218     * Provided content is too long (typically, this is a denial of service protection type of error).
219     */
220    TOOLONG, 
221    /**
222     * The code or system could not be understood, or it was not valid in the context of a particular ValueSet.code.
223     */
224    CODEINVALID, 
225    /**
226     * An extension was found that was not acceptable, could not be resolved, or a modifierExtension was not recognized.
227     */
228    EXTENSION, 
229    /**
230     * The operation was stopped to protect server resources; e.g. a request for a value set expansion on all of SNOMED CT.
231     */
232    TOOCOSTLY, 
233    /**
234     * The content/operation failed to pass some business rule, and so could not proceed.
235     */
236    BUSINESSRULE, 
237    /**
238     * 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.)
239     */
240    CONFLICT, 
241    /**
242     * Not all data sources typically accessed could be reached, or responded in time, so the returned information may not be complete.
243     */
244    INCOMPLETE, 
245    /**
246     * Transient processing issues. The system receiving the error may be able to resubmit the same content once an underlying issue is resolved.
247     */
248    TRANSIENT, 
249    /**
250     * A resource/record locking failure (usually in an underlying database).
251     */
252    LOCKERROR, 
253    /**
254     * The persistent store is unavailable; e.g. the database is down for maintenance or similar action.
255     */
256    NOSTORE, 
257    /**
258     * An unexpected internal error has occurred.
259     */
260    EXCEPTION, 
261    /**
262     * An internal timeout has occurred.
263     */
264    TIMEOUT, 
265    /**
266     * The system is not prepared to handle this request due to load management.
267     */
268    THROTTLED, 
269    /**
270     * 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.).
271     */
272    INFORMATIONAL, 
273    /**
274     * added to help the parsers with the generic types
275     */
276    NULL;
277    public static IssueType fromCode(String codeString) throws FHIRException {
278      if (codeString == null || "".equals(codeString))
279        return null;
280      if ("invalid".equals(codeString))
281        return INVALID;
282      if ("structure".equals(codeString))
283        return STRUCTURE;
284      if ("required".equals(codeString))
285        return REQUIRED;
286      if ("value".equals(codeString))
287        return VALUE;
288      if ("invariant".equals(codeString))
289        return INVARIANT;
290      if ("security".equals(codeString))
291        return SECURITY;
292      if ("login".equals(codeString))
293        return LOGIN;
294      if ("unknown".equals(codeString))
295        return UNKNOWN;
296      if ("expired".equals(codeString))
297        return EXPIRED;
298      if ("forbidden".equals(codeString))
299        return FORBIDDEN;
300      if ("suppressed".equals(codeString))
301        return SUPPRESSED;
302      if ("processing".equals(codeString))
303        return PROCESSING;
304      if ("not-supported".equals(codeString))
305        return NOTSUPPORTED;
306      if ("duplicate".equals(codeString))
307        return DUPLICATE;
308      if ("not-found".equals(codeString))
309        return NOTFOUND;
310      if ("too-long".equals(codeString))
311        return TOOLONG;
312      if ("code-invalid".equals(codeString))
313        return CODEINVALID;
314      if ("extension".equals(codeString))
315        return EXTENSION;
316      if ("too-costly".equals(codeString))
317        return TOOCOSTLY;
318      if ("business-rule".equals(codeString))
319        return BUSINESSRULE;
320      if ("conflict".equals(codeString))
321        return CONFLICT;
322      if ("incomplete".equals(codeString))
323        return INCOMPLETE;
324      if ("transient".equals(codeString))
325        return TRANSIENT;
326      if ("lock-error".equals(codeString))
327        return LOCKERROR;
328      if ("no-store".equals(codeString))
329        return NOSTORE;
330      if ("exception".equals(codeString))
331        return EXCEPTION;
332      if ("timeout".equals(codeString))
333        return TIMEOUT;
334      if ("throttled".equals(codeString))
335        return THROTTLED;
336      if ("informational".equals(codeString))
337        return INFORMATIONAL;
338      else
339        throw new FHIRException("Unknown IssueType code '"+codeString+"'");
340    }
341    public String toCode() {
342      switch (this) {
343      case INVALID: return "invalid";
344      case STRUCTURE: return "structure";
345      case REQUIRED: return "required";
346      case VALUE: return "value";
347      case INVARIANT: return "invariant";
348      case SECURITY: return "security";
349      case LOGIN: return "login";
350      case UNKNOWN: return "unknown";
351      case EXPIRED: return "expired";
352      case FORBIDDEN: return "forbidden";
353      case SUPPRESSED: return "suppressed";
354      case PROCESSING: return "processing";
355      case NOTSUPPORTED: return "not-supported";
356      case DUPLICATE: return "duplicate";
357      case NOTFOUND: return "not-found";
358      case TOOLONG: return "too-long";
359      case CODEINVALID: return "code-invalid";
360      case EXTENSION: return "extension";
361      case TOOCOSTLY: return "too-costly";
362      case BUSINESSRULE: return "business-rule";
363      case CONFLICT: return "conflict";
364      case INCOMPLETE: return "incomplete";
365      case TRANSIENT: return "transient";
366      case LOCKERROR: return "lock-error";
367      case NOSTORE: return "no-store";
368      case EXCEPTION: return "exception";
369      case TIMEOUT: return "timeout";
370      case THROTTLED: return "throttled";
371      case INFORMATIONAL: return "informational";
372      default: return "?";
373      }
374    }
375    public String getSystem() {
376      switch (this) {
377      case INVALID: return "http://hl7.org/fhir/issue-type";
378      case STRUCTURE: return "http://hl7.org/fhir/issue-type";
379      case REQUIRED: return "http://hl7.org/fhir/issue-type";
380      case VALUE: return "http://hl7.org/fhir/issue-type";
381      case INVARIANT: return "http://hl7.org/fhir/issue-type";
382      case SECURITY: return "http://hl7.org/fhir/issue-type";
383      case LOGIN: return "http://hl7.org/fhir/issue-type";
384      case UNKNOWN: return "http://hl7.org/fhir/issue-type";
385      case EXPIRED: return "http://hl7.org/fhir/issue-type";
386      case FORBIDDEN: return "http://hl7.org/fhir/issue-type";
387      case SUPPRESSED: return "http://hl7.org/fhir/issue-type";
388      case PROCESSING: return "http://hl7.org/fhir/issue-type";
389      case NOTSUPPORTED: return "http://hl7.org/fhir/issue-type";
390      case DUPLICATE: return "http://hl7.org/fhir/issue-type";
391      case NOTFOUND: return "http://hl7.org/fhir/issue-type";
392      case TOOLONG: return "http://hl7.org/fhir/issue-type";
393      case CODEINVALID: return "http://hl7.org/fhir/issue-type";
394      case EXTENSION: return "http://hl7.org/fhir/issue-type";
395      case TOOCOSTLY: return "http://hl7.org/fhir/issue-type";
396      case BUSINESSRULE: return "http://hl7.org/fhir/issue-type";
397      case CONFLICT: return "http://hl7.org/fhir/issue-type";
398      case INCOMPLETE: return "http://hl7.org/fhir/issue-type";
399      case TRANSIENT: return "http://hl7.org/fhir/issue-type";
400      case LOCKERROR: return "http://hl7.org/fhir/issue-type";
401      case NOSTORE: return "http://hl7.org/fhir/issue-type";
402      case EXCEPTION: return "http://hl7.org/fhir/issue-type";
403      case TIMEOUT: return "http://hl7.org/fhir/issue-type";
404      case THROTTLED: return "http://hl7.org/fhir/issue-type";
405      case INFORMATIONAL: return "http://hl7.org/fhir/issue-type";
406      default: return "?";
407      }
408    }
409    public String getDefinition() {
410      switch (this) {
411      case INVALID: return "Content invalid against the specification or a profile.";
412      case STRUCTURE: return "A structural issue in the content such as wrong namespace, or unable to parse the content completely, or invalid json syntax.";
413      case REQUIRED: return "A required element is missing.";
414      case VALUE: return "An element value is invalid.";
415      case INVARIANT: return "A content validation rule failed - e.g. a schematron rule.";
416      case SECURITY: return "An authentication/authorization/permissions issue of some kind.";
417      case LOGIN: return "The client needs to initiate an authentication process.";
418      case UNKNOWN: return "The user or system was not able to be authenticated (either there is no process, or the proferred token is unacceptable).";
419      case EXPIRED: return "User session expired; a login may be required.";
420      case FORBIDDEN: return "The user does not have the rights to perform this action.";
421      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.";
422      case PROCESSING: return "Processing issues. These are expected to be final e.g. there is no point resubmitting the same content unchanged.";
423      case NOTSUPPORTED: return "The resource or profile is not supported.";
424      case DUPLICATE: return "An attempt was made to create a duplicate record.";
425      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.";
426      case TOOLONG: return "Provided content is too long (typically, this is a denial of service protection type of error).";
427      case CODEINVALID: return "The code or system could not be understood, or it was not valid in the context of a particular ValueSet.code.";
428      case EXTENSION: return "An extension was found that was not acceptable, could not be resolved, or a modifierExtension was not recognized.";
429      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.";
430      case BUSINESSRULE: return "The content/operation failed to pass some business rule, and so could not proceed.";
431      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.)";
432      case INCOMPLETE: return "Not all data sources typically accessed could be reached, or responded in time, so the returned information may not be complete.";
433      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.";
434      case LOCKERROR: return "A resource/record locking failure (usually in an underlying database).";
435      case NOSTORE: return "The persistent store is unavailable; e.g. the database is down for maintenance or similar action.";
436      case EXCEPTION: return "An unexpected internal error has occurred.";
437      case TIMEOUT: return "An internal timeout has occurred.";
438      case THROTTLED: return "The system is not prepared to handle this request due to load management.";
439      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.).";
440      default: return "?";
441      }
442    }
443    public String getDisplay() {
444      switch (this) {
445      case INVALID: return "Invalid Content";
446      case STRUCTURE: return "Structural Issue";
447      case REQUIRED: return "Required element missing";
448      case VALUE: return "Element value invalid";
449      case INVARIANT: return "Validation rule failed";
450      case SECURITY: return "Security Problem";
451      case LOGIN: return "Login Required";
452      case UNKNOWN: return "Unknown User";
453      case EXPIRED: return "Session Expired";
454      case FORBIDDEN: return "Forbidden";
455      case SUPPRESSED: return "Information  Suppressed";
456      case PROCESSING: return "Processing Failure";
457      case NOTSUPPORTED: return "Content not supported";
458      case DUPLICATE: return "Duplicate";
459      case NOTFOUND: return "Not Found";
460      case TOOLONG: return "Content Too Long";
461      case CODEINVALID: return "Invalid Code";
462      case EXTENSION: return "Unacceptable Extension";
463      case TOOCOSTLY: return "Operation Too Costly";
464      case BUSINESSRULE: return "Business Rule Violation";
465      case CONFLICT: return "Edit Version Conflict";
466      case INCOMPLETE: return "Incomplete Results";
467      case TRANSIENT: return "Transient Issue";
468      case LOCKERROR: return "Lock Error";
469      case NOSTORE: return "No Store Available";
470      case EXCEPTION: return "Exception";
471      case TIMEOUT: return "Timeout";
472      case THROTTLED: return "Throttled";
473      case INFORMATIONAL: return "Informational Note";
474      default: return "?";
475      }
476    }
477  }
478
479
480  private Source source;
481  private int line;
482  private int col;
483  private String location; // fhirPath
484  private String message;
485  private IssueType type;
486  private IssueSeverity level;
487  private String html;
488  private String locationLink;
489  private String txLink;
490
491
492  /**
493   * Constructor
494   */
495  public ValidationMessage() {
496    // nothing
497  }
498
499  public ValidationMessage(Source source, IssueType type, String path, String message, IssueSeverity level) {
500    super();
501    this.line = -1;
502    this.col = -1;
503    this.location = path;
504    if (message == null)
505      throw new Error("message is null");
506    this.message = message;
507    this.html = Utilities.escapeXml(message);
508    this.level = level;
509    this.source = source;
510    this.type = type;
511    if (level == IssueSeverity.NULL)
512      determineLevel(path);
513    if (type == null)
514      throw new Error("A type must be provided");
515  }
516
517  public ValidationMessage(Source source, IssueType type, int line, int col, String path, String message, IssueSeverity level) {
518    super();
519    this.line = line;
520    this.col = col;
521    this.location = path;
522    this.message = message;
523    this.html = Utilities.escapeXml(message);
524    this.level = level;
525    this.source = source;
526    this.type = type;
527    if (level == IssueSeverity.NULL)
528      determineLevel(path);
529    if (type == null)
530      throw new Error("A type must be provided");
531  }
532
533  public ValidationMessage(Source source, IssueType type, String path, String message, String html, IssueSeverity level) {
534    super();
535    this.line = -1;
536    this.col = -1;
537    this.location = path;
538    if (message == null)
539      throw new Error("message is null");
540    this.message = message;
541    this.html = html;
542    this.level = level;
543    this.source = source;
544    this.type = type;
545    if (level == IssueSeverity.NULL)
546      determineLevel(path);
547    if (type == null)
548      throw new Error("A type must be provided");
549  }
550
551  public ValidationMessage(Source source, IssueType type, int line, int col, String path, String message, String html, IssueSeverity level) {
552    super();
553    this.line = line;
554    this.col = col;
555    this.location = path;
556    if (message == null)
557      throw new Error("message is null");
558    this.message = message;
559    this.html = html;
560    this.level = level;
561    this.source = source;
562    this.type = type;
563    if (level == IssueSeverity.NULL)
564      determineLevel(path);
565    if (type == null)
566      throw new Error("A type must be provided");
567  }
568
569//  public ValidationMessage(Source source, IssueType type, String message, IssueSeverity level) {
570//    super();
571//    this.line = -1;
572//    this.col = -1;
573//    if (message == null)
574//      throw new Error("message is null");
575//    this.message = message;
576//    this.level = level;
577//    this.source = source;
578//    this.type = type;
579//    if (type == null)
580//      throw new Error("A type must be provided");
581//  }
582
583  private IssueSeverity determineLevel(String path) {
584    if (isGrandfathered(path))
585      return IssueSeverity.WARNING;
586    else
587      return IssueSeverity.ERROR;
588  }
589
590  private boolean isGrandfathered(String path) {
591    if (path.startsWith("xds-documentmanifest."))
592      return true;
593    if (path.startsWith("observation-device-metric-devicemetricobservation."))
594      return true;
595    if (path.startsWith("medicationadministration-immunization-vaccine."))
596      return true;
597    if (path.startsWith("elementdefinition-de-dataelement."))
598      return true;
599    if (path.startsWith("dataelement-sdc-sdcelement."))
600      return true;
601    if (path.startsWith("questionnaireresponse-sdc-structureddatacaptureanswers."))
602      return true;
603    if (path.startsWith("valueset-sdc-structureddatacapturevalueset."))
604      return true;
605    if (path.startsWith("dataelement-sdc-de-sdcelement."))
606      return true;
607    if (path.startsWith("do-uslab-uslabdo."))
608      return true;
609    if (path.startsWith("."))
610      return true;
611    if (path.startsWith("."))
612      return true;
613    if (path.startsWith("."))
614      return true;
615    if (path.startsWith("."))
616      return true;
617
618    return false;
619  }
620
621  public String getMessage() {
622    return message;
623  }
624  public ValidationMessage setMessage(String message) {
625    this.message = message;
626    return this;
627  }
628
629  public IssueSeverity getLevel() {
630    return level;
631  }
632  public ValidationMessage setLevel(IssueSeverity level) {
633    this.level = level;
634    return this;
635  }
636
637  public Source getSource() {
638    return source;
639  }
640  public ValidationMessage setSource(Source source) {
641    this.source = source;
642    return this;
643  }
644
645  public int getLine() {
646    return line;
647  }
648
649  public void setLine(int theLine) {
650    line = theLine;
651  }
652
653  public int getCol() {
654    return col;
655  }
656
657  public void setCol(int theCol) {
658    col = theCol;
659  }
660
661  public String getLocation() {
662    return location;
663  }
664  public ValidationMessage setLocation(String location) {
665    this.location = location;
666    return this;
667  }
668
669  public IssueType getType() {
670    return type;
671  }
672
673  public ValidationMessage setType(IssueType type) {
674    this.type = type;
675    return this;
676  }
677
678  public String summary() {
679    return level.toString()+" @ "+location+(line>= 0 && col >= 0 ? " (line "+Integer.toString(line)+", col"+Integer.toString(col)+"): " : ": ") +message +(source != null ? " (src = "+source+")" : "");
680  }
681
682
683  public String toXML() {
684    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>";
685  }
686
687  public String getHtml() {
688    return html == null ? Utilities.escapeXml(message) : html;
689  }
690
691  public String getDisplay() {
692    return level + ": " + (location==null || location.isEmpty() ? "" : (location + ": ")) + message;
693  }
694  
695  /**
696   * Returns a representation of this ValidationMessage suitable for logging. The values of
697   * most of the internal fields are included, so this may not be suitable for display to 
698   * an end user.
699   */
700  @Override
701  public String toString() {
702    ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
703    b.append("level", level);
704    b.append("type", type);
705    b.append("location", location);
706    b.append("message", message);
707    return b.build();
708  }
709
710  @Override
711  public boolean equals(Object o) {
712    return (this.getMessage() != null && this.getMessage().equals(((ValidationMessage)o).getMessage())) && (this.getLocation() != null && this.getLocation().equals(((ValidationMessage)o).getLocation()));
713  }
714
715  @Override
716  public int compare(ValidationMessage x, ValidationMessage y) {
717    String sx = x.getLevel().getDisplay() + x.getType().getDisplay() + String.format("%06d", x.getLine()) + x.getMessage();
718    String sy = y.getLevel().getDisplay() + y.getType().getDisplay() + String.format("%06d", y.getLine()) + y.getMessage();
719    return sx.compareTo(sy);
720  }  
721
722  @Override
723  public int compareTo(ValidationMessage y) {
724    return compare(this, y);
725  }
726
727  public String getLocationLink() {
728    return locationLink;
729  }
730
731  public ValidationMessage setLocationLink(String locationLink) {
732    this.locationLink = locationLink;
733    return this;
734  }
735
736  public String getTxLink() {
737    return txLink;
738  }
739
740  public ValidationMessage setTxLink(String txLink) {
741    this.txLink = txLink;
742    return this;
743  }
744
745  public void setHtml(String html) {
746    this.html = html;
747  }
748
749  
750}