
001package org.hl7.fhir.r5.comparison; 002 003import java.io.IOException; 004import java.util.ArrayList; 005import java.util.Collections; 006import java.util.HashMap; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010 011import org.hl7.fhir.exceptions.FHIRException; 012import org.hl7.fhir.r5.model.Base; 013import org.hl7.fhir.r5.model.CanonicalResource; 014import org.hl7.fhir.r5.model.CanonicalType; 015import org.hl7.fhir.r5.model.CodeType; 016import org.hl7.fhir.r5.model.CodeableConcept; 017import org.hl7.fhir.r5.model.Coding; 018import org.hl7.fhir.r5.model.DataType; 019import org.hl7.fhir.r5.model.PrimitiveType; 020import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 021import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 022import org.hl7.fhir.utilities.Utilities; 023import org.hl7.fhir.utilities.i18n.RenderingI18nContext; 024import org.hl7.fhir.utilities.validation.ValidationMessage; 025import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 026import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 027import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 028import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 029import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 030import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 031import org.hl7.fhir.utilities.xhtml.XhtmlNode; 032 033@MarkedToMoveToAdjunctPackage 034public abstract class CanonicalResourceComparer extends ResourceComparer { 035 036 037 public enum ChangeAnalysisState { 038 Unknown, NotChanged, Changed, CannotEvaluate; 039 040 boolean noteable() { 041 return this == Changed || this == CannotEvaluate; 042 } 043 } 044 045 046 public abstract class CanonicalResourceComparison<T extends CanonicalResource> extends ResourceComparison { 047 protected T left; 048 protected T right; 049 protected T union; 050 protected T intersection; 051 052 private ChangeAnalysisState changedMetadata = ChangeAnalysisState.Unknown; 053 private ChangeAnalysisState changedDefinitions = ChangeAnalysisState.Unknown; 054 private ChangeAnalysisState changedContent = ChangeAnalysisState.Unknown; 055 private ChangeAnalysisState changedContentInterpretation = ChangeAnalysisState.Unknown; 056 057 protected Map<String, StructuralMatch<String>> metadata = new HashMap<>(); 058 private List<String> chMetadataFields; 059 060 public CanonicalResourceComparison(T left, T right) { 061 super(left.getId(), right.getId()); 062 this.left = left; 063 this.right = right; 064 } 065 066 public T getLeft() { 067 return left; 068 } 069 070 public T getRight() { 071 return right; 072 } 073 074 public T getUnion() { 075 return union; 076 } 077 078 public T getIntersection() { 079 return intersection; 080 } 081 082 public Map<String, StructuralMatch<String>> getMetadata() { 083 return metadata; 084 } 085 086 public void setLeft(T left) { 087 this.left = left; 088 } 089 090 public void setRight(T right) { 091 this.right = right; 092 } 093 094 public void setUnion(T union) { 095 this.union = union; 096 } 097 098 public void setIntersection(T intersection) { 099 this.intersection = intersection; 100 } 101 102 private ChangeAnalysisState updateState(ChangeAnalysisState newState, ChangeAnalysisState oldState) { 103 switch (newState) { 104 case CannotEvaluate: 105 return ChangeAnalysisState.CannotEvaluate; 106 case Changed: 107 if (oldState != ChangeAnalysisState.CannotEvaluate) { 108 return ChangeAnalysisState.Changed; 109 } 110 break; 111 case NotChanged: 112 if (oldState == ChangeAnalysisState.Unknown) { 113 return ChangeAnalysisState.NotChanged; 114 } 115 break; 116 case Unknown: 117 default: 118 break; 119 } 120 return oldState; 121 } 122 123 public void updatedMetadataState(ChangeAnalysisState state) { 124 changedMetadata = updateState(state, changedMetadata); 125 } 126 127 public void updateDefinitionsState(ChangeAnalysisState state) { 128 changedDefinitions = updateState(state, changedDefinitions); 129 } 130 131 public void updateContentState(ChangeAnalysisState state) { 132 changedContent = updateState(state, changedContent); 133 } 134 135 public void updateContentInterpretationState(ChangeAnalysisState state) { 136 changedContentInterpretation = updateState(state, changedContentInterpretation); 137 } 138 139 public void updatedMetadataState(boolean changed, List<String> chMetadataFields) { 140 changedMetadata = updateState(changed ? ChangeAnalysisState.Changed : ChangeAnalysisState.NotChanged, changedMetadata); 141 this.chMetadataFields = chMetadataFields; 142 } 143 144 public void updateDefinitionsState(boolean changed) { 145 changedDefinitions = updateState(changed ? ChangeAnalysisState.Changed : ChangeAnalysisState.NotChanged, changedDefinitions); 146 } 147 148 public void updateContentState(boolean changed) { 149 changedContent = updateState(changed ? ChangeAnalysisState.Changed : ChangeAnalysisState.NotChanged, changedContent); 150 } 151 152 public void updateContentInterpretationState(boolean changed) { 153 changedContentInterpretation = updateState(changed ? ChangeAnalysisState.Changed : ChangeAnalysisState.NotChanged, changedContentInterpretation); 154 } 155 156 public boolean anyUpdates() { 157 return changedMetadata.noteable() || changedDefinitions.noteable() || changedContent.noteable() || changedContentInterpretation.noteable(); 158 } 159 160 161 public ChangeAnalysisState getChangedMetadata() { 162 return changedMetadata; 163 } 164 165 public ChangeAnalysisState getChangedDefinitions() { 166 return changedDefinitions; 167 } 168 169 public ChangeAnalysisState getChangedContent() { 170 return changedContent; 171 } 172 173 public ChangeAnalysisState getChangedContentInterpretation() { 174 return changedContentInterpretation; 175 } 176 177 @Override 178 protected String toTable() { 179 String s = ""; 180 s = s + refCell(left); 181 s = s + refCell(right); 182 s = s + "<td><a href=\""+getId()+".html\">Comparison</a></td>"; 183 s = s + "<td><a href=\""+getId()+"-union.html\">Union</a></td>"; 184 s = s + "<td><a href=\""+getId()+"-intersection.html\">Intersection</a></td>"; 185 s = s + "<td>"+outcomeSummary()+"</td>"; 186 return "<tr style=\"background-color: "+color()+"\">"+s+"</tr>\r\n"; 187 } 188 189 @Override 190 protected void countMessages(MessageCounts cnts) { 191 for (StructuralMatch<String> sm : metadata.values()) { 192 sm.countMessages(cnts); 193 } 194 } 195 196 protected String changeSummary() { 197 if (!(changedMetadata.noteable() || changedDefinitions.noteable() || changedContent.noteable() || changedContentInterpretation.noteable())) { 198 return null; 199 }; 200 CommaSeparatedStringBuilder bc = new CommaSeparatedStringBuilder(); 201 if (changedMetadata == ChangeAnalysisState.CannotEvaluate) { 202 bc.append("Metadata"); 203 } 204 if (changedDefinitions == ChangeAnalysisState.CannotEvaluate) { 205 bc.append("Definitions"); 206 } 207 if (changedContent == ChangeAnalysisState.CannotEvaluate) { 208 bc.append("Content"); 209 } 210 if (changedContentInterpretation == ChangeAnalysisState.CannotEvaluate) { 211 bc.append("Interpretation"); 212 } 213 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 214 if (changedMetadata == ChangeAnalysisState.Changed) { 215 b.append("Metadata"); 216 } 217 if (changedDefinitions == ChangeAnalysisState.Changed) { 218 b.append("Definitions"); 219 } 220 if (changedContent == ChangeAnalysisState.Changed) { 221 b.append("Content"); 222 } 223 if (changedContentInterpretation == ChangeAnalysisState.Changed) { 224 b.append("Interpretation"); 225 } 226 return (bc.length() == 0 ? "" : "Error Checking: "+bc.toString()+"; ")+ "Changed: "+b.toString(); 227 } 228 229 public String getMetadataFieldsAsText() { 230 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 231 if (chMetadataFields != null) { 232 for (String s : chMetadataFields) { 233 b.append(s); 234 } 235 } 236 return b.toString(); 237 } 238 239 public boolean noUpdates() { 240 return !(changedMetadata.noteable() || changedDefinitions.noteable() || !changedContent.noteable() || !changedContentInterpretation.noteable()); 241 } 242 243 public boolean noChangeOtherThanMetadata(String[] metadataFields) { 244 if (changedDefinitions.noteable() || changedContent.noteable() || changedContentInterpretation.noteable()) { 245 return false; 246 } 247 if (!changedMetadata.noteable()) { 248 return true; 249 } 250 for (String s : this.chMetadataFields) { 251 if (!Utilities.existsInList(s, metadataFields)) { 252 return false; 253 } 254 } 255 return true; 256 } 257 } 258 259 public CanonicalResourceComparer(ComparisonSession session) { 260 super(session); 261 } 262 263 protected boolean compareMetadata(CanonicalResource left, CanonicalResource right, Map<String, StructuralMatch<String>> comp, CanonicalResourceComparison<? extends CanonicalResource> res, List<String> changes, Base parent) { 264 var changed = false; 265 if (comparePrimitivesWithTracking("url", left.getUrlElement(), right.getUrlElement(), comp, IssueSeverity.ERROR, res, parent)) { 266 changed = true; 267 changes.add("url"); 268 } 269 if (!session.isAnnotate()) { 270 if (comparePrimitivesWithTracking("version", left.getVersionElement(), right.getVersionElement(), comp, IssueSeverity.ERROR, res, parent)) { 271 changed = true; 272 changes.add("version"); 273 } 274 } 275 if (comparePrimitivesWithTracking("name", left.getNameElement(), right.getNameElement(), comp, IssueSeverity.INFORMATION, res, parent)) { 276 changed = true; 277 changes.add("name"); 278 } 279 if (comparePrimitivesWithTracking("title", left.getTitleElement(), right.getTitleElement(), comp, IssueSeverity.INFORMATION, res, parent)) { 280 changed = true; 281 changes.add("title"); 282 } 283 if (comparePrimitivesWithTracking("status", left.getStatusElement(), right.getStatusElement(), comp, IssueSeverity.INFORMATION, res, parent)) { 284 changed = true; 285 changes.add("status"); 286 } 287 if (comparePrimitivesWithTracking("experimental", left.getExperimentalElement(), right.getExperimentalElement(), comp, IssueSeverity.WARNING, res, parent)) { 288 changed = true; 289 changes.add("experimental"); 290 } 291 if (!session.isAnnotate()) { 292 if (comparePrimitivesWithTracking("date", left.getDateElement(), right.getDateElement(), comp, IssueSeverity.INFORMATION, res, parent)) { 293 changed = true; 294 changes.add("date"); 295 } 296 } 297 if (comparePrimitivesWithTracking("publisher", left.getPublisherElement(), right.getPublisherElement(), comp, IssueSeverity.INFORMATION, res, parent)) { 298 changed = true; 299 changes.add("publisher"); 300 } 301 if (comparePrimitivesWithTracking("description", left.getDescriptionElement(), right.getDescriptionElement(), comp, IssueSeverity.NULL, res, parent)) { 302 changed = true; 303 changes.add("description"); 304 } 305 if (comparePrimitivesWithTracking("purpose", left.getPurposeElement(), right.getPurposeElement(), comp, IssueSeverity.NULL, res, parent)) { 306 changed = true; 307 changes.add("purpose"); 308 } 309 if (comparePrimitivesWithTracking("copyright", left.getCopyrightElement(), right.getCopyrightElement(), comp, IssueSeverity.INFORMATION, res, parent)) { 310 changed = true; 311 changes.add("copyright"); 312 } 313 if (compareCodeableConceptList("jurisdiction", left.getJurisdiction(), right.getJurisdiction(), comp, IssueSeverity.INFORMATION, res, res.getUnion().getJurisdiction(), res.getIntersection().getJurisdiction())) { 314 changed = true; 315 changes.add("jurisdiction"); 316 } 317 return changed; 318 } 319 320 protected boolean compareCodeableConceptList(String name, List<CodeableConcept> left, List<CodeableConcept> right, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, List<CodeableConcept> union, List<CodeableConcept> intersection ) { 321 boolean result = false; 322 List<CodeableConcept> matchR = new ArrayList<>(); 323 StructuralMatch<String> combined = new StructuralMatch<String>(); 324 for (CodeableConcept l : left) { 325 CodeableConcept r = findCodeableConceptInList(right, l); 326 if (r == null) { 327 union.add(l); 328 result = true; 329 combined.getChildren().add(new StructuralMatch<String>(gen(l), vm(IssueSeverity.INFORMATION, "Removed the item '"+gen(l)+"'", fhirType()+"."+name, res.getMessages()))); 330 } else { 331 matchR.add(r); 332 union.add(r); 333 intersection.add(r); 334 StructuralMatch<String> sm = new StructuralMatch<String>(gen(l), gen(r)); 335 combined.getChildren().add(sm); 336 if (sm.isDifferent()) { 337 result = true; 338 } 339 } 340 } 341 for (CodeableConcept r : right) { 342 if (!matchR.contains(r)) { 343 union.add(r); 344 result = true; 345 combined.getChildren().add(new StructuralMatch<String>(vm(IssueSeverity.INFORMATION, "Added the item '"+gen(r)+"'", fhirType()+"."+name, res.getMessages()), gen(r))); 346 } 347 } 348 comp.put(name, combined); 349 return result; 350 } 351 352 353 private CodeableConcept findCodeableConceptInList(List<CodeableConcept> list, CodeableConcept item) { 354 for (CodeableConcept t : list) { 355 if (t.matches(item)) { 356 return t; 357 } 358 } 359 return null; 360 } 361 362 protected String gen(CodeableConcept cc) { 363 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 364 for (Coding c : cc.getCoding()) { 365 b.append(gen(c)); 366 } 367 return b.toString(); 368 } 369 370 protected String gen(Coding c) { 371 return c.getSystem()+(c.hasVersion() ? "|"+c.getVersion() : "")+"#"+c.getCode(); 372 } 373 374 protected void compareCanonicalList(String name, List<CanonicalType> left, List<CanonicalType> right, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, List<CanonicalType> union, List<CanonicalType> intersection ) { 375 List<CanonicalType> matchR = new ArrayList<>(); 376 StructuralMatch<String> combined = new StructuralMatch<String>(); 377 for (CanonicalType l : left) { 378 CanonicalType r = findCanonicalInList(right, l); 379 if (r == null) { 380 union.add(l); 381 combined.getChildren().add(new StructuralMatch<String>(l.getValue(), vm(IssueSeverity.INFORMATION, "Removed the item '"+l.getValue()+"'", fhirType()+"."+name, res.getMessages()))); 382 } else { 383 matchR.add(r); 384 union.add(r); 385 intersection.add(r); 386 StructuralMatch<String> sm = new StructuralMatch<String>(l.getValue(), r.getValue()); 387 combined.getChildren().add(sm); 388 } 389 } 390 for (CanonicalType r : right) { 391 if (!matchR.contains(r)) { 392 union.add(r); 393 combined.getChildren().add(new StructuralMatch<String>(vm(IssueSeverity.INFORMATION, "Added the item '"+r.getValue()+"'", fhirType()+"."+name, res.getMessages()), r.getValue())); 394 } 395 } 396 comp.put(name, combined); 397 } 398 399 private CanonicalType findCanonicalInList(List<CanonicalType> list, CanonicalType item) { 400 for (CanonicalType t : list) { 401 if (t.getValue().equals(item.getValue())) { 402 return t; 403 } 404 } 405 return null; 406 } 407 408 protected void compareCodeList(String name, List<CodeType> left, List<CodeType> right, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, List<CodeType> union, List<CodeType> intersection ) { 409 List<CodeType> matchR = new ArrayList<>(); 410 StructuralMatch<String> combined = new StructuralMatch<String>(); 411 for (CodeType l : left) { 412 CodeType r = findCodeInList(right, l); 413 if (r == null) { 414 union.add(l); 415 combined.getChildren().add(new StructuralMatch<String>(l.getValue(), vm(IssueSeverity.INFORMATION, "Removed the item '"+l.getValue()+"'", fhirType()+"."+name, res.getMessages()))); 416 } else { 417 matchR.add(r); 418 union.add(r); 419 intersection.add(r); 420 StructuralMatch<String> sm = new StructuralMatch<String>(l.getValue(), r.getValue()); 421 combined.getChildren().add(sm); 422 } 423 } 424 for (CodeType r : right) { 425 if (!matchR.contains(r)) { 426 union.add(r); 427 combined.getChildren().add(new StructuralMatch<String>(vm(IssueSeverity.INFORMATION, "Added the item '"+r.getValue()+"'", fhirType()+"."+name, res.getMessages()), r.getValue())); 428 } 429 } 430 comp.put(name, combined); 431 } 432 433 private CodeType findCodeInList(List<CodeType> list, CodeType item) { 434 for (CodeType t : list) { 435 if (t.getValue().equals(item.getValue())) { 436 return t; 437 } 438 } 439 return null; 440 } 441 442 @SuppressWarnings("rawtypes") 443 protected boolean comparePrimitives(String name, PrimitiveType l, PrimitiveType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res) { 444 StructuralMatch<String> match = null; 445 if (l.isEmpty() && r.isEmpty()) { 446 match = new StructuralMatch<>(null, null, null); 447 } else if (l.isEmpty()) { 448 match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "Added the item '"+r.primitiveValue()+"'", fhirType()+"."+name)); 449 } else if (r.isEmpty()) { 450 match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "Removed the item '"+l.primitiveValue()+"'", fhirType()+"."+name)); 451 } else if (!l.hasValue() && !r.hasValue()) { 452 match = new StructuralMatch<>(null, null, vmI(IssueSeverity.INFORMATION, "No Value", fhirType()+"."+name)); 453 } else if (!l.hasValue()) { 454 match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "No Value on Left", fhirType()+"."+name)); 455 } else if (!r.hasValue()) { 456 match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "No Value on Right", fhirType()+"."+name)); 457 } else if (l.getValue().equals(r.getValue())) { 458 match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), null); 459 } else { 460 match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), vmI(level, "Values Differ", fhirType()+"."+name)); 461 if (level != IssueSeverity.NULL) { 462 res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.primitiveValue()+"' vs '"+r.primitiveValue()+"'", level)); 463 } 464 } 465 comp.put(name, match); 466 return match.isDifferent(); 467 } 468 469 470 protected boolean comparePrimitivesWithTracking(String name, List< ? extends PrimitiveType> ll, List<? extends PrimitiveType> rl, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, Base parent) { 471 boolean def = false; 472 473 List<PrimitiveType> matchR = new ArrayList<>(); 474 for (PrimitiveType l : ll) { 475 PrimitiveType r = findInList(rl, l); 476 if (r == null) { 477 session.markDeleted(parent, "element", l); 478 } else { 479 matchR.add(r); 480 def = comparePrimitivesWithTracking(name, l, r, comp, level, res, parent) || def; 481 } 482 } 483 for (PrimitiveType r : rl) { 484 if (!matchR.contains(r)) { 485 session.markAdded(r); 486 } 487 } 488 return def; 489 } 490 491 private PrimitiveType findInList(List<? extends PrimitiveType> rl, PrimitiveType l) { 492 for (PrimitiveType r : rl) { 493 if (r.equalsDeep(l)) { 494 return r; 495 } 496 } 497 return null; 498 } 499 500 @SuppressWarnings("rawtypes") 501 protected boolean comparePrimitivesWithTracking(String name, PrimitiveType l, PrimitiveType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, Base parent) { 502 StructuralMatch<String> match = null; 503 if (l.isEmpty() && r.isEmpty()) { 504 match = new StructuralMatch<>(null, null, null); 505 } else if (l.isEmpty()) { 506 match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "Added the item '"+r.primitiveValue()+"'", fhirType()+"."+name)); 507 session.markAdded(r); 508 } else if (r.isEmpty()) { 509 match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "Removed the item '"+l.primitiveValue()+"'", fhirType()+"."+name)); 510 session.markDeleted(parent, name, l); 511 } else if (!l.hasValue() && !r.hasValue()) { 512 match = new StructuralMatch<>(null, null, vmI(IssueSeverity.INFORMATION, "No Value", fhirType()+"."+name)); 513 } else if (!l.hasValue()) { 514 match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "No Value on Left", fhirType()+"."+name)); 515 session.markAdded(r); 516 } else if (!r.hasValue()) { 517 match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "No Value on Right", fhirType()+"."+name)); 518 session.markDeleted(parent, name, l); 519 } else if (l.getValue().equals(r.getValue())) { 520 match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), null); 521 } else { 522 session.markChanged(r, l); 523 match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), vmI(level, "Values Differ", fhirType()+"."+name)); 524 if (level != IssueSeverity.NULL && res != null) { 525 res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.primitiveValue()+"' vs '"+r.primitiveValue()+"'", level)); 526 } 527 } 528 if (comp != null) { 529 comp.put(name, match); 530 } 531 return match.isDifferent(); 532 } 533 534 535 protected boolean compareDataTypesWithTracking(String name, List< ? extends DataType> ll, List<? extends DataType> rl, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, Base parent) { 536 boolean def = false; 537 538 List<DataType> matchR = new ArrayList<>(); 539 for (DataType l : ll) { 540 DataType r = findInList(rl, l); 541 if (r == null) { 542 session.markDeleted(parent, "element", l); 543 } else { 544 matchR.add(r); 545 def = compareDataTypesWithTracking(name, l, r, comp, level, res, parent) || def; 546 } 547 } 548 for (DataType r : rl) { 549 if (!matchR.contains(r)) { 550 session.markAdded(r); 551 } 552 } 553 return def; 554 } 555 556 private DataType findInList(List<? extends DataType> rl, DataType l) { 557 for (DataType r : rl) { 558 if (r.equalsDeep(l)) { 559 return r; 560 } 561 } 562 return null; 563 } 564 565 @SuppressWarnings("rawtypes") 566 protected boolean compareDataTypesWithTracking(String name, DataType l, DataType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, Base parent) { 567 StructuralMatch<String> match = null; 568 boolean le = l == null || l.isEmpty(); 569 boolean re = r == null || r.isEmpty(); 570 if (le && re) { 571 match = new StructuralMatch<>(null, null, null); 572 } else if (le) { 573 match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "Added the item '"+r.fhirType()+"'", fhirType()+"."+name)); 574 session.markAdded(r); 575 } else if (re) { 576 match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "Removed the item '"+l.fhirType()+"'", fhirType()+"."+name)); 577 session.markDeleted(parent, name, l); 578 } else if (l.equalsDeep(r)) { 579 match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), null); 580 } else { 581 session.markChanged(r, l); 582 match = new StructuralMatch<>(l.fhirType(), r.fhirType(), vmI(level, "Values Differ", fhirType()+"."+name)); 583 if (level != IssueSeverity.NULL && res != null) { 584 res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.fhirType()+"' vs '"+r.fhirType()+"'", level)); 585 } 586 } 587 if (comp != null) { 588 comp.put(name, match); 589 } 590 return match.isDifferent(); 591 } 592 593 protected abstract String fhirType(); 594 595 public XhtmlNode renderMetadata(CanonicalResourceComparison<? extends CanonicalResource> comparison, String id, String prefix) throws FHIRException, IOException { 596 // columns: code, display (left|right), properties (left|right) 597 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false, "c"); 598 TableModel model = gen.new TableModel(id, true); 599 model.setAlternating(true); 600 model.getTitles().add(gen.new Title(null, null, "Name", "Property Name", null, 100)); 601 model.getTitles().add(gen.new Title(null, null, "Value", "The value of the property", null, 200, 2)); 602 model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200)); 603 604 for (String n : sorted(comparison.getMetadata().keySet())) { 605 StructuralMatch<String> t = comparison.getMetadata().get(n); 606 addRow(gen, model.getRows(), n, t); 607 } 608 return gen.generate(model, prefix, 0, null); 609 } 610 611 private void addRow(HierarchicalTableGenerator gen, List<Row> rows, String name, StructuralMatch<String> t) { 612 Row r = gen.new Row(); 613 rows.add(r); 614 r.getCells().add(gen.new Cell(null, null, name, null, null)); 615 if (t.hasLeft() && t.hasRight()) { 616 if (t.getLeft().equals(t.getRight())) { 617 r.getCells().add(gen.new Cell(null, null, t.getLeft(), null, null).span(2)); 618 } else { 619 r.getCells().add(gen.new Cell(null, null, t.getLeft(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 620 r.getCells().add(gen.new Cell(null, null, t.getRight(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 621 } 622 } else if (t.hasLeft()) { 623 r.setColor(COLOR_NO_ROW_RIGHT); 624 r.getCells().add(gen.new Cell(null, null, t.getLeft(), null, null)); 625 r.getCells().add(missingCell(gen)); 626 } else if (t.hasRight()) { 627 r.setColor(COLOR_NO_ROW_LEFT); 628 r.getCells().add(missingCell(gen)); 629 r.getCells().add(gen.new Cell(null, null, t.getRight(), null, null)); 630 } else { 631 r.getCells().add(missingCell(gen).span(2)); 632 } 633 r.getCells().add(cellForMessages(gen, t.getMessages())); 634 int i = 0; 635 for (StructuralMatch<String> c : t.getChildren()) { 636 addRow(gen, r.getSubRows(), name+"["+i+"]", c); 637 i++; 638 } 639 } 640 641 642 private List<String> sorted(Set<String> keys) { 643 List<String> res = new ArrayList<>(); 644 res.addAll(keys); 645 Collections.sort(res); 646 return res; 647 } 648 649 650}