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}