
001package org.hl7.fhir.r5.conformance.profile; 002 003import java.util.ArrayList; 004import java.util.HashSet; 005import java.util.List; 006import java.util.Set; 007 008import org.hl7.fhir.exceptions.DefinitionException; 009import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions; 010import org.hl7.fhir.r5.context.IWorkerContext; 011import org.hl7.fhir.r5.model.ElementDefinition; 012import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 013import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 014import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 015import org.hl7.fhir.r5.model.StructureDefinition; 016import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent; 017import org.hl7.fhir.r5.utils.TypesUtilities; 018import org.hl7.fhir.r5.utils.UserDataNames; 019import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 020import org.hl7.fhir.utilities.Utilities; 021import org.hl7.fhir.utilities.i18n.I18nConstants; 022 023/** 024 * when a slice is encountered, it may have additional details defined after the slice that must be merged into 025 * each of the slices. That's kind of multiple inheritance, and fiendishly complicated to add to the snapshot generator 026 * 027 * This class pre-processes the differential, finding the slices that have these trailing properties, and 028 * filling them out in the slices that follow 029 * 030 * There's potential problems here, mostly around slicing extensions (other kind of slicing isn't allowed) 031 * and also the merging logic might need to be sophisticated. 032 * 033 */ 034public class SnapshotGenerationPreProcessor { 035 036 037 public class ElementAnalysis { 038 private StructureDefinition structure; 039 private ElementDefinition element; 040 private String type; 041 public SourcedChildDefinitions children; 042 protected ElementAnalysis(StructureDefinition structure, ElementDefinition element, String type) { 043 super(); 044 this.structure = structure; 045 this.element = element; 046 this.type = type; 047 } 048 public StructureDefinition getStructure() { 049 return structure; 050 } 051 public ElementDefinition getElement() { 052 return element; 053 } 054 public SourcedChildDefinitions getChildren() { 055 return children; 056 } 057 public void setChildren(SourcedChildDefinitions children) { 058 this.children = children; 059 } 060 public String getType() { 061 return type; 062 } 063 public String summary() { 064 return element.getName()+":"+type; 065 } 066 } 067 068 public class SliceInfo { 069 SliceInfo parent; 070 String path; 071 boolean closed; 072 ElementDefinition slicer; 073 List<ElementDefinition> sliceStuff; 074 List<ElementDefinition> slices; 075 076 public SliceInfo(SliceInfo parent, ElementDefinition ed) { 077 this.parent = parent; 078 path = ed.getPath(); 079 slicer = ed; 080 sliceStuff = new ArrayList<>(); 081 if (parent != null) { 082 parent.add(ed); 083 } 084 } 085 086 public void newSlice(ElementDefinition ed) { 087 if (slices == null) { 088 slices = new ArrayList<ElementDefinition>(); 089 } 090 slices.add(ed); 091 if (parent != null) { 092 parent.add(ed); 093 } 094 } 095 public void add(ElementDefinition ed) { 096 if (slices == null) { 097 sliceStuff.add(ed); 098 } 099 if (parent != null) { 100 parent.add(ed); 101 } 102 } 103 } 104 105 private IWorkerContext context; 106 private ProfileUtilities utils; 107 Set<String> typeNames; 108 private List<SliceInfo> slicings = new ArrayList<>(); 109 110 public SnapshotGenerationPreProcessor(ProfileUtilities utils) { 111 super(); 112 this.utils = utils; 113 this.context = utils.getContext(); 114 } 115 116 public void process(StructureDefinitionDifferentialComponent diff) { 117 // first pass, divide it up 118 for (int cursor = 0; cursor < diff.getElement().size(); cursor++) { 119 ElementDefinition ed = diff.getElement().get(cursor); 120 121 SliceInfo si = getSlicing(ed); 122 if (si == null) { 123 if (ed.hasSlicing() && !isExtensionSlicing(ed)) { 124 si = new SliceInfo(null, ed); 125 slicings.add(si); 126 } else { 127 // ignore this 128 } 129 } else { 130 if (ed.hasSliceName() && ed.getPath().equals(si.path)) { 131 si.newSlice(ed); 132 } else if (ed.hasSlicing() && !isExtensionSlicing(ed)) { 133 si = new SliceInfo(si, ed); 134 slicings.add(si); 135 } else { 136 si.add(ed); 137 } 138 } 139 } 140 141 for (SliceInfo si : slicings) { 142 if (!si.sliceStuff.isEmpty() && si.slices != null) { 143 for (ElementDefinition ed : si.sliceStuff) { 144 if (ed.hasSlicing() && !isExtensionSlicing(ed)) { 145 String message = context.formatMessage(I18nConstants.UNSUPPORTED_SLICING_COMPLEXITY, si.slicer.getPath(), ed.getPath(), ed.getSlicing().summary()); 146 System.out.println(message); 147 return; 148 } 149 } 150 } 151 } 152 153 // working backward 154 for (int i = slicings.size() - 1; i >= 0; i--) { 155 SliceInfo si = slicings.get(i); 156 if (!si.sliceStuff.isEmpty() && si.slices != null) { 157 // for each actual slice, we need to merge sliceStuff in 158 for (ElementDefinition slice : si.slices) { 159 mergeElements(diff.getElement(), si.sliceStuff, slice, si.slicer); 160 } 161 } else { 162 // we just ignore these - nothing to do 163 } 164 } 165 166 } 167 168 private void mergeElements(List<ElementDefinition> elements, List<ElementDefinition> allSlices, ElementDefinition slice, ElementDefinition slicer) { 169 // we have 170 // elements - the list of all the elements 171 // allSlices which is the content defined for all the slices 172 // slice -the anchor element for the slice 173 174 int sliceIndex = elements.indexOf(slice); 175 int startOfSlice = sliceIndex + 1; 176 int endOfSlice = findEndOfSlice(elements, slice); 177 178 Set<String> missing = new HashSet<>(); 179 // the simple case is that all the stuff in allSlices exists between startOfSlice and endOfSlice 180 boolean allFound = true; 181 for (int i = 0; i < allSlices.size(); i++) { 182 boolean found = false; 183 for (int j = startOfSlice; j <= endOfSlice; j++) { 184 if (elements.get(j).getPath().equals(allSlices.get(i).getPath())) { 185 found = true; 186 break; 187 } 188 } 189 if (!found) { 190 missing.add(allSlices.get(i).getPath()); 191 allFound = false; 192 } 193 } 194 195 if (allFound) { 196 // then we just merge it in 197 for (int j = startOfSlice; j <= endOfSlice; j++) { 198 for (int i = 0; i < allSlices.size(); i++) { 199 if (elements.get(j).getPath().equals(allSlices.get(i).getPath())) { 200 merge(elements.get(j), allSlices.get(i)); 201 } 202 } 203 } 204 } else { 205 Set<ElementDefinition> handled = new HashSet<>(); 206 207 // merge the simple stuff 208 for (int j = startOfSlice; j <= endOfSlice; j++) { 209 for (int i = 0; i < allSlices.size(); i++) { 210 if (elements.get(j).getPath().equals(allSlices.get(i).getPath())) { 211 handled.add(allSlices.get(i)); 212 merge(elements.get(j), allSlices.get(i)); 213 } 214 } 215 } 216 217 // we have a lot of work to do 218 // the challenge is that the things missing from startOfSlice..endOfSlice have to injected in the correct order 219 // which means that we need to know the definitions 220 // and is extra tricky because we're sparse. so we just use the stated path 221 for (ElementDefinition ed : allSlices) { 222 if (!handled.contains(ed)) { 223 List<ElementAnalysis> edDef = analysePath(ed); 224 String id = ed.getId().replace(slicer.getId(), slice.getId()); 225 int index = determineInsertionPoint(elements, startOfSlice, endOfSlice, id, ed.getPath(), edDef); 226 ElementDefinition edc = ed.copy(); 227 edc.setUserData(UserDataNames.SNAPSHOT_PREPROCESS_INJECTED, true); 228 edc.setId(id); 229 elements.add(index, edc); 230 endOfSlice++; 231 } 232 } 233 } 234 235 } 236 237 private int determineInsertionPoint(List<ElementDefinition> elements, int startOfSlice, int endOfSlice, String id, String path, List<ElementAnalysis> edDef) { 238 // we work backwards through the id, looking for peers (this is the only way we can manage slicing) 239 String[] p = id.split("\\."); 240 for (int i = p.length-1; i >= 1; i--) { 241 String subId = p[0]; 242 for (int j = 1; j <= i; j++) { 243 subId += "."+p[j]; 244 } 245 List<ElementDefinition> peers = findPeers(elements, startOfSlice, endOfSlice, subId); 246 if (!peers.isEmpty()) { 247 // Once we find some, we figure out the insertion point - before one of them, or after the last? 248 for (ElementDefinition ed : peers) { 249 if (comesAfterThis(id, path, edDef, ed)) { 250 return elements.indexOf(ed); 251 } 252 } 253 return elements.indexOf(peers.get(peers.size() -1))+1; 254 } 255 } 256 return endOfSlice+1; 257 } 258 259 private List<ElementDefinition> findPeers(List<ElementDefinition> elements, int startOfSlice, int endOfSlice, String subId) { 260 List<ElementDefinition> peers = new ArrayList<>(); 261 for (int i = startOfSlice; i <= endOfSlice; i++) { 262 ElementDefinition ed = elements.get(i); 263 if (ed.getId().startsWith(subId)) { 264 peers.add(ed); 265 } 266 } 267 return peers; 268 } 269 270 private String summary(List<ElementAnalysis> edDef) { 271 List<String> s = new ArrayList<>(); 272 for (ElementAnalysis ed : edDef) { 273 s.add(ed.summary()); 274 } 275 276 return CommaSeparatedStringBuilder.join(",", s); 277 } 278 279 private boolean comesAfterThis(String id, String path, List<ElementAnalysis> edDef, ElementDefinition ed) { 280 String[] p1 = id.split("\\."); 281 String[] p2 = ed.getId().split("\\."); 282 for (int i = 0; i < Integer.min(p1.length, p2.length); i++) { 283 if (!p1[i].equals(p2[i])) { 284 ElementAnalysis sed = edDef.get(i-1); 285 int i1 = indexOfName(sed, p1[i]); 286 int i2 = indexOfName(sed, p2[i]); 287 return i1 < i2; 288 } else { 289 // well, we just go on 290 } 291 } 292 return p1.length < p2.length; 293 } 294 295 private int indexOfName(ElementAnalysis sed, String name) { 296 if (name.contains(":")) { 297 name = name.substring(0, name.indexOf(":")); 298 } 299 for (int i = 0; i < sed.getChildren().getList().size(); i++) { 300 if (name.equals(sed.getChildren().getList().get(i).getName())) { 301 return i; 302 } 303 } 304 return -1; 305 } 306 307 private List<ElementAnalysis> analysePath(ElementDefinition ed) { 308 List<ElementAnalysis> res = new ArrayList<>(); 309 for (String pn : ed.getPath().split("\\.")) { 310 if (res.isEmpty()) { 311 StructureDefinition sd = context.fetchTypeDefinition(pn); 312 if (sd == null) { 313 String message = context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, pn, ed.getId()); 314 throw new DefinitionException(message); 315 } 316 res.add(new ElementAnalysis(sd, sd.getSnapshot().getElementFirstRep(), null)); 317 } else { 318 ElementAnalysis sed = res.get(res.size()-1); 319 sed.setChildren(utils.getChildMap(sed.getStructure(), sed.getElement(), true, sed.getType())); 320 ElementDefinition t = null; 321 String type = null; 322 for (ElementDefinition child : sed.getChildren().getList()) { 323 if (pn.equals(child.getName())) { 324 t = child; 325 break; 326 } 327 if (child.getName().endsWith("[x]")) { 328 String rn = child.getName().substring(0, child.getName().length()-3); 329 if (pn.startsWith(rn)) { 330 t = child; 331 String tn = pn.substring(rn.length()); 332 if (TypesUtilities.isPrimitive(Utilities.uncapitalize(tn))) { 333 type = Utilities.uncapitalize(tn); 334 } else { 335 type = tn; 336 } 337 break; 338 } 339 } 340 } 341 if (t == null) { 342 String message = context.formatMessage(I18nConstants.UNKNOWN_PROPERTY, pn, ed.getPath()); 343 throw new DefinitionException("Unknown path "+pn+" in path "+ed.getPath()+": "+message); 344 } else { 345 res.add(new ElementAnalysis(sed.getChildren().getSource(), t, type)); 346 } 347 } 348 } 349 return res; 350 } 351 352 private int findEndOfSlice(List<ElementDefinition> elements, ElementDefinition slice) { 353 for (int i = elements.indexOf(slice); i < elements.size(); i++) { 354 ElementDefinition e = elements.get(i); 355 if (e.getPath().length() < slice.getPath().length() || (e.getPath().equals(slice.getPath()) && !slice.getSliceName().equals(e.getSliceName()))) { 356 return i-1; 357 } 358 } 359 return elements.size() - 1; 360 } 361 362 private void merge(ElementDefinition focus, ElementDefinition base) { 363 if (base.hasLabel() && !focus.hasLabel()) { 364 focus.setLabelElement(base.getLabelElement()); 365 } 366 if (base.hasCode() && !focus.hasCode()) { 367 focus.getCode().addAll(base.getCode()); 368 } 369 if (base.hasShort() && !focus.hasShort()) { 370 focus.setShortElement(base.getShortElement()); 371 } 372 if (base.hasDefinition() && !focus.hasDefinition()) { 373 focus.setDefinitionElement(base.getDefinitionElement()); 374 } 375 if (base.hasComment() && !focus.hasComment()) { 376 focus.setCommentElement(base.getCommentElement()); 377 } 378 if (base.hasRequirements() && !focus.hasRequirements()) { 379 focus.setRequirementsElement(base.getRequirementsElement()); 380 } 381 if (base.hasAlias() && !focus.hasAlias()) { 382 focus.getAlias().addAll(base.getAlias()); 383 } 384 if (base.hasMin() && !focus.hasMin()) { 385 focus.setMinElement(base.getMinElement()); 386 } 387 if (base.hasMax() && !focus.hasMax()) { 388 focus.setMaxElement(base.getMaxElement()); 389 } 390 if (base.hasType() && !focus.hasType()) { 391 focus.getType().addAll(base.getType()); 392 } 393 if (base.hasDefaultValue() && !focus.hasDefaultValue()) { 394 focus.setDefaultValue(base.getDefaultValue()); 395 } 396 if (base.hasMeaningWhenMissing() && !focus.hasMeaningWhenMissing()) { 397 focus.setMeaningWhenMissingElement(base.getMeaningWhenMissingElement()); 398 } 399 if (base.hasOrderMeaning() && !focus.hasOrderMeaning()) { 400 focus.setOrderMeaningElement(base.getOrderMeaningElement()); 401 } 402 if (base.hasFixed() && !focus.hasFixed()) { 403 focus.setFixed(base.getFixed()); 404 } 405 if (base.hasPattern() && !focus.hasPattern()) { 406 focus.setPattern(base.getPattern()); 407 } 408 if (base.hasExample() && !focus.hasExample()) { 409 focus.getExample().addAll(base.getExample()); 410 } 411 if (base.hasMinValue() && !focus.hasMinValue()) { 412 focus.setMinValue(base.getMinValue()); 413 } 414 if (base.hasMaxValue() && !focus.hasMaxValue()) { 415 focus.setMaxValue(base.getMaxValue()); 416 } 417 if (base.hasMaxLength() && !focus.hasMaxLength()) { 418 focus.setMaxLengthElement(base.getMaxLengthElement()); 419 } 420 if (base.hasConstraint() && !focus.hasConstraint()) { 421 focus.getConstraint().addAll(base.getConstraint()); 422 } 423 if (base.hasMustHaveValue() && !focus.hasMustHaveValue()) { 424 focus.setMustHaveValueElement(base.getMustHaveValueElement()); 425 } 426 if (base.hasValueAlternatives() && !focus.hasValueAlternatives()) { 427 focus.getValueAlternatives().addAll(base.getValueAlternatives()); 428 } 429 if (base.hasMustSupport() && !focus.hasMustSupport()) { 430 focus.setMustSupportElement(base.getMustSupportElement()); 431 } 432 if (base.hasIsModifier() && !focus.hasIsModifier()) { 433 focus.setIsModifierElement(base.getIsModifierElement()); 434 } 435 if (base.hasIsModifierReason() && !focus.hasIsModifierReason()) { 436 focus.setIsModifierReasonElement(base.getIsModifierReasonElement()); 437 } 438 if (base.hasIsSummary() && !focus.hasIsSummary()) { 439 focus.setIsSummaryElement(base.getIsSummaryElement()); 440 } 441 if (base.hasBinding() && !focus.hasBinding()) { 442 focus.setBinding(base.getBinding()); 443 } 444 } 445 446 private boolean isExtensionSlicing(ElementDefinition ed) { 447 if (!Utilities.existsInList(ed.getName(), "extension", "modiferExtension")) { 448 return false; 449 } 450 if (ed.getSlicing().getRules() != SlicingRules.OPEN || (!ed.getSlicing().hasOrdered() || ed.getSlicing().getOrdered()) || ed.getSlicing().getDiscriminator().size() != 1) { 451 return false; 452 } 453 ElementDefinitionSlicingDiscriminatorComponent d = ed.getSlicing().getDiscriminatorFirstRep(); 454 return d.getType() == DiscriminatorType.VALUE && "url".equals(d.getPath()); 455 } 456 457 private SliceInfo getSlicing(ElementDefinition ed) { 458 for (int i = slicings.size() - 1; i >= 0; i--) { 459 SliceInfo si = slicings.get(i); 460 if (!si.closed) { 461 if (si.path.length() > ed.getPath().length()) { 462 si.closed = true; 463 } else if (ed.getPath().startsWith(si.path)) { 464 return si; 465 } 466 } 467 } 468 return null; 469 } 470 471}