001package org.hl7.fhir.r5.comparison;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import org.hl7.fhir.r5.model.CanonicalResource;
007import org.hl7.fhir.r5.model.CodeSystem;
008import org.hl7.fhir.r5.model.StructureDefinition;
009import org.hl7.fhir.r5.model.ValueSet;
010import org.hl7.fhir.utilities.Utilities;
011import org.hl7.fhir.utilities.validation.ValidationMessage;
012import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
013import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
014import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
015import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
016import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
017import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
018import org.hl7.fhir.utilities.xhtml.NodeType;
019import org.hl7.fhir.utilities.xhtml.XhtmlNode;
020
021public class ResourceComparer {
022 
023  public static class MessageCounts {
024    private int errors;
025    private int warnings;
026    private int hints;
027    public int getErrors() {
028      return errors;
029    }
030    public int getWarnings() {
031      return warnings;
032    }
033    public int getHints() {
034      return hints;
035    }
036    public void error() {
037      errors++;
038    }
039    public void warning() {
040      warnings++;
041    }
042    public void hint() {
043      hints++;
044    }
045  }
046
047
048  public static abstract class ResourceComparison {
049    private String id;
050    private String leftId;
051    private String rightId;
052    private MessageCounts cnts;
053    
054    public ResourceComparison(String leftId, String rightId) {
055      super();
056      this.leftId = leftId;
057      this.rightId = rightId;
058      id = abbreviation()+"-"+leftId + "-" + rightId;
059    }
060
061    protected String refCell(CanonicalResource cr) {
062      if (cr == null) {
063        return "<td></td>";
064      }
065      String t = cr.present();
066      if (Utilities.noString(t)) {
067        t = cr.getId();
068      }
069      if (cr.hasWebPath()) {
070        String p = cr.getWebPath();
071        return "<td><a href=\""+(Utilities.isAbsoluteUrl(p) ? "" : "../")+p+"\">"+Utilities.escapeXml(t)+"</td>";
072      } else 
073        return "<td>"+Utilities.escapeXml(t)+"</td>";
074    }
075    
076    protected abstract String abbreviation(); 
077
078    public String getLeftId() {
079      return leftId;
080    }
081
082    public String getRightId() {
083      return rightId;
084    }
085
086    protected List<ValidationMessage> messages = new ArrayList<>();
087    
088    public List<ValidationMessage> getMessages() {
089      return messages;
090    }
091
092    public String getId() {
093      return id;
094    }
095
096    protected abstract String summary();
097
098    protected abstract String fhirType();
099
100    protected abstract String toTable();
101    
102    protected String color() {
103      if (hasErrors()) {
104        return COLOR_DIFFERENT;
105      } else if (noChange()) {
106        return COLOR_NO_CHANGE;
107      } else { 
108        return COLOR_DIFFERENT_LESS;
109      }
110    }
111
112    protected boolean hasErrors() {
113      MessageCounts cnts = getCounts();
114      return cnts.getErrors() > 0;
115    }
116
117    protected boolean noChange() {
118      MessageCounts cnts = getCounts();
119      return cnts.getErrors() + cnts.getWarnings() + cnts.getHints() == 0;
120    }
121
122    protected String outcomeSummary() {
123      MessageCounts cnts = getCounts();
124      return 
125          Integer.toString(cnts.getErrors())+" "+Utilities.pluralize("Breaking Change", cnts.getErrors())+", "+
126          Integer.toString(cnts.getWarnings())+" "+Utilities.pluralize("Change", cnts.getWarnings())+", "+
127          Integer.toString(cnts.getHints())+" "+Utilities.pluralize("Note", cnts.getHints());
128    }
129
130    public MessageCounts getCounts() {
131      if (cnts == null) { 
132        cnts = new MessageCounts();
133        countMessages(cnts);
134      }
135      return cnts;
136    }
137
138    protected abstract void countMessages(MessageCounts cnts);
139  }
140  
141  
142  public static class PlaceHolderComparison extends ResourceComparison {
143    private CanonicalResource left;
144    private CanonicalResource right;
145    private Throwable e;
146
147    public PlaceHolderComparison(CanonicalResource left, CanonicalResource right) {
148      super(left == null ? right.getId() : left.getId(), right == null ? left.getId() : right.getId());
149      this.left = left;
150      this.right = right;
151    }
152
153    public PlaceHolderComparison(CanonicalResource left, CanonicalResource right, Throwable e) {
154      super(left == null ? right.getId() : left.getId(), right == null ? left.getId() : right.getId());
155      this.e = e;
156      this.left = left;
157      this.right = right;
158    }
159
160    @Override
161    protected String abbreviation() {
162      CanonicalResource cr = left == null ? right : left;
163      if (cr instanceof CodeSystem) {
164        return "cs";
165      } else if (cr instanceof ValueSet) {
166        return "vs";
167      } else if (cr instanceof StructureDefinition) {
168        return "sd";
169      } else {
170        return "xx";
171      }
172    }
173
174    @Override
175    protected String summary() {
176      if (e != null) {
177        return e.getMessage();
178      }
179      CanonicalResource cr = left == null ? right : left;
180      return cr.fhirType()+(left == null ? " Added" : " Removed");
181    }
182
183    @Override
184    protected String fhirType() {
185      CanonicalResource cr = left == null ? right : left;
186      return cr.fhirType();
187    }
188
189    @Override
190    protected String toTable() {
191      String s = "";
192      String color = null;
193      s = s + refCell(left);
194      s = s + refCell(right);
195      if (left == null) {
196        s = s + "<td>Added</td>";
197        color = COLOR_NO_ROW_LEFT;
198      } else if (right == null) {
199        s = s + "<td>Removed</td>";        
200        color = COLOR_NO_ROW_RIGHT;
201      } else {
202        s = s + "<td><a href=\""+getId()+".html\">Failed<a></td>";
203        color = COLOR_ISSUE;
204      }
205      s = s + "<td>"+(e != null ? Utilities.escapeXml(e.getMessage()) : "")+"</td>";
206      return "<tr style=\"background-color: "+color+"\">"+s+"</tr>\r\n";
207    }
208
209   
210
211    public Throwable getE() {
212      return e;
213    }
214
215    @Override
216    protected void countMessages(MessageCounts cnts) {
217      if (e != null) {
218        cnts.error();
219      }
220    }    
221    
222  }
223
224  public final static String COLOR_NO_ROW_LEFT = "#ffffb3";
225  public final static String COLOR_NO_CELL_LEFT = "#ffff4d";
226  public final static String COLOR_NO_ROW_RIGHT = "#ffecb3";
227  public final static String COLOR_NO_CELL_RIGHT = "#ffcc33";  
228  public final static String COLOR_DIFFERENT = "#f0b3ff";
229  public final static String COLOR_DIFFERENT_LESS = "#f8e6ff";
230  public final static String COLOR_ISSUE = "#ffad99";
231  public final static String COLOR_NO_CHANGE = "#ffffff";
232
233  protected ComparisonSession session;
234  
235  public ResourceComparer(ComparisonSession session) {
236    super();
237    this.session = session;
238  }
239
240  public Cell missingCell(HierarchicalTableGenerator gen) {
241    Cell c = gen.new Cell(null, null, "", null, null);
242    return c;
243  }
244
245  public Cell missingCell(HierarchicalTableGenerator gen, String color) {
246    Cell c = gen.new Cell(null, null, "", null, null);
247    if (color != null) {
248      c.setStyle("background-color: "+color);
249    }
250    return c;
251  }
252
253  public XhtmlNode renderErrors(ResourceComparison csc) {
254    XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
255    XhtmlNode tbl = div.table("grid");
256    for (ValidationMessage vm : csc.messages) {
257      XhtmlNode tr = tbl.tr();
258      tr.style("background-color: "+colorForLevel(vm.getLevel()));
259      tr.td().tx(vm.getLevel().getDisplay());
260      tr.td().tx(vm.getLocation());
261      tr.td().tx(vm.getMessage().replace("\"", "'"));
262    }
263    return div;
264  }
265
266
267  protected ValidationMessage vmI(IssueSeverity level, String message, String path) {
268    ValidationMessage vm = new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, message, level == IssueSeverity.NULL ? IssueSeverity.INFORMATION : level);
269    return vm;    
270  }
271  
272  protected void vm(IssueSeverity level, String message, String path, List<ValidationMessage> genMessages, List<ValidationMessage> specMessages) {
273    ValidationMessage vm = new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, message, level == IssueSeverity.NULL ? IssueSeverity.INFORMATION : level);
274    genMessages.add(vm);
275    if (specMessages != null) {
276      specMessages.add(vm);
277    }
278  }
279
280  protected ValidationMessage vm(IssueSeverity level, String message, String path, List<ValidationMessage> genMessages) {
281    ValidationMessage vm = new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, message, level == IssueSeverity.NULL ? IssueSeverity.INFORMATION : level);
282    genMessages.add(vm);
283    return vm;
284  }
285  
286  private String colorForLevel(IssueSeverity level) {
287    switch (level) {
288    case ERROR:
289      return "#ffcccc";
290    case FATAL:
291      return "#ff9999";
292    case WARNING:
293      return "#ffebcc";
294    default: // INFORMATION:
295      return "#ffffe6";
296    }
297  }
298
299  private String halfColorForLevel(IssueSeverity level) {
300    switch (level) {
301    case ERROR:
302      return "#ffdddd";
303    case FATAL:
304      return "#ffcccc";
305    case WARNING:
306      return "#fff6ee";
307    default: // INFORMATION:
308      return "#fffff2";
309    }
310  }
311
312  protected Cell cellForMessages(HierarchicalTableGenerator gen, List<ValidationMessage> messages) {
313    Cell cell = gen.new Cell();
314    Piece piece = gen.new Piece("ul");
315    cell.addPiece(piece);
316    for (ValidationMessage msg : messages) {
317      XhtmlNode li = new XhtmlNode(NodeType.Element, "li");
318      piece.getChildren().add(li);
319      li.style("background-color: "+halfColorForLevel(msg.getLevel()));
320      if (msg.getLevel() == IssueSeverity.ERROR) {
321        li.style("font-weight: bold");
322      }
323      li.tx(msg.getMessage());
324    }
325    return cell;
326  }
327 
328
329
330}