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