
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.MarkedToMoveToAdjunctPackage; 011import org.hl7.fhir.utilities.Utilities; 012import org.hl7.fhir.utilities.validation.ValidationMessage; 013import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 014import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 015import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 016import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 017import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 018import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 019import org.hl7.fhir.utilities.xhtml.NodeType; 020import org.hl7.fhir.utilities.xhtml.XhtmlNode; 021 022@MarkedToMoveToAdjunctPackage 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.hasWebPath()) { 072 String p = cr.getWebPath(); 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 colspan=\"3\">"+(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", false); 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 331 332}