001package org.hl7.fhir.r5.terminologies.utilities;
002
003import java.util.*;
004
005import org.hl7.fhir.r5.model.CodeableConcept;
006import org.hl7.fhir.r5.model.Coding;
007import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
008import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
009import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
010import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
011
012public class ValidationResult {
013  private ConceptDefinitionComponent definition;
014  private String preferredDisplay;
015  private String system;
016  private String version;
017  private IssueSeverity severity;
018  private List<String> messages = new ArrayList<>();
019  private TerminologyServiceErrorClass errorClass;
020  private String txLink;
021  private String diagnostics;
022  private List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
023  private CodeableConcept codeableConcept;
024  private Set<String> unknownSystems;
025  private boolean inactive;
026  private String status;
027  private String server;
028  
029  @Override
030  public String toString() {
031    return "ValidationResult [definition=" + definition + ", system=" + system + ", severity=" + severity + ", message=" + getMessage() + ", errorClass="
032        + errorClass + ", txLink=" + txLink + "]";
033  }
034
035  public ValidationResult(ValidationResult validationResult) {
036    this.definition = validationResult.definition == null ? null : validationResult.definition.copy();
037    this.preferredDisplay = validationResult.preferredDisplay;
038    this.system = validationResult.system;
039    this.version = validationResult.version;
040    this.severity = validationResult.severity;
041    if (validationResult.messages != null) {
042      this.messages.addAll(validationResult.messages);
043    }
044    this.errorClass = validationResult.errorClass;
045    this.txLink = validationResult.txLink;
046    this.diagnostics = validationResult.diagnostics;
047    if (validationResult.issues != null) {
048      for (OperationOutcomeIssueComponent issue : validationResult.issues) {
049        this.issues.add(issue.copy());
050      }
051    }
052    this.codeableConcept = validationResult.codeableConcept == null ? null : validationResult.codeableConcept.copy();
053    this.unknownSystems = validationResult.unknownSystems == null ? null : new HashSet<>(validationResult.unknownSystems);
054    this.inactive = validationResult.inactive;
055    this.status = validationResult.status;
056    this.server = validationResult.server;
057  }
058
059  public ValidationResult(IssueSeverity severity, String message, List<OperationOutcomeIssueComponent> issues) {
060    this.severity = severity;
061    if (message != null) {
062      this.messages.add(message);
063    }
064    if (issues != null) {
065      this.issues.addAll(issues);
066    }
067  }
068
069  public ValidationResult(String system, String version, ConceptDefinitionComponent definition, String preferredDisplay) {
070    this.system = system;
071    this.version = version;
072    this.definition = definition;
073    this.preferredDisplay = preferredDisplay;
074  }
075
076  public ValidationResult(IssueSeverity severity, String message, String system, String version, ConceptDefinitionComponent definition, String preferredDisplay, List<OperationOutcomeIssueComponent>  issues) {
077    this.severity = severity;
078    if (message != null) {
079      this.messages.add(message);
080    }
081    this.system = system;
082    this.version = version;
083    this.definition = definition;
084    this.preferredDisplay = preferredDisplay;
085    if (issues != null) {
086      this.issues.addAll(issues);
087    }
088  }
089  public ValidationResult(IssueSeverity severity, List<String> messages, String system, String version, ConceptDefinitionComponent definition, String preferredDisplay, List<OperationOutcomeIssueComponent>  issues) {
090    this.severity = severity;
091    this.messages.addAll(messages);
092    this.system = system;
093    this.version = version;
094    this.definition = definition;
095    this.preferredDisplay = preferredDisplay;
096    if (issues != null) {
097      this.issues.addAll(issues);
098    }
099  }
100
101  public ValidationResult(IssueSeverity severity, String message, TerminologyServiceErrorClass errorClass, List<OperationOutcomeIssueComponent>  issues) {
102    this.severity = severity;
103    if (message != null) {
104      this.messages.add(message);
105    }
106    this.errorClass = errorClass;
107    if (issues != null) {
108      this.issues.addAll(issues);
109    }
110  }
111
112  public boolean isOk() {
113    return severity == null || severity == IssueSeverity.INFORMATION || severity == IssueSeverity.WARNING;
114  }
115
116  public String getSystem() {
117    return system;
118  }
119
120  public String getVersion() {
121    return version;
122  }
123
124  public String getDisplay() {
125    if (preferredDisplay != null) {
126      return preferredDisplay; 
127    } else {
128      return definition == null ? null : definition.getDisplay();
129    }
130  }
131
132  public void setDisplay(String display) {
133    this.preferredDisplay = display;
134  }
135
136  public void setSystem(String system) {
137    this.system = system;
138  }
139
140  public void setVersion(String version) {
141    this.version = version;
142  }
143
144  public String getCode() {
145    return definition == null ? null : definition.getCode();
146  }
147
148  public String getDefinition() {
149    return definition == null ? null : definition.getDefinition();
150  }
151
152  public void setDefinition(ConceptDefinitionComponent definition) {
153    this.definition = definition;
154  }
155
156  public ConceptDefinitionComponent asConceptDefinition() {
157    return definition;
158  }
159
160  public IssueSeverity getSeverity() {
161    return severity;
162  }
163
164  public String getMessage() {
165    if (messages.size() == 0) {
166      return null;
167    }
168    Collections.sort(messages);
169    return CommaSeparatedStringBuilder.join("; ", messages);
170  }
171
172  public String getTrimmedMessage() {
173    List<String> toTrim = new ArrayList<>();
174    for (OperationOutcomeIssueComponent iss : getIssues()) {
175      toTrim.add(iss.getDetails().getText());
176    }
177    List<String> trimmed = new ArrayList<>();
178    trimmed.addAll(messages);
179    trimmed.removeAll(toTrim);
180    if (trimmed.size() == 0) {
181      return null;
182    }       
183    Collections.sort(trimmed);
184    return CommaSeparatedStringBuilder.join("; ", trimmed);
185  }
186
187  public boolean IsNoService() {
188    return errorClass == TerminologyServiceErrorClass.NOSERVICE;
189  }
190
191  public TerminologyServiceErrorClass getErrorClass() {
192    return errorClass;
193  }
194
195  public ValidationResult setSeverity(IssueSeverity severity) {
196    this.severity = severity;
197    return this;
198  }
199
200  public ValidationResult setMessage(String message) {
201    this.messages.clear();
202    if (message != null) {
203      this.messages.add(message);
204    }
205    return this;
206  }
207  
208  public ValidationResult addMessage(String message) {
209    if (message != null) {
210      this.messages.add(message);
211    }
212    return this;
213  }
214  
215  public ValidationResult setErrorClass(TerminologyServiceErrorClass errorClass) {
216    this.errorClass = errorClass;
217    return this;
218  }
219
220  public String getTxLink() {
221    return txLink;
222  }
223
224  public ValidationResult setTxLink(String txLink) {
225    this.txLink = txLink;
226    return this;
227  }
228
229  public boolean hasMessage() {
230    return !messages.isEmpty();
231  }
232
233  public String getDiagnostics() {
234    return diagnostics;
235  }
236
237  public void setDiagnostics(String diagnostics) {
238    this.diagnostics = diagnostics;
239  }
240
241  public Coding asCoding() {
242    if (isOk() && definition != null && definition.getCode() != null) {
243      return new Coding(system, definition.getCode(), definition.getDisplay());
244    } else {
245      return null;
246    }
247  }
248
249  public List<OperationOutcomeIssueComponent> getIssues() {
250    return issues;
251  }
252
253  public ValidationResult addCodeableConcept(CodeableConcept vcc) {
254    if (!vcc.isEmpty()) {
255      codeableConcept = vcc;
256    }
257    return this;
258  }
259
260  public CodeableConcept getCodeableConcept() {
261    return codeableConcept;
262  }
263
264  public Set<String> getUnknownSystems() {
265    return unknownSystems;
266  }
267
268  public ValidationResult setUnknownSystems(Set<String> unknownSystems) {
269    this.unknownSystems = unknownSystems;
270    return this;
271  }
272
273  public String unknownSystems() {
274    if (unknownSystems == null) {
275      return null;
276    }
277    if (unknownSystems.size() == 1) {
278      return unknownSystems.iterator().next();        
279    } else {
280      return String.join(",", unknownSystems);
281    }
282  }
283
284  public void setIssues(List<OperationOutcomeIssueComponent> issues) {
285    if (this.issues != null) {
286      issues.addAll(this.issues);
287    }
288    this.issues = issues;
289    
290  }
291
292  public void trimPath(String prefix) {
293    if (issues != null) {
294      for (OperationOutcomeIssueComponent iss : issues) {
295        for (int i = iss.getLocation().size() -1; i >= 0; i--) {
296          var s = iss.getLocation().get(i).primitiveValue();
297          if (prefix.equals(s)) {
298            iss.getLocation().remove(i);
299          } else if (s.startsWith(prefix+".")) {
300            iss.getLocation().get(i).setValueAsString(s.substring(prefix.length()+1));                
301          }            
302        }
303        for (int i = iss.getExpression().size() -1; i >= 0; i--) {
304          var s = iss.getExpression().get(i).primitiveValue();
305          if (prefix.equals(s)) {
306            iss.getExpression().remove(i);
307          } else if (s.startsWith(prefix+".")) {
308            iss.getExpression().get(i).setValueAsString(s.substring(prefix.length()+1));                
309          }            
310        }
311      }
312    }      
313    
314  }
315
316  public boolean isInactive() {
317    return inactive;
318  }
319
320  public String getStatus() {
321    return status;
322  }
323
324  public ValidationResult setStatus(boolean inactive, String status) {
325    this.inactive = inactive;
326    if (!"inactive".equals(status)) {
327      this.status = status;
328    }
329    return this;
330  }
331
332  public boolean messageIsInIssues() {
333    // the message is sometimes a summary of the issues that are already tracked. 
334    // this returns true in that case, so that duplication of messages is suppressed
335    
336    for (String s : messages) {
337      boolean found = false;
338      for (OperationOutcomeIssueComponent iss : issues) {
339        if (iss.getSeverity().ordinal() <= getSeverity().ordinal() && s.equals(iss.getDetails().getText())) {
340          found = true;
341        }
342      }
343      if (!found) {
344        return false;
345      }
346    }
347    return true;
348  }
349
350  public String getServer() {
351    return server;
352  }
353
354  public void setServer(String server) {
355    this.server = server;
356  }
357
358  public boolean equals(Object otherObject) {
359    if (!(otherObject instanceof ValidationResult)) {
360      return false;
361    }
362
363    ValidationResult other = (ValidationResult) otherObject;
364    if (!Objects.equals(this.system, other.system)) {
365      return false;
366    }
367    if (!Objects.equals(this.version, other.version)) {
368      return false;
369    }
370    if (!Objects.equals(this.preferredDisplay, other.preferredDisplay)) {
371      return false;
372    }
373    if (!Objects.equals(this.severity, other.severity)) {
374      return false;
375    }
376    if (!Objects.equals(this.definition, other.definition)) {
377      return false;
378    }
379    if (!Objects.equals(this.messages, other.messages)) {
380      return false;
381    }
382    if (!Objects.equals(this.errorClass, other.errorClass)) {
383      return false;
384    }
385    if (!Objects.equals(this.txLink, other.txLink)) {
386      return false;
387    }
388    if (!Objects.equals(this.diagnostics, other.diagnostics)) {
389      return false;
390    }
391    if (!Objects.equals(this.issues, other.issues)) {
392      return false;
393    }
394    if (!Objects.equals(this.codeableConcept, other.codeableConcept)) {
395      return false;
396    }
397    if (!Objects.equals(this.unknownSystems, other.unknownSystems)) {
398      return false;
399    }
400    if (this.inactive != other.inactive) {
401      return false;
402    }
403    if (!Objects.equals(this.status, other.status)) {
404      return false;
405    }
406    if (!Objects.equals(this.server, other.server)) {
407      return false;
408    }
409    return true;
410  }
411}