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