
001package org.hl7.fhir.r5.conformance.profile; 002 003import java.math.BigDecimal; 004import java.util.ArrayList; 005import java.util.HashSet; 006import java.util.List; 007import java.util.Set; 008import java.util.Stack; 009 010import lombok.extern.slf4j.Slf4j; 011import org.hl7.fhir.exceptions.DefinitionException; 012import org.hl7.fhir.exceptions.FHIRException; 013import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions; 014import org.hl7.fhir.r5.context.IWorkerContext; 015import org.hl7.fhir.r5.model.Base; 016import org.hl7.fhir.r5.model.CanonicalType; 017import org.hl7.fhir.r5.model.CodeType; 018import org.hl7.fhir.r5.model.DataType; 019import org.hl7.fhir.r5.model.DateTimeType; 020import org.hl7.fhir.r5.model.ElementDefinition; 021import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 022import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; 023import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 024import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 025import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 026import org.hl7.fhir.r5.model.Extension; 027import org.hl7.fhir.r5.model.Property; 028import org.hl7.fhir.r5.model.Quantity; 029import org.hl7.fhir.r5.model.StructureDefinition; 030import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent; 031import org.hl7.fhir.r5.utils.DefinitionNavigator; 032import org.hl7.fhir.r5.utils.ToolingExtensions; 033import org.hl7.fhir.r5.utils.TypesUtilities; 034import org.hl7.fhir.r5.utils.UserDataNames; 035import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 036import org.hl7.fhir.utilities.Utilities; 037import org.hl7.fhir.utilities.i18n.I18nConstants; 038 039/** 040 * when a slice is encountered, it may have additional details defined after the slice that must be merged into 041 * each of the slices. That's kind of multiple inheritance, and fiendishly complicated to add to the snapshot generator 042 * 043 * This class pre-processes the differential, finding the slices that have these trailing properties, and 044 * filling them out in the slices that follow 045 * 046 * There's potential problems here, mostly around slicing extensions (other kind of slicing isn't allowed) 047 * and also the merging logic might need to be sophisticated. 048 * 049 */ 050@Slf4j 051public class SnapshotGenerationPreProcessor { 052 053 public class ElementAnalysis { 054 private StructureDefinition structure; 055 private ElementDefinition element; 056 private String type; 057 public SourcedChildDefinitions children; 058 protected ElementAnalysis(StructureDefinition structure, ElementDefinition element, String type) { 059 super(); 060 this.structure = structure; 061 this.element = element; 062 this.type = type; 063 } 064 public StructureDefinition getStructure() { 065 return structure; 066 } 067 public ElementDefinition getElement() { 068 return element; 069 } 070 public SourcedChildDefinitions getChildren() { 071 return children; 072 } 073 public void setChildren(SourcedChildDefinitions children) { 074 this.children = children; 075 } 076 public String getType() { 077 return type; 078 } 079 public String summary() { 080 return element.getName()+":"+type; 081 } 082 } 083 084 public class SliceInfo { 085 SliceInfo parent; 086 String path; 087 boolean closed; 088 ElementDefinition slicer; 089 List<ElementDefinition> sliceStuff; 090 List<ElementDefinition> slices; 091 092 public SliceInfo(SliceInfo parent, ElementDefinition ed) { 093 this.parent = parent; 094 path = ed.getPath(); 095 slicer = ed; 096 sliceStuff = new ArrayList<>(); 097 if (parent != null) { 098 parent.add(ed); 099 } 100 } 101 102 public void newSlice(ElementDefinition ed) { 103 if (slices == null) { 104 slices = new ArrayList<ElementDefinition>(); 105 } 106 slices.add(ed); 107 if (parent != null) { 108 parent.add(ed); 109 } 110 } 111 public void add(ElementDefinition ed) { 112 if (slices == null) { 113 sliceStuff.add(ed); 114 } 115 if (parent != null) { 116 parent.add(ed); 117 } 118 } 119 } 120 121 private IWorkerContext context; 122 private ProfileUtilities utils; 123 Set<String> typeNames; 124 private List<SliceInfo> slicings = new ArrayList<>(); 125 126 public SnapshotGenerationPreProcessor(ProfileUtilities utils) { 127 super(); 128 this.utils = utils; 129 this.context = utils.getContext(); 130 } 131 132 public void process(StructureDefinitionDifferentialComponent diff, StructureDefinition srcOriginal) { 133 StructureDefinition srcWrapper = shallowClone(srcOriginal, diff); 134 processSlices(diff, srcWrapper); 135 if (srcWrapper.hasExtension(ToolingExtensions.EXT_ADDITIONAL_BASE)) { 136 insertMissingSparseElements(diff.getElement(), srcWrapper.getTypeName()); 137 for (Extension ext : srcWrapper.getExtensionsByUrl(ToolingExtensions.EXT_ADDITIONAL_BASE)) { 138 StructureDefinition ab = context.fetchResource(StructureDefinition.class, ext.getValue().primitiveValue()); 139 if (ab == null) { 140 throw new FHIRException("Unable to find additional base '"+ext.getValue().primitiveValue()+"'"); 141 } 142 if (!srcWrapper.getType().equals(ab.getType())) { 143 throw new FHIRException("Type mismatch"); 144 } 145 SnapshotGenerationPreProcessor abpp = new SnapshotGenerationPreProcessor(utils); 146 abpp.process(ab.getDifferential(), ab); 147 abpp.insertMissingSparseElements(ab.getDifferential().getElement(), srcWrapper.getTypeName()); 148 mergeElementsFromAdditionalBase(srcWrapper, ab); 149 } 150 } 151 } 152 153 private StructureDefinition shallowClone(StructureDefinition src, StructureDefinitionDifferentialComponent diff) { 154 StructureDefinition sd = new StructureDefinition(); 155 sd.setUrl(src.getUrl()); 156 sd.setVersion(src.getVersion()); 157 sd.setType(src.getType()); 158 sd.setDerivation(src.getDerivation()); 159 sd.setBaseDefinition(src.getBaseDefinition()); 160 sd.setExtension(src.getExtension()); 161 sd.setDifferential(diff); 162 return sd; 163 } 164 165 private void mergeElementsFromAdditionalBase(StructureDefinition sourceSD, StructureDefinition baseSD) { 166 List<ElementDefinition> output = new ArrayList<ElementDefinition>(); 167 output.add(mergeElementDefinitions(baseSD.getDifferential().getElementFirstRep(), sourceSD.getDifferential().getElementFirstRep(), baseSD)); 168 DefinitionNavigator base = new DefinitionNavigator(context, baseSD, true, false); 169 DefinitionNavigator source = new DefinitionNavigator(context, sourceSD, true, false); 170 StructureDefinition sdt = context.fetchTypeDefinition(sourceSD.getType()); 171 SourcedChildDefinitions children = utils.getChildMap(sdt, sdt.getSnapshot().getElementFirstRep(), false); 172 mergeElements(output, base, source, children, baseSD); 173 sourceSD.getDifferential().setElement(output); 174 } 175 176 private void mergeElements(List<ElementDefinition> output, DefinitionNavigator base, DefinitionNavigator source, SourcedChildDefinitions children, StructureDefinition baseSD) { 177 for (ElementDefinition child : children.getList()) { 178 DefinitionNavigator baseChild = base == null ? null : base.childByName(child.getName()); 179 DefinitionNavigator sourceChild = source == null ? null : source.childByName(child.getName()); 180 if (baseChild != null && sourceChild != null) { 181 if (!baseChild.hasSlices() && !sourceChild.hasSlices()) { 182 output.add(mergeElementDefinitions(baseChild.current(), sourceChild.current(), baseSD)); 183 if (sourceChild.hasChildren() || baseChild.hasChildren()) { 184 mergeElements(output, baseChild, sourceChild, getChildren(children, child, sourceChild, baseChild, baseSD), baseSD); 185 } 186 } else if (baseChild.hasSlices() && sourceChild.hasSlices()) { 187 if (!slicingIsConsistent(baseChild.getSlicing(), sourceChild.getSlicing())) { 188 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, baseSD.getVersionedUrl(), child.getPath()+".slicing", 189 describeDiscriminators(baseChild.getSlicing()), describeDiscriminators(sourceChild.getSlicing()))); 190 } 191 output.add(mergeElementDefinitions(baseChild.current(), sourceChild.current(), baseSD)); 192 mergeElements(output, baseChild, sourceChild, getChildren(children, child, sourceChild, baseChild, baseSD), baseSD); 193 List<DefinitionNavigator> handled = new ArrayList<>(); 194 for (DefinitionNavigator slice : sourceChild.slices()) { 195 DefinitionNavigator match = getMatchingSlice(baseChild, slice, sourceChild.getSlicing()); 196 if (match != null) { 197 handled.add(match); 198 output.add(mergeElementDefinitions(match.current(), slice.current(), baseSD)); 199 mergeElements(output, match, slice, getChildren(children, child, match, slice, baseSD), baseSD); 200 } else { 201 // this slice isn't in the base 202 output.add(slice.current().copy()); 203 mergeElements(output, null, slice, getChildren(children, child, slice, null, baseSD), baseSD); 204 } 205 } 206 for (DefinitionNavigator slice : baseChild.slices()) { 207 if (!handled.contains(slice)) { 208 output.add(slice.current().copy()); 209 mergeElements(output, slice, null, getChildren(children, child, null, slice, baseSD), baseSD); 210 } 211 } 212 } else if (baseChild.hasSlices()) { 213 throw new FHIRException("Not done yet"); 214 } else { // sourceChild.hasSlices() 215 throw new FHIRException("Not done yet"); 216 } 217 } else if (baseChild != null) { 218 output.add(baseChild.current().copy()); 219 if (baseChild.hasChildren()) { 220 mergeElements(output, baseChild, sourceChild, getChildren(children, child, null, baseChild, baseSD), baseSD); 221 } 222 if (baseChild.hasSlices()) { 223 for (DefinitionNavigator slice : baseChild.slices()) { 224 mergeElements(output, slice, null, getChildren(children, child, null, slice, baseSD), baseSD); 225 } 226 } 227 } else if (sourceChild != null) { 228 output.add(sourceChild.current().copy()); 229 if (sourceChild.hasSlices()) { 230 for (DefinitionNavigator slice : sourceChild.slices()) { 231 mergeElements(output, null, slice, getChildren(children, child, slice, null, baseSD), baseSD); 232 } 233 } 234 if (sourceChild.hasChildren()) { 235 mergeElements(output, baseChild, sourceChild, getChildren(children, child, sourceChild, null, baseSD), baseSD); 236 } 237 // slices 238 } else { 239 // do nothing - no match on either side 240 } 241 } 242 } 243 244 private DefinitionNavigator getMatchingSlice(DefinitionNavigator base, DefinitionNavigator slice, ElementDefinitionSlicingComponent slicing) { 245 List<DataType> values = new ArrayList<>(); 246 for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) { 247 values.add(getDiscriminatorValue(slice, d)); 248 } 249 DefinitionNavigator match = null; 250 for (DefinitionNavigator t : base.slices()) { 251 List<DataType> values2 = new ArrayList<>(); 252 for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) { 253 values2.add(getDiscriminatorValue(t, d)); 254 } 255 if (valuesMatch(values, values2)) { 256 if (match == null) { 257 match = t; 258 } else { 259 throw new Error("Duplicate slice"); 260 } 261 } 262 } 263 return match; 264 } 265 266 private DataType getDiscriminatorValue(DefinitionNavigator slice, ElementDefinitionSlicingDiscriminatorComponent d) { 267 // we're not following types, because we want to stop where the differential stops. but right here and now, 268 // we have to follow the types. So we're going to clone the navigator 269 DefinitionNavigator dn = new DefinitionNavigator(slice, true); 270 switch (d.getType() ) { 271 case EXISTS: 272 throw new Error("Not supported yet"); 273 case NULL: 274 throw new Error("Not supported yet"); 275 case PATTERN: 276 throw new Error("Not supported yet"); 277 case POSITION: 278 throw new Error("Not supported yet"); 279 case PROFILE: 280 throw new Error("Not supported yet"); 281 case TYPE: 282 if ("$this".equals(d.getPath())) { 283 return new CodeType(dn.getManualType() != null ? dn.getManualType().getCode() : dn.current().typeSummary()); 284 } else { 285 throw new Error("Not supported yet"); 286 } 287 case VALUE: 288 DefinitionNavigator child = dn.childByName(d.getPath()); 289 if (child != null) { 290 ElementDefinition ed = child.current(); 291 if (ed.hasFixed()) { 292 return ed.getFixed(); 293 } else if (ed.hasPattern()) { 294 return ed.getPattern(); 295 } 296 } else { 297 return null; 298 } 299 default: 300 throw new Error("Not supported yet"); 301 } 302 } 303 304 private boolean valuesMatch(List<DataType> values1, List<DataType> values2) { 305 for (int i = 0; i < values1.size(); i++) { 306 DataType v1 = values1.get(i); 307 DataType v2 = values2.get(i); 308 if (!valuesMatch(v1, v2)) { 309 return false; 310 } 311 } 312 return true; 313 } 314 315 private boolean valuesMatch(DataType v1, DataType v2) { 316 if (v1 == null && v2 == null) { 317 return true; 318 } else if (v1 != null && v2 != null) { 319 return v1.equalsDeep(v2); 320 } else { 321 return false; 322 } 323 } 324 325 private boolean slicingIsConsistent(ElementDefinitionSlicingComponent src, ElementDefinitionSlicingComponent base) { 326 if (src.getRules() != base.getRules()) { 327 return false; 328 } 329 if (src.getDiscriminator().size() != base.getDiscriminator().size()) { 330 return false; 331 } 332 for (ElementDefinitionSlicingDiscriminatorComponent d1 : src.getDiscriminator()) { 333 boolean found = false; 334 for (ElementDefinitionSlicingDiscriminatorComponent d2 : base.getDiscriminator()) { 335 found = found || (d1.getType() == d2.getType() && d1.getPath().equals(d2.getPath())); 336 } 337 if (!found) { 338 return false; 339 } 340 } 341 return true; 342 } 343 344 private Object describeDiscriminators(ElementDefinitionSlicingComponent slicing) { 345 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 346 for (ElementDefinitionSlicingDiscriminatorComponent t : slicing.getDiscriminator()) { 347 b.append(t.getType().toCode()+":"+t.getPath()); 348 } 349 return (slicing.hasRules() ? slicing.getRules().toCode()+":" : "")+b.toString()+(slicing.hasOrdered() ? " (ordered)" : ""); 350 } 351 352 private SourcedChildDefinitions getChildren(SourcedChildDefinitions children, ElementDefinition child, DefinitionNavigator source, DefinitionNavigator base, StructureDefinition baseSD) { 353 if (child.getType().size() > 1) { 354 String type = null; 355 if (source != null && base != null) { 356 String typeSource = statedOrImpliedType(source); 357 String typeBase = statedOrImpliedType(base); 358 if (typeSource != null && typeBase != null) { 359 if (typeSource.equals(typeBase)) { 360 type = typeSource; 361 } else { 362 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, baseSD.getVersionedUrl(), child.getPath()+".type", typeSource, typeBase)); 363 } 364 } else if (typeSource != null) { 365 type = typeSource; 366 } else if (typeBase != null) { 367 type = typeBase; 368 } 369 } else if (source != null) { 370 type = statedOrImpliedType(source); 371 } else if (base != null) { 372 type = statedOrImpliedType(base); 373 } else { 374 // type = "DataType"; 375 } 376 if (type == null) { 377 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INDETERMINATE_TYPE, baseSD.getVersionedUrl(), child.getPath()+".type")); 378 379 } else { 380 return utils.getChildMap(children.getSource(), child, true, type); 381 } 382 } else { 383 return utils.getChildMap(children.getSource(), child, true); 384 } 385 } 386 387 private String statedOrImpliedType(DefinitionNavigator source) { 388 if (source.getManualType() != null) { 389 return source.getManualType().getCode(); 390 } else if (source.current().getType().size() == 1) { 391 return source.current().getTypeFirstRep().getCode(); 392 } else { 393 return null; 394 } 395 } 396 397 private ElementDefinition mergeElementDefinitions(ElementDefinition base, ElementDefinition source, StructureDefinition baseSD) { 398 ElementDefinition merged = new ElementDefinition(); 399 merged.setPath(source.getPath()); 400 if (source.hasSlicing()) { 401 merged.setSlicing(source.getSlicing()); 402 } 403 404 merged.setLabelElement(chooseProp(source.getLabelElement(), base.getLabelElement())); 405 merged.setShortElement(chooseProp(source.getShortElement(), base.getShortElement())); 406 merged.setDefinitionElement(chooseProp(source.getDefinitionElement(), base.getDefinitionElement())); 407 merged.setCommentElement(chooseProp(source.getCommentElement(), base.getCommentElement())); 408 merged.setRequirementsElement(chooseProp(source.getRequirementsElement(), base.getRequirementsElement())); 409 merged.setMeaningWhenMissingElement(chooseProp(source.getMeaningWhenMissingElement(), base.getMeaningWhenMissingElement())); 410 merged.setOrderMeaningElement(chooseProp(source.getOrderMeaningElement(), base.getOrderMeaningElement())); 411 merged.setMaxLengthElement(chooseProp(source.getMaxLengthElement(), base.getMaxLengthElement())); 412 merged.setMustHaveValueElement(chooseProp( source.getMustHaveValueElement(), base.getMustHaveValueElement())); 413 merged.setMustSupportElement(chooseProp(source.getMustSupportElement(), base.getMustSupportElement())); 414 merged.setIsModifierElement(chooseProp(source.getIsModifierElement(), base.getIsModifierElement())); 415 merged.setIsModifierReasonElement(chooseProp(source.getIsModifierReasonElement(), base.getIsModifierReasonElement())); 416 merged.setIsSummaryElement(chooseProp(source.getIsSummaryElement(), base.getIsSummaryElement())); 417 418 if (source.hasMin() && base.hasMin()) { 419 merged.setMinElement(source.getMin() < base.getMin() ? source.getMinElement().copy() : base.getMinElement().copy()); 420 } else { 421 merged.setMinElement(chooseProp(source.getMinElement(), base.getMinElement().copy())); 422 } 423 if (source.hasMax() && base.hasMax()) { 424 merged.setMaxElement(source.getMaxAsInt() < base.getMaxAsInt() ? source.getMaxElement().copy() : base.getMaxElement().copy()); 425 } else { 426 merged.setMaxElement(chooseProp(source.getMaxElement(), base.getMaxElement())); 427 } 428 429 if (source.hasFixed() || base.hasFixed()) { 430 if (source.hasFixed()) { 431 if (base.hasFixed()) { 432 if (!source.getFixed().equalsDeep(base.getFixed())) { 433 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, baseSD.getVersionedUrl(), source.getPath()+".fixed", source.getFixed().toString(), base.getFixed().toString())); 434 } else { 435 merged.setFixed(source.getFixed().copy()); 436 } 437 } else if (base.hasPattern()) { 438 merged.setFixed(checkPatternValues(baseSD.getVersionedUrl(), source.getPath()+".fixed", source.getFixed(), base.getPattern(), false)); 439 } else { 440 merged.setFixed(source.getFixed().copy()); 441 } 442 } else if (source.hasPattern()) { // base.hasFixed() == true 443 merged.setFixed(checkPatternValues(baseSD.getVersionedUrl(), source.getPath()+".pattern", base.getFixed(), source.getPattern(), false)); 444 } else { 445 merged.setFixed(base.getFixed().copy()); 446 } 447 } else if (source.hasPattern() && base.hasPattern()) { 448 merged.setPattern(checkPatternValues(baseSD.getVersionedUrl(), source.getPath()+".pattern", source.getFixed(), base.getFixed(), true)); 449 } else { 450 merged.setPattern(chooseProp(source.getPattern(), base.getPattern())); 451 } 452 453 if (source.hasMinValue() && base.hasMinValue()) { 454 merged.setMinValue(isLower(baseSD.getVersionedUrl(), source.getPath(), "minValue", source.getMinValue(), base.getMinValue()) ? base.getMinValue().copy() : source.getMinValue().copy()); 455 } else { 456 merged.setMinValue(chooseProp(source.getMinValue(), base.getMinValue())); 457 } 458 if (source.hasMaxValue() && base.hasMaxValue()) { 459 merged.setMaxValue(isLower(baseSD.getVersionedUrl(), source.getPath(), "maxValue", source.getMaxValue(), base.getMaxValue()) ? source.getMaxValue().copy() : base.getMaxValue().copy()); 460 } else { 461 merged.setMaxValue(chooseProp(source.getMaxValue(), base.getMaxValue())); 462 } 463 if (source.hasMaxLength() && base.hasMaxLength()) { 464 merged.setMaxLengthElement(base.getMaxLength() < source.getMaxLength() ? source.getMaxLengthElement().copy() : base.getMaxLengthElement().copy()); 465 } else { 466 merged.setMaxLengthElement(chooseProp(source.getMaxLengthElement(), base.getMaxLengthElement().copy())); 467 } 468 // union 469 union(merged.getAlias(), source.getAlias(), base.getAlias()); 470 union(merged.getCode(), source.getCode(), base.getCode()); 471 union(merged.getExample(), source.getExample(), base.getExample()); 472 union(merged.getConstraint(), source.getConstraint(), base.getConstraint()); 473 union(merged.getMapping(), source.getMapping(), base.getMapping()); 474 475 // intersection 476 if (source.hasValueAlternatives() && base.hasValueAlternatives()) { 477 for (CanonicalType st : source.getValueAlternatives()) { 478 boolean exists = false; 479 for (CanonicalType st2 : base.getValueAlternatives()) { 480 exists = exists || st.equals(st2); 481 } 482 if (exists) { 483 merged.getValueAlternatives().add(st.copy()); 484 } 485 } 486 } else if (source.hasValueAlternatives()) { 487 for (CanonicalType st : source.getValueAlternatives()) { 488 merged.getValueAlternatives().add(st.copy()); 489 } 490 } else if (base.hasValueAlternatives()) { 491 for (CanonicalType st : base.getValueAlternatives()) { 492 merged.getValueAlternatives().add(st.copy()); 493 } 494 } 495 496 if (source.hasType() && base.hasType()) { 497 for (TypeRefComponent t1 : source.getType()) { 498 for (TypeRefComponent t2 : base.getType()) { 499 if (Utilities.stringsEqual(t1.getWorkingCode(), t2.getWorkingCode())) { 500 merged.getType().add(mergeTypes(baseSD.getVersionedUrl(), source.getPath(), t1, t2)); 501 } 502 } 503 } 504 if (merged.getType().isEmpty()) { 505 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, baseSD.getVersionedUrl(), source.getPath()+".type", source.typeSummary(), base.typeSummary())); 506 507 } 508 } else if (source.hasType()) { 509 for (TypeRefComponent st : source.getType()) { 510 merged.getType().add(st.copy()); 511 } 512 } else if (base.hasType()) { 513 for (TypeRefComponent st : base.getType()) { 514 merged.getType().add(st.copy()); 515 } 516 } 517 518 // binding 519 if (source.hasBinding() && base.hasBinding()) { 520 throw new Error("not done yet"); 521 } else if (source.hasBinding()) { 522 merged.setBinding(source.getBinding().copy()); 523 } else if (base.hasBinding()) { 524 merged.setBinding(base.getBinding().copy()); 525 } 526 527 528 return merged; 529 } 530 531 private <T extends DataType> T chooseProp(T source, T base) { 532 if (source != null && !source.isEmpty()) { 533 return (T) source.copy(); 534 } 535 if (base != null && !base.isEmpty()) { 536 return (T) base.copy(); 537 } 538 return null; 539 } 540 541 private TypeRefComponent mergeTypes(String vurl, String path, TypeRefComponent t1, TypeRefComponent t2) { 542 TypeRefComponent tr = t1.copy(); 543 if (t1.hasProfile() && t2.hasProfile()) { 544 // here, this is tricky, because we need to know what the merged additional bases of the pairings will be 545 if (t1.getProfile().size() > 1 || t2.getProfile().size() > 1) { 546 throw new FHIRException("Not handled yet: multiple profiles"); 547 } 548 StructureDefinition sd1 = context.fetchResource(StructureDefinition.class, t1.getProfile().get(0).asStringValue()); 549 if (sd1 == null) { 550 throw new FHIRException("Unknown type profile at '"+path+"': "+t1.getProfile().get(0).asStringValue()); 551 } 552 StructureDefinition sd2 = context.fetchResource(StructureDefinition.class, t2.getProfile().get(0).asStringValue()); 553 if (sd2 == null) { 554 throw new FHIRException("Unknown type profile at '"+path+"': "+t2.getProfile().get(0).asStringValue()); 555 } 556 tr.getProfile().clear(); 557 if (specialises(sd1, sd2)) { 558 // both sd1 and sd2 apply, but sd1 applies everything in sd2, so it's just sd1 559 tr.getProfile().add(t1.getProfile().get(0).copy()); 560 } else if (specialises(sd2, sd1)) { 561 // both sd1 and sd2 apply, but sd2 applies everything in sd1, so it's just sd2 562 tr.getProfile().add(t2.getProfile().get(0).copy()); 563 } else { 564 // oh dear. We have to find a type that is both of them 565 StructureDefinition sd3 = findJointProfile(sd1, sd2); 566 if (sd3 == null) { 567 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_NO_TYPE, vurl, path, sd1.getVersionedUrl(), sd2.getVersionedUrl())); 568 } else { 569 tr.getProfile().add(new CanonicalType(sd3.getUrl())); 570 } 571 } 572 } else if (t2.hasProfile()) { 573 for (CanonicalType ct : t2.getProfile()) { 574 tr.getProfile().add(ct.copy()); 575 } 576 } 577 if (t1.hasTargetProfile() && t2.hasTargetProfile()) { 578 // here, this is tricky, because we need to know what the merged additional bases of the pairings will be 579 } else if (t2.hasTargetProfile()) { 580 for (CanonicalType ct : t2.getTargetProfile()) { 581 tr.getTargetProfile().add(ct.copy()); 582 } 583 } 584 if (t1.hasAggregation() && t2.hasAggregation() && !t1.getAggregation().equals(t2.getAggregation())) { 585 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+".type["+tr.getWorkingCode()+"].aggregation", t1.getAggregation(), t2.getAggregation())); 586 } 587 if (t1.hasVersioning() && t2.hasVersioning() && t1.getVersioning() != t2.getVersioning()) { 588 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+".type["+tr.getWorkingCode()+"].aggregation", t1.getVersioning(), t2.getVersioning())); 589 } 590 return tr; 591 } 592 593 private StructureDefinition findJointProfile(StructureDefinition sd1, StructureDefinition sd2) { 594 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 595 boolean b1 = sd.getBaseDefinitions().contains(sd1.getUrl()) || sd.getBaseDefinitions().contains(sd1.getVersionedUrl()); 596 boolean b2 = sd.getBaseDefinitions().contains(sd2.getUrl()) || sd.getBaseDefinitions().contains(sd2.getVersionedUrl()); 597 if (b1 && b2) { 598 return sd; 599 } 600 } 601 return null; 602 } 603 604 private boolean specialises(StructureDefinition focus, StructureDefinition other) { 605 // we ignore impose and compliesWith - for now? 606 for (String url : focus.getBaseDefinitions()) { 607 StructureDefinition base = context.fetchResource(StructureDefinition.class, url); 608 if (base != null) { 609 if (base == other || specialises(base, other)) { 610 return true; 611 } 612 } 613 } 614 return false; 615 } 616 617 private <T extends Base> void union(List<T> merged, List<T> source, List<T> base) { 618 for (T st : source) { 619 merged.add((T) st.copy()); 620 } 621 for (T st : base) { 622 boolean exists = false; 623 for (T st2 : merged) { 624 exists = exists || st.equals(st2); 625 } 626 if (!exists) { 627 merged.add((T) st.copy()); 628 } 629 } 630 } 631 632 private boolean isLower(String vurl, String path, String property, DataType v1, DataType v2) { 633 if (v1 instanceof Quantity && v2 instanceof Quantity) { 634 Quantity q1 = (Quantity) v1; 635 Quantity q2 = (Quantity) v2; 636 if (q1.hasUnit() || q2.hasUnit()) { 637 if (Utilities.stringsEqual(q1.getUnit(), q2.getUnit())) { 638 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+"."+property+".unit", v1.fhirType(), v2.fhirType())); 639 } 640 } 641 return isLower(vurl, path, property+".value", q1.getValueElement(), q2.getValueElement()); 642 } else if (v1.isDateTime() && v2.isDateTime()) { 643 DateTimeType d1 = (DateTimeType) v1; 644 DateTimeType d2 = (DateTimeType) v2; 645 return d1.before(d2); 646 } else if (Utilities.isDecimal(v1.primitiveValue(), true) && Utilities.isDecimal(v2.primitiveValue(), true)) { 647 BigDecimal d1 = new BigDecimal(v1.primitiveValue()); 648 BigDecimal d2 = new BigDecimal(v2.primitiveValue()); 649 return d1.compareTo(d2) < 0; 650 } else { 651 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+"."+property, v1.fhirType(), v2.fhirType())); 652 } 653 } 654 655 private DataType checkPatternValues(String vurl, String path, DataType v1, DataType v2, boolean extras) { 656 if (!v1.fhirType().equals(v2.fhirType())) { 657 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path, v1.fhirType(), v2.fhirType())); 658 } 659 DataType merged = v1.copy(); 660 if (v1.isPrimitive()) { 661 if (!Utilities.stringsEqual(v1.primitiveValue(), v2.primitiveValue())) { 662 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+".value", v1.primitiveValue(), v2.primitiveValue())); 663 } 664 } 665 for (Property p1 : v1.children()) { 666 Property p2 = v2.getChildByName(p1.getName()); 667 if (p1.hasValues() && p2.hasValues()) { 668 if (p1.getValues().size() > 1 || p1.getValues().size() > 2) { 669 throw new Error("Not supported"); 670 } 671 merged.setProperty(p1.getName(), checkPatternValues(vurl, path+"."+p1.getName(), (DataType) p1.getValues().get(0), (DataType) p2.getValues().get(0), extras)); 672 } else if (p2.hasValues()) { 673 if (!extras) { 674 throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+"."+p1.getName(), "null", v2.primitiveValue())); 675 } 676 if (p2.getValues().size() > 1) { 677 throw new Error("Not supported"); 678 } 679 merged.setProperty(p1.getName(), p2.getValues().get(0).copy()); 680 } 681 } 682 return merged; 683 } 684 685 686 private void processSlices(StructureDefinitionDifferentialComponent diff, StructureDefinition src) { 687 // first pass, divide it up 688 for (int cursor = 0; cursor < diff.getElement().size(); cursor++) { 689 ElementDefinition ed = diff.getElement().get(cursor); 690 691 SliceInfo si = getSlicing(ed); 692 if (si == null) { 693 if (ed.hasSlicing() && !isExtensionSlicing(ed)) { 694 si = new SliceInfo(null, ed); 695 slicings.add(si); 696 } else { 697 // ignore this 698 } 699 } else { 700 if (ed.hasSliceName() && ed.getPath().equals(si.path)) { 701 si.newSlice(ed); 702 } else if (ed.hasSlicing() && !isExtensionSlicing(ed)) { 703 si = new SliceInfo(si, ed); 704 slicings.add(si); 705 } else { 706 si.add(ed); 707 } 708 } 709 } 710 711 for (SliceInfo si : slicings) { 712 if (!si.sliceStuff.isEmpty() && si.slices != null) { 713 for (ElementDefinition ed : si.sliceStuff) { 714 if (ed.hasSlicing() && !isExtensionSlicing(ed)) { 715 String message = context.formatMessage(I18nConstants.UNSUPPORTED_SLICING_COMPLEXITY, si.slicer.getPath(), ed.getPath(), ed.getSlicing().summary()); 716 log.warn(message); 717 return; 718 } 719 } 720 } 721 } 722 723 // working backward 724 for (int i = slicings.size() - 1; i >= 0; i--) { 725 SliceInfo si = slicings.get(i); 726 if (!si.sliceStuff.isEmpty() && si.slices != null) { 727 // for each actual slice, we need to merge sliceStuff in 728 for (ElementDefinition slice : si.slices) { 729 mergeElements(diff.getElement(), si.sliceStuff, slice, si.slicer); 730 } 731 } else { 732 // we just ignore these - nothing to do 733 } 734 } 735 736 for (ElementDefinition ed : diff.getElement()) { 737 ProfileUtilities.markExtensions(ed, false, src); 738 } 739 } 740 741 private void mergeElements(List<ElementDefinition> elements, List<ElementDefinition> allSlices, ElementDefinition slice, ElementDefinition slicer) { 742 // we have 743 // elements - the list of all the elements 744 // allSlices which is the content defined for all the slices 745 // slice -the anchor element for the slice 746 747 int sliceIndex = elements.indexOf(slice); 748 int startOfSlice = sliceIndex + 1; 749 int endOfSlice = findEndOfSlice(elements, slice); 750 751 Set<String> missing = new HashSet<>(); 752 // the simple case is that all the stuff in allSlices exists between startOfSlice and endOfSlice 753 boolean allFound = true; 754 for (int i = 0; i < allSlices.size(); i++) { 755 boolean found = false; 756 for (int j = startOfSlice; j <= endOfSlice; j++) { 757 if (elementsMatch(elements.get(j), allSlices.get(i))) { 758 found = true; 759 break; 760 } 761 } 762 if (!found) { 763 missing.add(allSlices.get(i).getPath()); 764 allFound = false; 765 } 766 } 767 768 if (allFound) { 769 // then we just merge it in 770 for (int j = startOfSlice; j <= endOfSlice; j++) { 771 for (int i = 0; i < allSlices.size(); i++) { 772 if (elementsMatch(elements.get(j), allSlices.get(i))) { 773 merge(elements.get(j), allSlices.get(i)); 774 } 775 } 776 } 777 } else { 778 Set<ElementDefinition> handled = new HashSet<>(); 779 780 // merge the simple stuff 781 for (int j = startOfSlice; j <= endOfSlice; j++) { 782 for (int i = 0; i < allSlices.size(); i++) { 783 if (elementsMatch(elements.get(j), allSlices.get(i))) { 784 handled.add(allSlices.get(i)); 785 merge(elements.get(j), allSlices.get(i)); 786 } 787 } 788 } 789 790 // we have a lot of work to do 791 // the challenge is that the things missing from startOfSlice..endOfSlice have to injected in the correct order 792 // which means that we need to know the definitions 793 // and is extra tricky because we're sparse. so we just use the stated path 794 for (ElementDefinition ed : allSlices) { 795 if (!handled.contains(ed)) { 796 List<ElementAnalysis> edDef = analysePath(ed); 797 String id = ed.getId().replace(slicer.getId(), slice.getId()); 798 int index = determineInsertionPoint(elements, startOfSlice, endOfSlice, id, ed.getPath(), edDef); 799 ElementDefinition edc = ed.copy(); 800 edc.setUserData(UserDataNames.SNAPSHOT_PREPROCESS_INJECTED, true); 801 edc.setId(id); 802 elements.add(index, edc); 803 endOfSlice++; 804 } 805 } 806 } 807 808 } 809 810 private boolean elementsMatch(ElementDefinition ed1, ElementDefinition ed2) { 811 if (!pathsMatch(ed1.getPath(), ed2.getPath())) { 812 return false; 813 } else if (ed1.getSliceName() != null && ed2.getSliceName() != null) { 814 return ed1.getSliceName().equals(ed2.getSliceName()); 815 } else if (ed1.getSliceName() != null || ed2.getSliceName() != null) { 816 return false; 817 } else { 818 return true; 819 } 820 } 821 822 private boolean pathsMatch(String path1, String path2) { 823 if (path1.equals(path2)) { 824 return true; 825 } 826 if (path1.endsWith("[x]")) { 827 path1 = path1.substring(0, path1.length()-3); 828 if (path2.startsWith(path1)) { 829 if (!path2.substring(path1.length()).contains(".")) { 830 return true; 831 } 832 } 833 } 834 if (path2.endsWith("[x]")) { 835 path2 = path2.substring(0, path2.length()-3); 836 if (path1.startsWith(path2)) { 837 if (!path1.substring(path2.length()).contains(".")) { 838 return true; 839 } 840 } 841 } 842 return false; 843 } 844 845 private int determineInsertionPoint(List<ElementDefinition> elements, int startOfSlice, int endOfSlice, String id, String path, List<ElementAnalysis> edDef) { 846 // we work backwards through the id, looking for peers (this is the only way we can manage slicing) 847 String[] p = id.split("\\."); 848 for (int i = p.length-1; i >= 1; i--) { 849 String subId = p[0]; 850 for (int j = 1; j <= i; j++) { 851 subId += "."+p[j]; 852 } 853 List<ElementDefinition> peers = findPeers(elements, startOfSlice, endOfSlice, subId); 854 if (!peers.isEmpty()) { 855 // Once we find some, we figure out the insertion point - before one of them, or after the last? 856 for (ElementDefinition ed : peers) { 857 if (comesAfterThis(id, path, edDef, ed)) { 858 return elements.indexOf(ed); 859 } 860 } 861 return elements.indexOf(peers.get(peers.size() -1))+1; 862 } 863 } 864 return endOfSlice+1; 865 } 866 867 private List<ElementDefinition> findPeers(List<ElementDefinition> elements, int startOfSlice, int endOfSlice, String subId) { 868 List<ElementDefinition> peers = new ArrayList<>(); 869 for (int i = startOfSlice; i <= endOfSlice; i++) { 870 ElementDefinition ed = elements.get(i); 871 if (ed.getId().startsWith(subId)) { 872 peers.add(ed); 873 } 874 } 875 return peers; 876 } 877 878 private String summary(List<ElementAnalysis> edDef) { 879 List<String> s = new ArrayList<>(); 880 for (ElementAnalysis ed : edDef) { 881 s.add(ed.summary()); 882 } 883 884 return CommaSeparatedStringBuilder.join(",", s); 885 } 886 887 private boolean comesAfterThis(String id, String path, List<ElementAnalysis> edDef, ElementDefinition ed) { 888 String[] p1 = id.split("\\."); 889 String[] p2 = ed.getId().split("\\."); 890 for (int i = 0; i < Integer.min(p1.length, p2.length); i++) { 891 if (!p1[i].equals(p2[i])) { 892 ElementAnalysis sed = edDef.get(i-1); 893 int i1 = indexOfName(sed, p1[i]); 894 int i2 = indexOfName(sed, p2[i]); 895 if (i == Integer.min(p1.length, p2.length) -1 && i1 == i2) { 896 if (!p1[i].contains(":") && p2[i].contains(":")) { 897 // launched straight into slicing without setting it up, 898 // and now it's being set up 899 return true; 900 } 901 } 902 return i1 < i2; 903 } else { 904 // well, we just go on 905 } 906 } 907 return p1.length < p2.length; 908 } 909 910 private int indexOfName(ElementAnalysis sed, String name) { 911 if (name.contains(":")) { 912 name = name.substring(0, name.indexOf(":")); 913 } 914 for (int i = 0; i < sed.getChildren().getList().size(); i++) { 915 if (name.equals(sed.getChildren().getList().get(i).getName())) { 916 return i; 917 } 918 } 919 return -1; 920 } 921 922 private List<ElementAnalysis> analysePath(ElementDefinition ed) { 923 List<ElementAnalysis> res = new ArrayList<>(); 924 for (String pn : ed.getPath().split("\\.")) { 925 analysePathSegment(ed, res, pn); 926 } 927 return res; 928 } 929 930 private void analysePathSegment(ElementDefinition ed, List<ElementAnalysis> res, String pn) { 931 if (res.isEmpty()) { 932 StructureDefinition sd = context.fetchTypeDefinition(pn); 933 if (sd == null) { 934 String message = context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, pn, ed.getId()); 935 throw new DefinitionException(message); 936 } 937 res.add(new ElementAnalysis(sd, sd.getSnapshot().getElementFirstRep(), null)); 938 } else { 939 ElementAnalysis sed = res.get(res.size()-1); 940 sed.setChildren(utils.getChildMap(sed.getStructure(), sed.getElement(), true, sed.getType())); 941 ElementDefinition t = null; 942 String type = null; 943 for (ElementDefinition child : sed.getChildren().getList()) { 944 if (pn.equals(child.getName())) { 945 t = child; 946 break; 947 } 948 if (child.getName().endsWith("[x]")) { 949 String rn = child.getName().substring(0, child.getName().length()-3); 950 if (pn.startsWith(rn)) { 951 t = child; 952 String tn = pn.substring(rn.length()); 953 if (TypesUtilities.isPrimitive(Utilities.uncapitalize(tn))) { 954 type = Utilities.uncapitalize(tn); 955 } else { 956 type = tn; 957 } 958 break; 959 } 960 } 961 } 962 if (t == null) { 963 String message = context.formatMessage(I18nConstants.UNKNOWN_PROPERTY, pn, ed.getPath()); 964 throw new DefinitionException("Unknown path "+pn+" in path "+ed.getPath()+": "+message); 965 } else { 966 res.add(new ElementAnalysis(sed.getChildren().getSource(), t, type)); 967 } 968 } 969 } 970 971 private int findEndOfSlice(List<ElementDefinition> elements, ElementDefinition slice) { 972 for (int i = elements.indexOf(slice); i < elements.size(); i++) { 973 ElementDefinition e = elements.get(i); 974 if (!(e.getPath().startsWith(slice.getPath()+".") || 975 (e.getPath().equals(slice.getPath()) && slice.getSliceName().equals(e.getSliceName())))) { 976 return i-1; 977 } 978 } 979 return elements.size() - 1; 980 } 981 982 private void merge(ElementDefinition focus, ElementDefinition base) { 983 if (base.hasLabel() && !focus.hasLabel()) { 984 focus.setLabelElement(base.getLabelElement()); 985 } 986 if (base.hasCode() && !focus.hasCode()) { 987 focus.getCode().addAll(base.getCode()); 988 } 989 if (base.hasShort() && !focus.hasShort()) { 990 focus.setShortElement(base.getShortElement()); 991 } 992 if (base.hasDefinition() && !focus.hasDefinition()) { 993 focus.setDefinitionElement(base.getDefinitionElement()); 994 } 995 if (base.hasComment() && !focus.hasComment()) { 996 focus.setCommentElement(base.getCommentElement()); 997 } 998 if (base.hasRequirements() && !focus.hasRequirements()) { 999 focus.setRequirementsElement(base.getRequirementsElement()); 1000 } 1001 if (base.hasAlias() && !focus.hasAlias()) { 1002 focus.getAlias().addAll(base.getAlias()); 1003 } 1004 if (base.hasMin() && !focus.hasMin()) { 1005 focus.setMinElement(base.getMinElement()); 1006 } 1007 if (base.hasMax() && !focus.hasMax()) { 1008 focus.setMaxElement(base.getMaxElement()); 1009 } 1010 if (base.hasType() && !focus.hasType()) { 1011 focus.getType().addAll(base.getType()); 1012 } 1013 if (base.hasDefaultValue() && !focus.hasDefaultValue()) { 1014 focus.setDefaultValue(base.getDefaultValue()); 1015 } 1016 if (base.hasMeaningWhenMissing() && !focus.hasMeaningWhenMissing()) { 1017 focus.setMeaningWhenMissingElement(base.getMeaningWhenMissingElement()); 1018 } 1019 if (base.hasOrderMeaning() && !focus.hasOrderMeaning()) { 1020 focus.setOrderMeaningElement(base.getOrderMeaningElement()); 1021 } 1022 if (base.hasFixed() && !focus.hasFixed()) { 1023 focus.setFixed(base.getFixed()); 1024 } 1025 if (base.hasPattern() && !focus.hasPattern()) { 1026 focus.setPattern(base.getPattern()); 1027 } 1028 if (base.hasExample() && !focus.hasExample()) { 1029 focus.getExample().addAll(base.getExample()); 1030 } 1031 if (base.hasMinValue() && !focus.hasMinValue()) { 1032 focus.setMinValue(base.getMinValue()); 1033 } 1034 if (base.hasMaxValue() && !focus.hasMaxValue()) { 1035 focus.setMaxValue(base.getMaxValue()); 1036 } 1037 if (base.hasMaxLength() && !focus.hasMaxLength()) { 1038 focus.setMaxLengthElement(base.getMaxLengthElement()); 1039 } 1040 if (base.hasConstraint() && !focus.hasConstraint()) { 1041 focus.getConstraint().addAll(base.getConstraint()); 1042 } 1043 if (base.hasMustHaveValue() && !focus.hasMustHaveValue()) { 1044 focus.setMustHaveValueElement(base.getMustHaveValueElement()); 1045 } 1046 if (base.hasValueAlternatives() && !focus.hasValueAlternatives()) { 1047 focus.getValueAlternatives().addAll(base.getValueAlternatives()); 1048 } 1049 if (base.hasMustSupport() && !focus.hasMustSupport()) { 1050 focus.setMustSupportElement(base.getMustSupportElement()); 1051 } 1052 if (base.hasIsModifier() && !focus.hasIsModifier()) { 1053 focus.setIsModifierElement(base.getIsModifierElement()); 1054 } 1055 if (base.hasIsModifierReason() && !focus.hasIsModifierReason()) { 1056 focus.setIsModifierReasonElement(base.getIsModifierReasonElement()); 1057 } 1058 if (base.hasIsSummary() && !focus.hasIsSummary()) { 1059 focus.setIsSummaryElement(base.getIsSummaryElement()); 1060 } 1061 if (base.hasBinding() && !focus.hasBinding()) { 1062 focus.setBinding(base.getBinding()); 1063 } 1064 } 1065 1066 private boolean isExtensionSlicing(ElementDefinition ed) { 1067 if (!Utilities.existsInList(ed.getName(), "extension", "modiferExtension")) { 1068 return false; 1069 } 1070 if (ed.getSlicing().getRules() != SlicingRules.OPEN || (!ed.getSlicing().hasOrdered() || ed.getSlicing().getOrdered()) || ed.getSlicing().getDiscriminator().size() != 1) { 1071 return false; 1072 } 1073 ElementDefinitionSlicingDiscriminatorComponent d = ed.getSlicing().getDiscriminatorFirstRep(); 1074 return d.getType() == DiscriminatorType.VALUE && "url".equals(d.getPath()); 1075 } 1076 1077 private SliceInfo getSlicing(ElementDefinition ed) { 1078 for (int i = slicings.size() - 1; i >= 0; i--) { 1079 SliceInfo si = slicings.get(i); 1080 if (!si.closed) { 1081 if (si.path.length() > ed.getPath().length()) { 1082 si.closed = true; 1083 } else if (ed.getPath().startsWith(si.path)) { 1084 return si; 1085 } 1086 } 1087 } 1088 return null; 1089 } 1090 1091 public List<ElementDefinition> supplementMissingDiffElements(StructureDefinition profile) { 1092 List<ElementDefinition> list = new ArrayList<>(); 1093 list.addAll(profile.getDifferential().getElement()); 1094 if (list.isEmpty()) { 1095 ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName()); 1096 root.setId(profile.getTypeName()); 1097 list.add(root); 1098 } else { 1099 if (list.get(0).getPath().contains(".")) { 1100 ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName()); 1101 root.setId(profile.getTypeName()); 1102 list.add(0, root); 1103 } 1104 } 1105 insertMissingSparseElements(list, profile.getTypeName()); 1106 return list; 1107 } 1108 1109 private void insertMissingSparseElements(List<ElementDefinition> list, String typeName) { 1110 if (list.isEmpty() || list.get(0).getPath().contains(".")) { 1111 ElementDefinition ed = new ElementDefinition(); 1112 ed.setPath(typeName); 1113 list.add(0, ed); 1114 } 1115 int i = 1; 1116 while (i < list.size()) { 1117 String[] pathCurrent = list.get(i).getPath().split("\\."); 1118 String[] pathLast = list.get(i-1).getPath().split("\\."); 1119 int firstDiff = 0; // the first entry must be a match 1120 while (firstDiff < pathCurrent.length && firstDiff < pathLast.length && pathCurrent[firstDiff].equals(pathLast[firstDiff])) { 1121 firstDiff++; 1122 } 1123 if (!(isSibling(pathCurrent, pathLast, firstDiff) || isChild(pathCurrent, pathLast, firstDiff))) { 1124 // now work backwards down to lastMatch inserting missing path nodes 1125 ElementDefinition parent = findParent(list, i, list.get(i).getPath()); 1126 int parentDepth = Utilities.charCount(parent.getPath(), '.')+1; 1127 int childDepth = Utilities.charCount(list.get(i).getPath(), '.')+1; 1128 if (childDepth > parentDepth + 1) { 1129 String basePath = parent.getPath(); 1130 String baseId = parent.getId(); 1131 for (int index = parentDepth; index >= firstDiff; index--) { 1132 String mtail = makeTail(pathCurrent, parentDepth, index); 1133 ElementDefinition root = new ElementDefinition().setPath(basePath+"."+mtail); 1134 root.setId(baseId+"."+mtail); 1135 list.add(i, root); 1136 } 1137 } 1138 } 1139 i++; 1140 } 1141 } 1142 1143 1144 private ElementDefinition findParent(List<ElementDefinition> list, int i, String path) { 1145 while (i > 0 && !path.startsWith(list.get(i).getPath()+".")) { 1146 i--; 1147 } 1148 return list.get(i); 1149 } 1150 1151 private boolean isSibling(String[] pathCurrent, String[] pathLast, int firstDiff) { 1152 return pathCurrent.length == pathLast.length && firstDiff == pathCurrent.length-1; 1153 } 1154 1155 1156 private boolean isChild(String[] pathCurrent, String[] pathLast, int firstDiff) { 1157 return pathCurrent.length == pathLast.length+1 && firstDiff == pathLast.length; 1158 } 1159 1160 private String makeTail(String[] pathCurrent, int start, int index) { 1161 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("."); 1162 for (int i = start; i <= index; i++) { 1163 b.append(pathCurrent[i]); 1164 } 1165 return b.toString(); 1166 } 1167 1168 public StructureDefinition trimSnapshot(StructureDefinition profile) { 1169 // first pass: mark elements from the diff 1170 Stack<ElementDefinition> stack = new Stack<ElementDefinition>(); 1171 ElementDefinition edRoot = profile.getSnapshot().getElementFirstRep(); 1172 if (!edRoot.hasUserData(UserDataNames.SNAPSHOT_FROM_DIFF)) { 1173 stack.push(edRoot); 1174 for (int i = 1; i < profile.getSnapshot().getElement().size(); i++) { 1175 ElementDefinition ed = profile.getSnapshot().getElement().get(i); 1176 String cpath = ed.getPath(); 1177 boolean fromDiff = ed.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF); 1178 1179 String spath = stack.peek().getPath(); 1180 while (!(cpath.equals(spath) || cpath.startsWith(spath+"."))) { 1181 stack.pop(); 1182 spath = stack.peek().getPath(); 1183 } 1184 stack.push(ed); 1185 if (fromDiff) { 1186 for (int j = stack.size() - 1; j >= 0; j--) { 1187 if (stack.get(j).hasUserData(UserDataNames.SNAPSHOT_FROM_DIFF)) { 1188 break; 1189 } else { 1190 stack.get(j).setUserData(UserDataNames.SNAPSHOT_FROM_DIFF, true); 1191 } 1192 } 1193 } 1194 } 1195 } 1196 edRoot.setUserData(UserDataNames.SNAPSHOT_FROM_DIFF, true); 1197 1198 StructureDefinition res = new StructureDefinition(); 1199 res.setUrl(profile.getUrl()); 1200 res.setVersion(profile.getVersion()); 1201 res.setName(profile.getName()); 1202 res.setBaseDefinition(profile.getBaseDefinition()); 1203 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 1204 if (ed.hasUserData(UserDataNames.SNAPSHOT_FROM_DIFF)) { 1205 res.getSnapshot().getElement().add(ed); 1206 } 1207 } 1208 res.setWebPath(profile.getWebPath()); 1209 return res; 1210 } 1211 1212}