001package org.hl7.fhir.r5.conformance.profile; 002 003import java.util.ArrayList; 004import java.util.Iterator; 005import java.util.List; 006import java.util.Set; 007 008import org.hl7.fhir.exceptions.DefinitionException; 009import org.hl7.fhir.exceptions.FHIRException; 010import org.hl7.fhir.r5.conformance.ElementRedirection; 011import org.hl7.fhir.r5.model.Base; 012import org.hl7.fhir.r5.model.CanonicalType; 013import org.hl7.fhir.r5.model.ElementDefinition; 014import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 015import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; 016import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 017import org.hl7.fhir.r5.model.OperationOutcome.IssueType; 018import org.hl7.fhir.r5.model.StructureDefinition; 019import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 020import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; 021import org.hl7.fhir.r5.utils.ToolingExtensions; 022import org.hl7.fhir.utilities.Utilities; 023import org.hl7.fhir.utilities.VersionUtilities; 024import org.hl7.fhir.utilities.i18n.I18nConstants; 025import org.hl7.fhir.utilities.validation.ValidationMessage; 026import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 027import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 028 029import lombok.AccessLevel; 030import lombok.AllArgsConstructor; 031import lombok.Getter; 032import lombok.With; 033 034@AllArgsConstructor(access = AccessLevel.PRIVATE) 035public class ProfilePathProcessor { 036 037 @Getter 038 protected final ProfileUtilities profileUtilities; 039 040 @Getter 041 @With 042 final String debugIndent; 043 044 @Getter 045 @With 046 final StructureDefinition.StructureDefinitionSnapshotComponent result; 047 048 @Getter 049 @With 050 final StructureDefinition.StructureDefinitionDifferentialComponent differential; 051 052 @Getter 053 @With 054 final int baseLimit; 055 056 @Getter 057 @With 058 final int diffLimit; 059 060 @Getter 061 @With 062 final String url; 063 064 @Getter 065 @With 066 final String webUrl; 067 068 @Getter 069 @With 070 final String profileName; 071 072 @Getter 073 @With 074 final String contextPathSource; 075 076 @Getter 077 @With 078 final String contextPathTarget; 079 080 @Getter 081 @With 082 final boolean trimDifferential; 083 084 @Getter 085 @With 086 final List<ElementRedirection> redirector; 087 088 @Getter 089 @With 090 final StructureDefinition sourceStructureDefinition; 091 092 @Getter 093 @With 094 final StructureDefinition derived; 095 096 @Getter 097 @With 098 final PathSlicingParams slicing; 099 100 101 private ProfilePathProcessor( 102 ProfileUtilities profileUtilities 103 ) { 104 this.profileUtilities = profileUtilities; 105 debugIndent = ""; 106 this.result = null; 107 this.differential = null; 108 this.baseLimit = 0; 109 this.diffLimit = 0; 110 this.url = null; 111 this.webUrl = null; 112 this.profileName = null; 113 this.contextPathSource = null; 114 this.contextPathTarget = null; 115 this.trimDifferential = false; 116 this.redirector = null; 117 this.sourceStructureDefinition = null; 118 this.derived = null; 119 this.slicing = null; 120 } 121 122 public static ProfilePathProcessor getInstance( ProfileUtilities profileUtilities) { 123 return new ProfilePathProcessor(profileUtilities); 124 } 125 126 public ProfilePathProcessor incrementDebugIndent() { 127 return this.withDebugIndent(this.debugIndent + " ".repeat(2)); 128 } 129 130 131 protected static void processPaths(ProfileUtilities profileUtilities, StructureDefinition base, StructureDefinition derived, String url, String webUrl, StructureDefinition.StructureDefinitionDifferentialComponent differential, StructureDefinition.StructureDefinitionSnapshotComponent baseSnapshot, MappingAssistant mapHelper) { 132 133 ProfilePathProcessorState cursors = new ProfilePathProcessorState( 134 baseSnapshot, 135 0, 136 0, 137 base.getUrl(), 138 null); 139 140 141 getInstance(profileUtilities) 142 .withResult(derived.getSnapshot()) 143 .withDifferential(differential) 144 .withBaseLimit(baseSnapshot.getElement().size() - 1) 145 .withDiffLimit(derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size() - 1 : -1) 146 .withUrl(url) 147 .withWebUrl(webUrl) 148 .withProfileName(derived.present()) 149 .withContextPathSource(null) 150 .withContextPathTarget(null) 151 .withTrimDifferential(false) 152 .withRedirector(new ArrayList<ElementRedirection>()) 153 .withSourceStructureDefinition(base) 154 .withDerived(derived) 155 .withSlicing(new PathSlicingParams()).processPaths(cursors, mapHelper); 156 157 } 158 159 /** 160 * @param cursors 161 * @param mapHelper 162 * @throws DefinitionException, FHIRException 163 * @throws Exception 164 */ 165 private ElementDefinition processPaths(final ProfilePathProcessorState cursors, MappingAssistant mapHelper) throws FHIRException { 166 debugProcessPathsEntry(cursors); 167 ElementDefinition res = null; 168 List<TypeSlice> typeList = new ArrayList<>(); 169 // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries) 170 while (cursors.baseCursor <= getBaseLimit() && cursors.baseCursor < cursors.base.getElement().size()) { 171 // get the current focus of the base, and decide what to do 172 ElementDefinition currentBase = cursors.base.getElement().get(cursors.baseCursor); 173 String currentBasePath = profileUtilities.fixedPathSource(getContextPathSource(), currentBase.getPath(), getRedirector()); 174 debugProcessPathsIteration(cursors, currentBasePath); 175 checkDiffAssignedAndCursor(cursors); 176 List<ElementDefinition> diffMatches = profileUtilities.getDiffMatches(getDifferential(), currentBasePath, cursors.diffCursor, getDiffLimit(), getProfileName()); // get a list of matching elements in scope 177 178 // in the simple case, source is not sliced. 179 if (!currentBase.hasSlicing() || currentBasePath.equals(getSlicing().getPath())) 180 { 181 ElementDefinition currentRes = processSimplePath(currentBase, currentBasePath, diffMatches, typeList, cursors, mapHelper); 182 if (res == null) { 183 res = currentRes; 184 } 185 } 186 else { 187 processPathWithSlicedBase(currentBase, currentBasePath, diffMatches, typeList, cursors, mapHelper); 188 } 189 } 190 191 int i = 0; 192 for (ElementDefinition e : getResult().getElement()) { 193 i++; 194 if (e.hasMinElement() && e.getMinElement().getValue() == null) 195 throw new Error(profileUtilities.getContext().formatMessage(I18nConstants.NULL_MIN)); 196 } 197 return res; 198 } 199 200 private void checkDiffAssignedAndCursor(ProfilePathProcessorState cursors) { 201// int i = 0; 202// List<ElementDefinition> list = getDifferential().getElement(); 203// for (ElementDefinition ed : list) { 204// boolean assigned = ed.hasUserData("derived.pointer"); 205// if (i < cursors.diffCursor) { 206// if (!assigned) { 207// throw new Error("what?"); 208// } 209// } else if (i > cursors.diffCursor) { 210// if (assigned) { 211// throw new Error("what!?"); 212// } 213// } 214// i++; 215// } 216 217 } 218 219 private void debugProcessPathsIteration(ProfilePathProcessorState cursors, String currentBasePath) { 220 if (profileUtilities.isDebug()) { 221 System.out.println(getDebugIndent() + " - " + currentBasePath + ": "+ 222 "base = " + cursors.baseCursor + " (" + profileUtilities.descED(cursors.base.getElement(), cursors.baseCursor) + ") to " + getBaseLimit() +" (" + profileUtilities.descED(cursors.base.getElement(), getBaseLimit()) + "), "+ 223 "diff = " + cursors.diffCursor + " (" + profileUtilities.descED(getDifferential().getElement(), cursors.diffCursor) + ") to " + getDiffLimit() + " (" + profileUtilities.descED(getDifferential().getElement(), getDiffLimit()) + ") " + 224 "(slicingDone = " + getSlicing().isDone() + ") (diffpath= " + (getDifferential().getElement().size() > cursors.diffCursor ? getDifferential().getElement().get(cursors.diffCursor).getPath() : "n/a") + ")"); 225 String path = cursors.diffCursor >=0 && cursors.diffCursor < getDifferential().getElement().size() ? getDifferential().getElement().get(cursors.diffCursor).present() : null; 226 } 227 228 } 229 230 private void debugProcessPathsEntry(ProfilePathProcessorState cursors) { 231 if (profileUtilities.isDebug()) { 232 System.out.println(getDebugIndent() + "PP @ " + cursors.resultPathBase + " / " + getContextPathSource() + " : base = " + cursors.baseCursor + " to " + getBaseLimit() + ", diff = " + cursors.diffCursor + " to " + getDiffLimit() + " (slicing = " + getSlicing().isDone() + ", k " + (getRedirector() == null ? "null" : getRedirector().toString()) + ")"); 233 } 234 } 235 236 237 public ElementDefinition processSimplePath( 238 final ElementDefinition currentBase, 239 final String currentBasePath, 240 final List<ElementDefinition> diffMatches, 241 final List<TypeSlice> typeList, 242 final ProfilePathProcessorState cursors, MappingAssistant mapHelper) throws FHIRException { 243 ElementDefinition res = null; 244 245 // the differential doesn't say anything about this item 246 // so we just copy it in 247 if (diffMatches.isEmpty()) 248 processSimplePathWithEmptyDiffMatches(currentBase, currentBasePath, diffMatches, cursors, mapHelper); 249 // one matching element in the differential 250 else if (oneMatchingElementInDifferential(getSlicing().isDone(), currentBasePath, diffMatches)) 251 res = processSimplePathWithOneMatchingElementInDifferential(currentBase, currentBasePath, diffMatches, cursors, mapHelper); 252 else if (profileUtilities.diffsConstrainTypes(diffMatches, currentBasePath, typeList)) 253 processSimplePathWhereDiffsConstrainTypes(currentBasePath, diffMatches, typeList, cursors, mapHelper); 254 else 255 processSimplePathDefault(currentBase, currentBasePath, diffMatches, cursors, mapHelper); 256 257 258 return res; 259 } 260 261 private void processSimplePathDefault(ElementDefinition currentBase, String currentBasePath, List<ElementDefinition> diffMatches, ProfilePathProcessorState cursors, MappingAssistant mapHelper) { 262 // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct 263 if (!profileUtilities.unbounded(currentBase) && !profileUtilities.isSlicedToOneOnly(diffMatches.get(0))) 264 // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1 265 // (but you might do that in order to split up constraints by type) 266 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ATTEMPT_TO_A_SLICE_AN_ELEMENT_THAT_DOES_NOT_REPEAT__FROM__IN_, currentBase.getPath(), currentBase.getPath(), cursors.contextName, diffMatches.get(0).getId(), profileUtilities.sliceNames(diffMatches))); 267 if (!diffMatches.get(0).hasSlicing() && !profileUtilities.isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error 268 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.DIFFERENTIAL_DOES_NOT_HAVE_A_SLICE__B_OF_____IN_PROFILE_, currentBase.getPath(), cursors.baseCursor, getBaseLimit(), cursors.diffCursor, getDiffLimit(), getUrl(), currentBasePath)); 269 270 // well, if it passed those preconditions then we slice the dest. 271 int start = 0; 272 int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); 273// if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) { 274 ElementDefinition slicerElement; 275 if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && (newBaseLimit > cursors.baseCursor || getDifferential().getElement().indexOf(diffMatches.get(1)) > getDifferential().getElement().indexOf(diffMatches.get(0)) + 1)) { // there's a default set before the slices 276 int newDiffCursor = getDifferential().getElement().indexOf(diffMatches.get(0)); 277 int newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor); 278 ElementDefinition e = 279 this 280 .incrementDebugIndent() 281 .withBaseLimit(newBaseLimit) 282 .withDiffLimit(newDiffLimit) 283 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, 0)).withSlicing(new PathSlicingParams(true, null, null)) 284 .processPaths(new ProfilePathProcessorState(cursors.base, cursors.baseCursor, newDiffCursor, cursors.contextName, cursors.resultPathBase), mapHelper); 285 if (e == null) 286 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.DID_NOT_FIND_SINGLE_SLICE_, diffMatches.get(0).getPath())); 287 e.setSlicing(diffMatches.get(0).getSlicing()); 288 slicerElement = e; 289 start++; 290 } else { 291 // we're just going to accept the differential slicing at face value 292 ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true); 293 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 294 profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); 295 296 if (!diffMatches.get(0).hasSlicing()) { 297 outcome.setSlicing(profileUtilities.makeExtensionSlicing()); 298 outcome.setUserData("auto-added-slicing", true); 299 } else { 300 outcome.setSlicing(diffMatches.get(0).getSlicing().copy()); 301 for (int i = 1; i < diffMatches.size(); i++) { 302 if (diffMatches.get(i).hasSlicing()) { 303 if (!slicingMatches(diffMatches.get(0).getSlicing(), diffMatches.get(i).getSlicing())) { 304 profileUtilities.getMessages().add(new ValidationMessage(Source.InstanceValidator, ValidationMessage.IssueType.BUSINESSRULE, diffMatches.get(0).getPath(), 305 profileUtilities.getContext().formatMessage(I18nConstants.ATTEMPT_TO_CHANGE_SLICING, diffMatches.get(0).getId(), slicingSummary(diffMatches.get(0).getSlicing()), diffMatches.get(i).getId(), slicingSummary(diffMatches.get(i).getSlicing())), 306 ValidationMessage.IssueSeverity.ERROR)); 307 } else { 308 profileUtilities.getMessages().add(new ValidationMessage(Source.InstanceValidator, ValidationMessage.IssueType.BUSINESSRULE, diffMatches.get(0).getPath(), 309 profileUtilities.getContext().formatMessage(I18nConstants.ATTEMPT_TO_CHANGE_SLICING, diffMatches.get(0).getId(), diffMatches.get(i).getId()), 310 IssueSeverity.INFORMATION)); 311 312 } 313 } 314 } 315 } 316 if (cursors.resultPathBase != null) { 317 if (!outcome.getPath().startsWith(cursors.resultPathBase)) 318 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); 319 } 320 debugCheck(outcome); 321 getResult().getElement().add(outcome); 322 slicerElement = outcome; 323 324 // differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice. 325 if (!diffMatches.get(0).hasSliceName()) { 326 profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(),getSourceStructureDefinition(), getDerived(), diffPath(diffMatches.get(0)), mapHelper); 327 profileUtilities.removeStatusExtensions(outcome); 328 if (!outcome.hasContentReference() && !outcome.hasType() && outcome.getPath().contains(".")) { 329 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.NOT_DONE_YET)); 330 } 331 if (profileUtilities.hasInnerDiffMatches(getDifferential(), currentBasePath, cursors.diffCursor, getDiffLimit(), cursors.base.getElement(), false)) { 332 if (baseHasChildren(cursors.base, currentBase)) { // not a new type here 333 if (cursors.diffCursor == 0) { 334 throw new DefinitionException("Error: The profile has slicing at the root ('"+currentBase.getPath()+"'), which is illegal"); 335 } else { 336 throw new Error("This situation is not yet handled (constrain slicing to 1..1 and fix base slice for inline structure - please report issue to grahame@fhir.org along with a test case that reproduces this error (@ " + currentBasePath + " | " + currentBase.getPath() + ")"); 337 } 338 } else { 339 StructureDefinition dt = profileUtilities.getTypeForElement(getDifferential(), cursors.diffCursor, getProfileName(), diffMatches, outcome, getWebUrl(), getDerived()); 340 cursors.contextName = dt.getUrl(); 341 cursors.diffCursor++; 342 start = cursors.diffCursor; 343 while (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + ".")) 344 cursors.diffCursor++; 345 cursors.diffCursor--; 346 347 this.incrementDebugIndent() 348 .withBaseLimit( dt.getSnapshot().getElement().size() - 1) 349 .withDiffLimit(cursors.diffCursor) 350 .withWebUrl(profileUtilities.getWebUrl(dt, getWebUrl())) 351 .withContextPathSource(currentBasePath) 352 .withContextPathTarget(outcome.getPath()).withSlicing(new PathSlicingParams()) /* starting again on the data type, but skip the root */ 353 . processPaths(new ProfilePathProcessorState(dt.getSnapshot(), 1 /* starting again on the data type, but skip the root */, start, 354 cursors.contextName, cursors.resultPathBase), mapHelper); 355 } 356 } 357 start++; 358 // result.getElement().remove(result.getElement().size()-1); 359 } else 360 profileUtilities.checkExtensionDoco(outcome); 361 } 362 // now, for each entry in the diff matches, we're going to process the base item 363 // our processing scope for base is all the children of the current path 364 int newDiffCursor = cursors.diffCursor; 365 int newDiffLimit = cursors.diffCursor; 366 for (int i = start; i < diffMatches.size(); i++) { 367 // our processing scope for the differential is the item in the list, and all the items before the next one in the list 368 newDiffCursor = getDifferential().getElement().indexOf(diffMatches.get(i)); 369 newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor); 370 371 // now we process the base scope repeatedly for each instance of the item in the differential list 372 373 this 374 .incrementDebugIndent() 375 .withBaseLimit(newBaseLimit) 376 .withDiffLimit(newDiffLimit) 377 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, i)) 378 .withSlicing(new PathSlicingParams(true, slicerElement, null)) 379 .processPaths(new ProfilePathProcessorState(cursors.base, cursors.baseCursor, newDiffCursor, cursors.contextName, cursors.resultPathBase), mapHelper); 380 } 381 // ok, done with that - next in the base list 382 cursors.baseCursor = newBaseLimit + 1; 383 cursors.diffCursor = newDiffLimit + 1; 384 } 385 386 private String diffPath(ElementDefinition ed) { 387 return "StructureDefinition.differential.element["+differential.getElement().indexOf(ed)+"]"; 388 } 389 390 private String slicingSummary(ElementDefinitionSlicingComponent s) { 391 return s.toString(); 392 } 393 394 private boolean slicingMatches(ElementDefinitionSlicingComponent s1, ElementDefinitionSlicingComponent s2) { 395 if ((!s1.hasOrdered() && s2.hasOrdered()) || (s1.hasOrdered() && s2.hasOrdered() && !Base.compareDeep(s1.getOrderedElement(), s2.getOrderedElement(), false))) { 396 return false; 397 } 398 if ((!s1.hasRules() && s2.hasRules()) || (s1.hasRules() && s2.hasRules() && !Base.compareDeep(s1.getRulesElement(), s2.getRulesElement(), false))) { 399 return false; 400 } 401 return Base.compareDeep(s1.getDiscriminator(), s2.getDiscriminator(), false); 402 } 403 404 private void processSimplePathWhereDiffsConstrainTypes(String currentBasePath, List<ElementDefinition> diffMatches, List<TypeSlice> typeList, ProfilePathProcessorState cursors, MappingAssistant mapHelper) { 405 int start = 0; 406 int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); 407 int newDiffCursor = getDifferential().getElement().indexOf(diffMatches.get(0)); 408 ElementDefinition elementToRemove = null; 409 boolean shortCut = !typeList.isEmpty() && typeList.get(0).getType() != null; 410 // we come here whether they are sliced in the diff, or whether the short cut is used. 411 String path = diffMatches.get(0).getPath(); 412 if (shortCut) { 413 // this is the short cut method, we've just dived in and specified a type slice. 414 // in R3 (and unpatched R4, as a workaround right now... 415 if (!VersionUtilities.isR4Plus(profileUtilities.getContext().getVersion()) || !profileUtilities.isNewSlicingProcessing()) { // newSlicingProcessing is a work around for editorial loop dependency 416 // we insert a cloned element with the right types at the start of the diffMatches 417 ElementDefinition ed = new ElementDefinition(); 418 ed.setPath(profileUtilities.determineTypeSlicePath(path, currentBasePath)); 419 for (TypeSlice ts : typeList) 420 ed.addType().setCode(ts.getType()); 421 ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); 422 ed.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); 423 ed.getSlicing().setRules(ElementDefinition.SlicingRules.CLOSED); 424 ed.getSlicing().setOrdered(false); 425 diffMatches.add(0, ed); 426 getDifferential().getElement().add(newDiffCursor, ed); 427 elementToRemove = ed; 428 } else { 429 // as of R4, this changed; if there's no slice, there's no constraint on the slice types, only one the type. 430 // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type. 431 // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element 432 ElementDefinition ed = new ElementDefinition(); 433 ed.setPath(profileUtilities.determineTypeSlicePath(path, currentBasePath)); 434 ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); 435 ed.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); 436 ed.getSlicing().setRules(ElementDefinition.SlicingRules.CLOSED); 437 ed.getSlicing().setOrdered(false); 438 diffMatches.add(0, ed); 439 getDifferential().getElement().add(newDiffCursor, ed); 440 elementToRemove = ed; 441 } 442 } else { // if it's not a short cut, then the path has to be correct 443 String t1 = currentBasePath.substring(currentBasePath.lastIndexOf(".")+1); 444 String t2 = path.substring(path.lastIndexOf(".")+1); 445 if (!t1.equals(t2)) { 446 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ED_PATH_WRONG_TYPE_MATCH, path.replace(t2, t1), path)); 447 } 448 449 } 450 int newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor); 451 // the first element is setting up the slicing 452 453 if (diffMatches.get(0).getSlicing().hasOrdered()) { 454 if (diffMatches.get(0).getSlicing().getOrdered()) { 455 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGORDERED__TRUE, currentBasePath, getUrl())); 456 } 457 } 458 if (diffMatches.get(0).getSlicing().hasDiscriminator()) { 459 if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1) { 460 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORCOUNT__1, currentBasePath, getUrl())); 461 } 462 if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != ElementDefinition.DiscriminatorType.TYPE) { 463 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORTYPE__TYPE, currentBasePath, getUrl())); 464 } 465 if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath())) { 466 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORPATH__THIS, currentBasePath, getUrl())); 467 } 468 } 469 // check the slice names too while we're at it... 470 for (TypeSlice ts : typeList) { 471 if (ts.getType() != null) { 472 String tn = profileUtilities.rootName(currentBasePath) + Utilities.capitalize(ts.getType()); 473 if (!ts.defn.hasSliceName()) { 474 ts.defn.setSliceName(tn); 475 } else if (!ts.defn.getSliceName().equals(tn)) { 476 if (profileUtilities.isAutoFixSliceNames()) { 477 ts.defn.setSliceName(tn); 478 } else { 479 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_NAME_MUST_BE__BUT_IS_, (!Utilities.noString(getContextPathSource()) ? getContextPathSource() : currentBasePath), tn, ts.defn.getSliceName())); 480 } 481 } 482 if (!ts.defn.hasType()) { 483 ts.defn.addType().setCode(ts.type); 484 } else if (ts.defn.getType().size() > 1) { 485 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_MORE_THAN_ONE_TYPE_, (!Utilities.noString(getContextPathSource()) ? getContextPathSource() : currentBasePath), tn, ts.defn.typeSummary())); 486 } else if (!ts.defn.getType().get(0).getCode().equals(ts.type)) { 487 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_WRONG_TYPE_, (!Utilities.noString(getContextPathSource()) ? getContextPathSource() : currentBasePath), tn, ts.defn.typeSummary())); 488 } 489 } 490 } 491 492 // ok passed the checks. 493 // copy the root diff, and then process any children it has 494 ElementDefinition elementDefinition = 495 this 496 .incrementDebugIndent() 497 .withBaseLimit(newBaseLimit) 498 .withDiffLimit(newDiffLimit) 499 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, 0)) 500 .withSlicing(new PathSlicingParams(true, null, null)) 501 .processPaths(new ProfilePathProcessorState(cursors.base, cursors.baseCursor, newDiffCursor, 502 cursors.contextName, cursors.resultPathBase), mapHelper); 503 if (elementDefinition == null) 504 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, path)); 505 // now set up slicing on the e (cause it was wiped by what we called. 506 elementDefinition.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); 507 elementDefinition.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); 508 elementDefinition.getSlicing().setRules(ElementDefinition.SlicingRules.CLOSED); // type slicing is always closed; the differential might call it open, but that just means it's not constraining the slices it doesn't mention 509 elementDefinition.getSlicing().setOrdered(false); 510 511 start++; 512 513 String fixedType = null; 514 // now process the siblings, which should each be type constrained - and may also have their own children 515 // now we process the base scope repeatedly for each instance of the item in the differential list 516 for (int i = start; i < diffMatches.size(); i++) { 517 // our processing scope for the differential is the item in the list, and all the items before the next one in the list 518 if (diffMatches.get(i).getMin() > 0) { 519 if (diffMatches.size() > i + 1) { 520 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.INVALID_SLICING__THERE_IS_MORE_THAN_ONE_TYPE_SLICE_AT__BUT_ONE_OF_THEM__HAS_MIN__1_SO_THE_OTHER_SLICES_CANNOT_EXIST, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 521 } else { 522 elementDefinition.setMin(1); 523 } 524 fixedType = profileUtilities.determineFixedType(diffMatches, fixedType, i); 525 } 526 newDiffCursor = getDifferential().getElement().indexOf(diffMatches.get(i)); 527 newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor); 528 ElementDefinition typeSliceElement = 529 this 530 .incrementDebugIndent() 531 .withBaseLimit(newBaseLimit) 532 .withDiffLimit(newDiffLimit) 533 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, i)) 534 .withSlicing(new PathSlicingParams(true, elementDefinition, null)) 535 .processPaths(new ProfilePathProcessorState(cursors.base, cursors.baseCursor, newDiffCursor, cursors.contextName, cursors.resultPathBase), mapHelper); 536 if (typeList.size() > start + 1) { 537 typeSliceElement.setMin(0); 538 } 539 } 540 if (elementToRemove != null) { 541 getDifferential().getElement().remove(elementToRemove); 542 newDiffLimit--; 543 } 544 if (fixedType != null) { 545 for (Iterator<ElementDefinition.TypeRefComponent> iter = elementDefinition.getType().iterator(); iter.hasNext(); ) { 546 ElementDefinition.TypeRefComponent tr = iter.next(); 547 if (!tr.getCode().equals(fixedType)) { 548 iter.remove(); 549 } 550 } 551 } 552 if (!"0".equals(elementDefinition.getMax())) { 553 // check that there's a slice for each allowed types 554 Set<String> allowedTypes = profileUtilities.getListOfTypes(elementDefinition); 555 for (TypeSlice t : typeList) { 556 if (t.type != null) { 557 allowedTypes.remove(t.type); 558 } else if (t.getDefn().hasSliceName() && t.getDefn().getType().size() == 1) { 559 allowedTypes.remove(t.getDefn().getType().get(0).getCode()); 560 } 561 } 562 if (!allowedTypes.isEmpty()) { 563 if (currentBasePath.contains("xtension.value") && shortCut) { 564 for (Iterator<ElementDefinition.TypeRefComponent> iter = elementDefinition.getType().iterator(); iter.hasNext(); ) { 565 ElementDefinition.TypeRefComponent tr = iter.next(); 566 if (allowedTypes.contains(tr.getCode())) { 567 iter.remove(); 568 } 569 } 570 } else { 571 elementDefinition.getSlicing().setRules(ElementDefinition.SlicingRules.OPEN); 572 } 573 } 574 } 575 // ok, done with that - next in the base list 576 cursors.baseCursor = newBaseLimit + 1; 577 cursors.diffCursor = newDiffLimit + 1; 578 } 579 580 private ElementDefinition processSimplePathWithOneMatchingElementInDifferential(ElementDefinition currentBase, String currentBasePath, List<ElementDefinition> diffMatches, ProfilePathProcessorState cursors, MappingAssistant mapHelper) { 581 ElementDefinition res; 582 ElementDefinition template = null; 583 if (diffMatches.get(0).hasType() && "Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode()) && !profileUtilities.isValidType(diffMatches.get(0).getType().get(0), currentBase)) { 584 if (!ProfileUtilities.isSuppressIgnorableExceptions()) { 585 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT, getUrl(), diffMatches.get(0).getPath(), diffMatches.get(0).getType().get(0), currentBase.typeSummary())); 586 } 587 } 588 String id = diffMatches.get(0).getId(); 589 String lid = profileUtilities.tail(id); 590 if (lid.contains("/")) { 591 // the template comes from the snapshot of the base 592 profileUtilities.generateIds(getResult().getElement(), getUrl(), getSourceStructureDefinition().getType(), getSourceStructureDefinition()); 593 String baseId = id.substring(0, id.length() - lid.length()) + lid.substring(0, lid.indexOf("/")); // this is wrong if there's more than one reslice (todo: one thing at a time) 594 template = profileUtilities.getById(getResult().getElement(), baseId); 595 596 } else if (diffMatches.get(0).hasType() 597 && diffMatches.get(0).getType().size() == 1 598 && diffMatches.get(0).getType().get(0).hasProfile() 599 && !"Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode()) 600 && !(currentBase.getType().get(0).hasProfile() && currentBase.getType().get(0).getProfile().get(0).primitiveValue().equals(diffMatches.get(0).getType().get(0).getProfile().get(0).primitiveValue()))) { 601 CanonicalType firstTypeProfile = diffMatches.get(0).getType().get(0).getProfile().get(0); 602 StructureDefinition firstTypeStructureDefinition = profileUtilities.getContext().fetchResource(StructureDefinition.class, firstTypeProfile.getValue()); 603 if (firstTypeStructureDefinition == null && profileUtilities.getXver() != null && profileUtilities.getXver().matchingUrl(firstTypeProfile.getValue())) { 604 switch (profileUtilities.getXver().status(firstTypeProfile.getValue())) { 605 case BadVersion: 606 throw new FHIRException("Reference to invalid version in extension url " + firstTypeProfile.getValue()); 607 case Invalid: 608 throw new FHIRException("Reference to invalid extension " + firstTypeProfile.getValue()); 609 case Unknown: 610 throw new FHIRException("Reference to unknown extension " + firstTypeProfile.getValue()); 611 case Valid: 612 firstTypeStructureDefinition = profileUtilities.getXver().makeDefinition(firstTypeProfile.getValue()); 613 profileUtilities.generateSnapshot(profileUtilities.getContext().fetchTypeDefinition("Extension"), firstTypeStructureDefinition, firstTypeStructureDefinition.getUrl(), getWebUrl(), firstTypeStructureDefinition.getName()); 614 } 615 } 616 if (firstTypeStructureDefinition != null) { 617 if (!profileUtilities.isMatchingType(firstTypeStructureDefinition, diffMatches.get(0).getType(), firstTypeProfile.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT))) { 618 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE2, firstTypeStructureDefinition.getUrl(), diffMatches.get(0).getPath(), firstTypeStructureDefinition.getType(), firstTypeProfile.getValue(), diffMatches.get(0).getType().get(0).getWorkingCode())); 619 } 620 if (profileUtilities.isGenerating(firstTypeStructureDefinition)) { 621 // this is a special case, because we're only going to access the first element, and we can rely on the fact that it's already populated. 622 // but we check anyway 623 if (firstTypeStructureDefinition.getSnapshot().getElementFirstRep().isEmpty()) { 624 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, firstTypeStructureDefinition.getUrl(), "Source for first element")); 625 } 626 } else if (!firstTypeStructureDefinition.hasSnapshot()) { 627 StructureDefinition sdb = profileUtilities.getContext().fetchResource(StructureDefinition.class, firstTypeStructureDefinition.getBaseDefinition()); 628 if (sdb == null) 629 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.UNABLE_TO_FIND_BASE__FOR_, firstTypeStructureDefinition.getBaseDefinition(), firstTypeStructureDefinition.getUrl())); 630 profileUtilities.checkNotGenerating(sdb, "an extension base"); 631 profileUtilities.generateSnapshot(sdb, firstTypeStructureDefinition, firstTypeStructureDefinition.getUrl(), (sdb.hasWebPath()) ? Utilities.extractBaseUrl(sdb.getWebPath()) : getWebUrl(), firstTypeStructureDefinition.getName()); 632 } 633 ElementDefinition src; 634 if (firstTypeProfile.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) { 635 src = null; 636 String eid = firstTypeProfile.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT); 637 for (ElementDefinition t : firstTypeStructureDefinition.getSnapshot().getElement()) { 638 if (eid.equals(t.getId())) 639 src = t; 640 } 641 if (src == null) 642 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT__IN_, eid, firstTypeProfile.getValue())); 643 } else { 644 if (firstTypeStructureDefinition.getSnapshot().getElement().isEmpty()) { 645 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.SNAPSHOT_IS_EMPTY, firstTypeStructureDefinition.getVersionedUrl(), "Source for first element")); 646 } else { 647 src = firstTypeStructureDefinition.getSnapshot().getElement().get(0).copy(); 648 if (!src.getPath().contains(".") && firstTypeStructureDefinition.getKind() == StructureDefinitionKind.RESOURCE) { 649 // we can't migrate the constraints in this case, because the sense of %resource changes when the root resource 650 // is treated as an element. The validator will enforce the constraint 651 src.getConstraint().clear(); // 652 } 653 } 654 } 655 template = src.copy().setPath(currentBase.getPath()); 656 template.setSliceName(null); 657 // temporary work around 658 if (!"Extension".equals(diffMatches.get(0).getType().get(0).getCode())) { 659 template.setMin(currentBase.getMin()); 660 template.setMax(currentBase.getMax()); 661 } 662 } 663 } 664 if (template == null) 665 template = currentBase.copy(); 666 else 667 // some of what's in currentBase overrides template 668 template = profileUtilities.fillOutFromBase(template, currentBase); 669 670 ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), template, true); 671 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 672 673 res = outcome; 674 profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); 675 if (diffMatches.get(0).hasSliceName()) { 676 template = currentBase.copy(); 677 template = profileUtilities.updateURLs(getUrl(), getWebUrl(), template, true); 678 template.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), template.getPath(), getRedirector(), getContextPathSource())); 679 680 checkToSeeIfSlicingExists(diffMatches.get(0), template); 681 outcome.setSliceName(diffMatches.get(0).getSliceName()); 682 if (!diffMatches.get(0).hasMin() && (diffMatches.size() > 1 || getSlicing().getElementDefinition()== null || getSlicing().getElementDefinition().getSlicing().getRules() != ElementDefinition.SlicingRules.CLOSED) && !currentBase.hasSliceName()) { 683 if (!currentBasePath.endsWith("xtension.value[x]")) { // hack work around for problems with snapshots in official releases 684 outcome.setMin(0); 685 } 686 } 687 } 688 profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived(), diffPath(diffMatches.get(0)), mapHelper); 689 profileUtilities.removeStatusExtensions(outcome); 690// if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*") && !diffMatches.get(0).hasSlicing()) // if the base profile allows multiple types, but the profile only allows one, rename it 691// outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode())); 692 outcome.setSlicing(null); 693 if (cursors.resultPathBase == null) 694 cursors.resultPathBase = outcome.getPath(); 695 else if (!outcome.getPath().startsWith(cursors.resultPathBase)) 696 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); 697 debugCheck(outcome); 698 getResult().getElement().add(outcome); 699 cursors.baseCursor++; 700 cursors.diffCursor = getDifferential().getElement().indexOf(diffMatches.get(0)) + 1; 701 if (getDiffLimit() >= cursors.diffCursor && outcome.getPath().contains(".") && (profileUtilities.isDataType(outcome.getType()) || profileUtilities.isBaseResource(outcome.getType()) || outcome.hasContentReference())) { // don't want to do this for the root, since that's base, and we're already processing it 702 if (profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), diffMatches.get(0).getPath() + ".") && !profileUtilities.baseWalksInto(cursors.base.getElement(), cursors.baseCursor)) { 703 if (outcome.getType().size() > 1) { 704 if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) { 705 String en = profileUtilities.tail(outcome.getPath()); 706 String tn = profileUtilities.tail(diffMatches.get(0).getPath()); 707 String t = tn.substring(en.length() - 3); 708 if (profileUtilities.isPrimitive(Utilities.uncapitalize(t))) 709 t = Utilities.uncapitalize(t); 710 List<ElementDefinition.TypeRefComponent> ntr = profileUtilities.getByTypeName(outcome.getType(), t); // keep any additional information 711 if (ntr.isEmpty()) 712 ntr.add(new ElementDefinition.TypeRefComponent().setCode(t)); 713 outcome.getType().clear(); 714 outcome.getType().addAll(ntr); 715 } 716 if (outcome.getType().size() > 1) 717 for (ElementDefinition.TypeRefComponent t : outcome.getType()) { 718 if (!t.getCode().equals("Reference")) { 719 boolean nonExtension = false; 720 for (ElementDefinition ed : diffMatches) 721 if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension")) 722 nonExtension = true; 723 if (nonExtension) 724 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, diffMatches.get(0).getPath(), getDifferential().getElement().get(cursors.diffCursor).getPath(), profileUtilities.typeCode(outcome.getType()), getProfileName())); 725 } 726 } 727 } 728 int start = cursors.diffCursor; 729 while (cursors.diffCursor <= getDiffLimit() && getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 730 cursors.diffCursor++; 731 if (outcome.hasContentReference()) { 732 ProfileUtilities.ElementDefinitionResolution target = profileUtilities.getElementById(getSourceStructureDefinition(), cursors.base.getElement(), outcome.getContentReference()); 733 if (target == null) 734 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.UNABLE_TO_RESOLVE_REFERENCE_TO_, outcome.getContentReference())); 735 profileUtilities.replaceFromContentReference(outcome, target.getElement()); 736 if (target.getSource() != getSourceStructureDefinition()) { 737 cursors.base = target.getSource().getSnapshot(); 738 int newBaseCursor = cursors.base.getElement().indexOf(target.getElement()) + 1; 739 int newBaseLimit = newBaseCursor; 740 while (newBaseLimit < cursors.base.getElement().size() && cursors.base.getElement().get(newBaseLimit).getPath().startsWith(target.getElement().getPath() + ".")) 741 newBaseLimit++; 742 743 this 744 .incrementDebugIndent() 745 .withBaseLimit(newBaseLimit - 1) 746 .withDiffLimit(cursors.diffCursor - 1) 747 .withContextPathSource(target.getElement().getPath()) 748 .withContextPathTarget(diffMatches.get(0).getPath()).withRedirector(profileUtilities.redirectorStack(getRedirector(), outcome, currentBasePath)) 749 .withSourceStructureDefinition(target.getSource()) 750 .withSlicing(new PathSlicingParams()).processPaths(new ProfilePathProcessorState(cursors.base, newBaseCursor, start - 1, cursors.contextName, cursors.resultPathBase), mapHelper); 751 } else { 752 final int newBaseCursor = cursors.base.getElement().indexOf(target.getElement()) + 1; 753 int newBaseLimit = newBaseCursor; 754 while (newBaseLimit < cursors.base.getElement().size() && cursors.base.getElement().get(newBaseLimit).getPath().startsWith(target.getElement().getPath() + ".")) 755 newBaseLimit++; 756 757 this 758 .incrementDebugIndent() 759 .withBaseLimit(newBaseLimit - 1) 760 .withDiffLimit(cursors.diffCursor - 1) 761 .withContextPathSource(target.getElement().getPath()) 762 .withContextPathTarget(diffMatches.get(0).getPath()) 763 .withRedirector(profileUtilities.redirectorStack(getRedirector(), outcome, currentBasePath)) 764 .withSlicing(new PathSlicingParams()).processPaths( 765 new ProfilePathProcessorState(cursors.base, newBaseCursor, start - 1, cursors.contextName, cursors.resultPathBase), mapHelper); 766 } 767 } else { 768 StructureDefinition dt = outcome.getType().size() == 1 ? profileUtilities.getProfileForDataType(outcome.getType().get(0), getWebUrl(), getDerived()) : profileUtilities.getProfileForDataType("Element"); 769 if (dt == null) 770 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants._HAS_CHILDREN__FOR_TYPE__IN_PROFILE__BUT_CANT_FIND_TYPE, diffMatches.isEmpty() ? "??" : diffMatches.get(0).getPath(), getDifferential().getElement().get(cursors.diffCursor).getPath(), profileUtilities.typeCode(outcome.getType()), getProfileName())); 771 cursors.contextName = dt.getUrl(); 772 773 this 774 .incrementDebugIndent() 775 .withBaseLimit(dt.getSnapshot().getElement().size() - 1) 776 .withDiffLimit(cursors.diffCursor - 1) 777 .withWebUrl( profileUtilities.getWebUrl(dt, getWebUrl())) 778 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, 0)) 779 .withContextPathSource(diffMatches.get(0).getPath()).withContextPathTarget(outcome.getPath()).withRedirector(new ArrayList<ElementRedirection>()) 780 .withSlicing(new PathSlicingParams()). /* starting again on the data type, but skip the root */ 781 processPaths(new ProfilePathProcessorState(dt.getSnapshot(), 1 /* starting again on the data type, but skip the root */, start, 782 cursors.contextName, cursors.resultPathBase), mapHelper); 783 } 784 } 785 } 786 return res; 787 } 788 789 private void checkToSeeIfSlicingExists(ElementDefinition ed, ElementDefinition template) { 790 List<ElementDefinition> ss = result.getElement(); 791 int i = ss.size() -1; 792 ElementDefinition m = null; 793 794 while (i >= 0) { 795 ElementDefinition t = ss.get(i); 796 if (pathsMatch(t.getPath(), ed.getPath())) { 797 if (t.hasSlicing() || t.hasSliceName() || t.getPath().endsWith("[x]")) { 798 m = t; 799 break; 800 } 801 } 802 if (t.getPath().length() < ed.getPath().length()) { 803 break; 804 } 805 i--; 806 } 807 if (m == null) { 808 if (template.getPath().endsWith(".extension")) { 809 template.getSlicing().setRules(SlicingRules.OPEN); 810 template.getSlicing().setOrdered(false); 811 template.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 812 result.getElement().add(template); 813 } else { 814 System.err.println("checkToSeeIfSlicingExists: "+ed.getPath()+":"+ed.getSliceName()+" is not sliced"); 815 } 816 } 817 } 818 819 private boolean pathsMatch(String path1, String path2) { 820 String[] p1 = path1.split("\\."); 821 String[] p2 = path2.split("\\."); 822 if (p1.length != p2.length) { 823 return false; 824 } 825 for (int i = 0; i < p1.length; i++) { 826 String pp1 = p1[i]; 827 String pp2 = p2[i]; 828 if (!pp1.equals(pp2)) { 829 if (pp1.endsWith("[x]")) { 830 if (!pp2.startsWith(pp1.substring(0, pp1.length()-3))) { 831 return false; 832 } 833 } else if (pp2.endsWith("[x]")) { 834 if (!pp1.startsWith(pp2.substring(0, pp2.length()-3))) { 835 return false; 836 } 837 838 } else { 839 return false; 840 } 841 } 842 } 843 return true; 844 } 845 846 private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, int baseLimit) { 847 return baseLimit+1; 848 } 849 850 private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) { 851 int index = base.getElement().indexOf(ed); 852 if (index == -1 || index >= base.getElement().size()-1) 853 return false; 854 String p = base.getElement().get(index+1).getPath(); 855 return isChildOf(p, ed.getPath()); 856 } 857 858 859 private boolean isChildOf(String sub, String focus) { 860 if (focus.endsWith("[x]")) { 861 focus = focus.substring(0, focus.length()-3); 862 return sub.startsWith(focus); 863 } else 864 return sub.startsWith(focus+"."); 865 } 866 867 868 private void processSimplePathWithEmptyDiffMatches(ElementDefinition currentBase, String currentBasePath, List<ElementDefinition> diffMatches, ProfilePathProcessorState cursors, MappingAssistant mapHelper) { 869 ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true); 870 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 871 profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); 872 profileUtilities.updateConstraintSources(outcome, getSourceStructureDefinition().getUrl()); 873 profileUtilities.checkExtensions(outcome); 874 profileUtilities.updateFromObligationProfiles(outcome); 875 profileUtilities.updateURLs(url, webUrl, outcome, true); 876 profileUtilities.markDerived(outcome); 877 if (cursors.resultPathBase == null) 878 cursors.resultPathBase = outcome.getPath(); 879 else if (!outcome.getPath().startsWith(cursors.resultPathBase)) 880 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH__OUTCOMEGETPATH___RESULTPATHBASE__, outcome.getPath(), cursors.resultPathBase)); 881 debugCheck(outcome); 882 getResult().getElement().add(outcome); 883 if (profileUtilities.hasInnerDiffMatches(getDifferential(), currentBasePath, cursors.diffCursor, getDiffLimit(), cursors.base.getElement(), true)) { 884 // well, the profile walks into this, so we need to as well 885 // did we implicitly step into a new type? 886 if (baseHasChildren(cursors.base, currentBase)) { // not a new type here 887 888 this.incrementDebugIndent().withSlicing(new PathSlicingParams()). processPaths( new ProfilePathProcessorState(cursors.base, cursors.baseCursor + 1, cursors.diffCursor, cursors.contextName, cursors.resultPathBase), mapHelper); 889 cursors.baseCursor = indexOfFirstNonChild(cursors.base, currentBase, cursors.baseCursor + 1, getBaseLimit()); 890 } 891 else { 892 if (outcome.getType().size() == 0 && !outcome.hasContentReference()) { 893 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, currentBasePath, getDifferential().getElement().get(cursors.diffCursor).getPath(), getProfileName())); 894 } 895 boolean nonExtension = false; 896 if (outcome.getType().size() > 1) { 897 for (ElementDefinition.TypeRefComponent t : outcome.getType()) { 898 if (!t.getWorkingCode().equals("Reference")) { 899 for (ElementDefinition ed : diffMatches) { 900 if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension")) { 901 nonExtension = true; 902 } 903 } 904 } 905 } 906 } 907 if (!profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + ".")) { 908 cursors.diffCursor++; 909 } 910 int start = cursors.diffCursor; 911 while (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + ".")) 912 cursors.diffCursor++; 913 if (nonExtension) { 914 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, currentBasePath, getDifferential().getElement().get(cursors.diffCursor).getPath(), profileUtilities.typeCode(outcome.getType()), getProfileName())); 915 } 916 if (outcome.hasContentReference()) { 917 ProfileUtilities.ElementDefinitionResolution tgt = profileUtilities.getElementById(getSourceStructureDefinition(), cursors.base.getElement(), outcome.getContentReference()); 918 if (tgt == null) 919 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.UNABLE_TO_RESOLVE_REFERENCE_TO_, outcome.getContentReference())); 920 profileUtilities.replaceFromContentReference(outcome, tgt.getElement()); 921 if (tgt.getSource() != getSourceStructureDefinition()) { 922 cursors.base = tgt.getSource().getSnapshot(); 923 int newBaseCursor = cursors.base.getElement().indexOf(tgt.getElement()) + 1; 924 int newBaseLimit = newBaseCursor; 925 while (newBaseLimit < cursors.base.getElement().size() && cursors.base.getElement().get(newBaseLimit).getPath().startsWith(tgt.getElement().getPath() + ".")) 926 newBaseLimit++; 927 928 this 929 .incrementDebugIndent() 930 .withBaseLimit(newBaseLimit - 1) 931 .withDiffLimit(cursors.diffCursor - 1) 932 .withContextPathSource(tgt.getElement().getPath()) 933 .withContextPathTarget(diffMatches.get(0).getPath()) 934 .withRedirector(profileUtilities.redirectorStack(getRedirector(), outcome, currentBasePath)) 935 .withSourceStructureDefinition(tgt.getSource()) 936 .withSlicing(new PathSlicingParams()).processPaths( 937 new ProfilePathProcessorState(cursors.base, newBaseCursor, start - 1, cursors.contextName, cursors.resultPathBase), mapHelper); 938 } else { 939 int newBaseCursor = cursors.base.getElement().indexOf(tgt.getElement()) + 1; 940 int newBaseLimit = newBaseCursor; 941 while (newBaseLimit < cursors.base.getElement().size() && cursors.base.getElement().get(newBaseLimit).getPath().startsWith(tgt.getElement().getPath() + ".")) 942 newBaseLimit++; 943// System.out.println("Test!"); 944 945 this 946 .incrementDebugIndent() 947 .withBaseLimit(newBaseLimit - 1) 948 .withDiffLimit(cursors.diffCursor - 1) 949 .withContextPathSource(tgt.getElement().getPath()) 950 .withContextPathTarget(outcome.getPath()) 951 .withRedirector(profileUtilities.redirectorStack(getRedirector(), outcome, currentBasePath)).withSlicing(new PathSlicingParams()).processPaths( 952 new ProfilePathProcessorState(cursors.base, newBaseCursor, start, cursors.contextName, cursors.resultPathBase), mapHelper); 953 } 954 } else { 955 StructureDefinition dt = outcome.getType().size() > 1 ? profileUtilities.getContext().fetchTypeDefinition("Element") : profileUtilities.getProfileForDataType(outcome.getType().get(0), getWebUrl(), getDerived()); 956 if (dt == null) { 957 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), currentBasePath)); 958 } 959 cursors.contextName = dt.getUrl(); 960 if (getRedirector() == null || getRedirector().isEmpty()) { 961 962 this 963 .incrementDebugIndent() 964 .withBaseLimit(dt.getSnapshot().getElement().size() - 1) 965 .withDiffLimit(cursors.diffCursor - 1) 966 .withWebUrl(profileUtilities.getWebUrl(dt, getWebUrl())) 967 .withContextPathSource(currentBasePath) 968 .withContextPathTarget(outcome.getPath()) 969 .withSlicing(new PathSlicingParams()).processPaths( /* starting again on the data type, but skip the root */ 970 new ProfilePathProcessorState(dt.getSnapshot(), 1 /* starting again on the data type, but skip the root */, start, 971 cursors.contextName, cursors.resultPathBase), mapHelper); 972 } else { 973 974 this 975 .incrementDebugIndent() 976 .withBaseLimit(dt.getSnapshot().getElement().size() - 1) 977 .withDiffLimit(cursors.diffCursor - 1) 978 .withWebUrl(profileUtilities.getWebUrl(dt, getWebUrl())) 979 .withContextPathSource(currentBasePath) 980 .withContextPathTarget( outcome.getPath()) 981 .withRedirector(profileUtilities.redirectorStack(getRedirector(), currentBase, currentBasePath)).withSlicing(new PathSlicingParams()).processPaths( /* starting again on the data type, but skip the root */ 982 new ProfilePathProcessorState(dt.getSnapshot(), 1 /* starting again on the data type, but skip the root */, start, 983 cursors.contextName, cursors.resultPathBase), mapHelper); 984 } 985 } 986 } 987 } 988 cursors.baseCursor++; 989 } 990 991 private void processPathWithSlicedBase( 992 ElementDefinition currentBase, 993 String currentBasePath, 994 List<ElementDefinition> diffMatches, List<TypeSlice> typeList, 995 final ProfilePathProcessorState cursors, MappingAssistant mapHelper 996 ) { 997 // the item is already sliced in the base profile. 998 // here's the rules 999 // 1. irrespective of whether the slicing is ordered or not, the definition order must be maintained 1000 // 2. slice element names have to match. 1001 // 3. new slices must be introduced at the end 1002 // corallory: you can't re-slice existing slices. is that ok? 1003 1004 // we're going to need this: 1005 String path = currentBase.getPath(); 1006 1007 if (diffMatches.isEmpty()) { 1008 processPathWithSlicedBaseAndEmptyDiffMatches(currentBase, currentBasePath, diffMatches, cursors, path, mapHelper); 1009 } 1010 else if (profileUtilities.diffsConstrainTypes(diffMatches, currentBasePath, typeList)) 1011 { 1012 processPathWithSlicedBaseWhereDiffsConstrainTypes(currentBasePath, diffMatches, typeList, cursors, mapHelper); 1013 } 1014 else 1015 { 1016 processPathWithSlicedBaseDefault(currentBase, currentBasePath, diffMatches, cursors, path, mapHelper); 1017 } 1018 } 1019 1020 private void processPathWithSlicedBaseDefault(ElementDefinition currentBase, String currentBasePath, List<ElementDefinition> diffMatches, ProfilePathProcessorState cursors, String path, MappingAssistant mapHelper) { 1021 // first - check that the slicing is ok 1022 boolean closed = currentBase.getSlicing().getRules() == ElementDefinition.SlicingRules.CLOSED; 1023 int diffpos = 0; 1024 if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing 1025// if (!isExtension) 1026// diffpos++; // if there's a slice on the first, we'll ignore any content it has 1027 ElementDefinition.ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing(); 1028 ElementDefinition.ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing(); 1029 if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !profileUtilities.orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement())) 1030 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___ORDER___, profileUtilities.summarizeSlicing(dSlice), profileUtilities.summarizeSlicing(bSlice), path, cursors.contextName)); 1031 if (!profileUtilities.discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator())) 1032 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___DISCIMINATOR___, profileUtilities.summarizeSlicing(dSlice), profileUtilities.summarizeSlicing(bSlice), path, url)); 1033 if (!currentBase.isChoice() && !profileUtilities.ruleMatches(dSlice.getRules(), bSlice.getRules())) 1034 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___RULE___, profileUtilities.summarizeSlicing(dSlice), profileUtilities.summarizeSlicing(bSlice), path, cursors.contextName)); 1035 } 1036 ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true); 1037 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 1038 profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); 1039 if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) { 1040 profileUtilities.updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); 1041 profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), closed, getUrl(), getSourceStructureDefinition(), getDerived(), diffPath(diffMatches.get(0)), mapHelper); // if there's no slice, we don't want to update the unsliced description 1042 profileUtilities.removeStatusExtensions(outcome); 1043 } else if (!diffMatches.get(0).hasSliceName()) { 1044 diffMatches.get(0).setUserData(profileUtilities.UD_GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called 1045 } else { 1046 outcome.setUserData("auto-added-slicing", true); 1047 } 1048 1049 debugCheck(outcome); 1050 getResult().getElement().add(outcome); 1051 1052 if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice 1053 diffpos++; 1054 } 1055 if (profileUtilities.hasInnerDiffMatches(getDifferential(), currentBasePath, cursors.diffCursor, getDiffLimit(), cursors.base.getElement(), false)) { 1056 int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); 1057 int ndx = getDifferential().getElement().indexOf(diffMatches.get(0)); 1058 int newDiffCursor = ndx + (diffMatches.get(0).hasSlicing() ? 1 : 0); 1059 int newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), ndx); 1060 if (newBaseLimit == cursors.baseCursor) { 1061 if (cursors.base.getElement().get(cursors.baseCursor).getType().size() != 1) { 1062 throw new Error(profileUtilities.getContext().formatMessage(I18nConstants.DIFFERENTIAL_WALKS_INTO____BUT_THE_BASE_DOES_NOT_AND_THERE_IS_NOT_A_SINGLE_FIXED_TYPE_THE_TYPE_IS__THIS_IS_NOT_HANDLED_YET, currentBasePath, diffMatches.get(0).toString(), cursors.base.getElement().get(cursors.baseCursor).typeSummary())); 1063 } 1064 StructureDefinition dt = profileUtilities.getProfileForDataType(cursors.base.getElement().get(cursors.baseCursor).getType().get(0), getWebUrl(), getDerived()); 1065 if (dt == null) { 1066 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath())); 1067 } 1068 cursors.contextName = dt.getUrl(); 1069 while (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + ".")) 1070 cursors.diffCursor++; 1071 1072 this 1073 .incrementDebugIndent() 1074 .withBaseLimit(dt.getSnapshot().getElement().size() - 1) 1075 .withDiffLimit(newDiffLimit) 1076 .withWebUrl(profileUtilities.getWebUrl(dt, getWebUrl())) 1077 .withContextPathSource(currentBasePath).withContextPathTarget(outcome.getPath()) 1078 .withSlicing(new PathSlicingParams()).processPaths( 1079 new ProfilePathProcessorState(dt.getSnapshot(), 1, newDiffCursor, 1080 cursors.contextName, cursors.resultPathBase), mapHelper); 1081 } else { 1082 1083 this 1084 .incrementDebugIndent() 1085 .withBaseLimit(newBaseLimit) 1086 .withDiffLimit(newDiffLimit) 1087 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, 0)) 1088 .withRedirector(null).withSlicing(new PathSlicingParams()).processPaths( 1089 new ProfilePathProcessorState(cursors.base, cursors.baseCursor + 1, newDiffCursor, 1090 cursors.contextName, cursors.resultPathBase), mapHelper); 1091 } 1092// throw new Error("Not done yet"); 1093// } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) { 1094 } else if (!currentBase.getType().isEmpty() && currentBase.getType().get(0).getCode().equals("BackboneElement")) { 1095 // We need to copy children of the backbone element before we start messing around with slices 1096 int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); 1097 for (int i = cursors.baseCursor + 1; i <= newBaseLimit; i++) { 1098 outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(i).copy(), true); 1099 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 1100 debugCheck(outcome); 1101 getResult().getElement().add(outcome); 1102 } 1103 } 1104 1105 // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff. 1106 List<ElementDefinition> baseMatches = profileUtilities.getSiblings(cursors.base.getElement(), currentBase); 1107 for (ElementDefinition baseItem : baseMatches) { 1108 cursors.baseCursor = cursors.base.getElement().indexOf(baseItem); 1109 outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), baseItem.copy(), true); 1110 profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); 1111 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 1112 outcome.setSlicing(null); 1113 if (!outcome.getPath().startsWith(cursors.resultPathBase)) 1114 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1115 if (diffpos < diffMatches.size() && diffMatches.get(diffpos).hasSliceName() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) { 1116 // if there's a diff, we update the outcome with diff 1117 // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url); 1118 //then process any children 1119 int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); 1120 int newDiffCursor = getDifferential().getElement().indexOf(diffMatches.get(diffpos)); 1121 int newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor); 1122 // now we process the base scope repeatedly for each instance of the item in the differential list 1123 1124 this 1125 .incrementDebugIndent() 1126 .withBaseLimit(newBaseLimit) 1127 .withDiffLimit(newDiffLimit) 1128 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, diffpos)) 1129 .withTrimDifferential(closed) 1130 .withSlicing(new PathSlicingParams(true, null, null)).processPaths( 1131 new ProfilePathProcessorState(cursors.base, cursors.baseCursor, newDiffCursor, cursors.contextName, cursors.resultPathBase), mapHelper); 1132 // ok, done with that - now set the cursors for if this is the end 1133 cursors.baseCursor = newBaseLimit; 1134 cursors.diffCursor = newDiffLimit + 1; 1135 diffpos++; 1136 } else { 1137 debugCheck(outcome); 1138 getResult().getElement().add(outcome); 1139 cursors.baseCursor++; 1140 // just copy any children on the base 1141 while (cursors.baseCursor < cursors.base.getElement().size() && cursors.base.getElement().get(cursors.baseCursor).getPath().startsWith(path) && !cursors.base.getElement().get(cursors.baseCursor).getPath().equals(path)) { 1142 outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(cursors.baseCursor).copy(), true); 1143 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 1144 if (!outcome.getPath().startsWith(cursors.resultPathBase)) 1145 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1146 outcome.setUserData(profileUtilities.UD_BASE_PATH, outcome.getPath()); 1147 outcome.setUserData(profileUtilities.UD_BASE_MODEL, getSourceStructureDefinition().getUrl()); 1148 debugCheck(outcome); 1149 getResult().getElement().add(outcome); 1150 cursors.baseCursor++; 1151 } 1152 //Lloyd - add this for test T15 1153 cursors.baseCursor--; 1154 } 1155 } 1156 // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed 1157 if (closed && diffpos < diffMatches.size()) { 1158 // this is a problem, unless we're on a polymorhpic type and we're going to constrain a slice that actually implicitly exists 1159 if (!currentBase.getPath().endsWith("[x]")) { 1160 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.THE_BASE_SNAPSHOT_MARKS_A_SLICING_AS_CLOSED_BUT_THE_DIFFERENTIAL_TRIES_TO_EXTEND_IT_IN__AT__, getProfileName(), path, currentBasePath)); 1161 } 1162 } 1163 if (diffpos != diffMatches.size()) { 1164 while (diffpos < diffMatches.size()) { 1165 ElementDefinition diffItem = diffMatches.get(diffpos); 1166 for (ElementDefinition baseItem : baseMatches) 1167 if (baseItem.getSliceName().equals(diffItem.getSliceName())) 1168 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.NAMED_ITEMS_ARE_OUT_OF_ORDER_IN_THE_SLICE)); 1169 outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true); 1170 // outcome = updateURLs(url, diffItem.copy()); 1171 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 1172 profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); 1173 outcome.setSlicing(null); 1174 outcome.setMin(0); // we're in a slice, so it's only a mandatory if it's explicitly marked so 1175 if (!outcome.getPath().startsWith(cursors.resultPathBase)) 1176 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1177 debugCheck(outcome); 1178 getResult().getElement().add(outcome); 1179 profileUtilities.updateFromDefinition(outcome, diffItem, getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived(), diffPath(diffItem), mapHelper); 1180 profileUtilities.removeStatusExtensions(outcome); 1181 // --- LM Added this 1182 cursors.diffCursor = getDifferential().getElement().indexOf(diffItem) + 1; 1183 if (!outcome.getType().isEmpty() && (/*outcome.getType().get(0).getCode().equals("Extension") || */getDifferential().getElement().size() > cursors.diffCursor) && outcome.getPath().contains(".")/* && isDataType(outcome.getType())*/) { // don't want to do this for the root, since that's base, and we're already processing it 1184 if (!profileUtilities.baseWalksInto(cursors.base.getElement(), cursors.baseCursor)) { 1185 if (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) { 1186 if (outcome.getType().size() > 1) 1187 for (ElementDefinition.TypeRefComponent t : outcome.getType()) { 1188 if (!t.getCode().equals("Reference")) 1189 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, diffMatches.get(0).getPath(), getDifferential().getElement().get(cursors.diffCursor).getPath(), profileUtilities.typeCode(outcome.getType()), getProfileName())); 1190 } 1191 ElementDefinition.TypeRefComponent t = outcome.getType().get(0); 1192 if (Utilities.existsInList(t.getCode(), "Base", "Element", "BackboneElement")) { 1193 int baseStart = cursors.base.getElement().indexOf(currentBase) + 1; 1194 int baseMax = baseStart + 1; 1195 while (baseMax < cursors.base.getElement().size() && cursors.base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath() + ".")) 1196 baseMax++; 1197 int start = cursors.diffCursor; 1198 while (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 1199 cursors.diffCursor++; 1200 1201 this.incrementDebugIndent().withBaseLimit(baseMax - 1) 1202 .withDiffLimit(cursors.diffCursor - 1) 1203 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, 0)) 1204 .withContextPathSource(cursors.base.getElement().get(0).getPath()) 1205 .withContextPathTarget(cursors.base.getElement().get(0).getPath()) 1206 .withSlicing(new PathSlicingParams()).processPaths( 1207 new ProfilePathProcessorState(cursors.base, baseStart, start - 1, 1208 cursors.contextName, cursors.resultPathBase), mapHelper); 1209 } else { 1210 StructureDefinition dt = profileUtilities.getProfileForDataType(outcome.getType().get(0), getWebUrl(), getDerived()); 1211 // if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) { 1212 // lloydfix dt = 1213 // } 1214 if (dt == null) 1215 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants._HAS_CHILDREN__FOR_TYPE__IN_PROFILE__BUT_CANT_FIND_TYPE, diffMatches.get(0).getPath(), getDifferential().getElement().get(cursors.diffCursor).getPath(), profileUtilities.typeCode(outcome.getType()), getProfileName())); 1216 cursors.contextName = dt.getUrl(); 1217 int start = cursors.diffCursor; 1218 while (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 1219 cursors.diffCursor++; 1220 1221 this 1222 .incrementDebugIndent() 1223 .withBaseLimit(dt.getSnapshot().getElement().size() - 1) 1224 .withDiffLimit(cursors.diffCursor - 1) 1225 .withWebUrl(profileUtilities.getWebUrl(dt, getWebUrl())) 1226 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, 0)) 1227 .withContextPathSource(diffMatches.get(0).getPath()).withContextPathTarget(outcome.getPath()).withSlicing(new PathSlicingParams()).processPaths( /* starting again on the data type, but skip the root */ 1228 new ProfilePathProcessorState(dt.getSnapshot(), 1 /* starting again on the data type, but skip the root */, start - 1, 1229 cursors.contextName, cursors.resultPathBase), mapHelper); 1230 } 1231 } 1232 } 1233 } 1234 // --- 1235 diffpos++; 1236 } 1237 } 1238 cursors.baseCursor++; 1239 } 1240 1241 private void debugCheck(ElementDefinition outcome) { 1242 if (outcome.getPath().startsWith("List.") && "http://nictiz.nl/fhir/StructureDefinition/Bundle-MedicationOverview".equals(url)) { 1243 System.out.println("wrong!"); 1244 } 1245 } 1246 1247 private void processPathWithSlicedBaseWhereDiffsConstrainTypes(String currentBasePath, List<ElementDefinition> diffMatches, List<TypeSlice> typeList, ProfilePathProcessorState cursors, MappingAssistant mapHelper) { 1248 int start = 0; 1249 int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); 1250 int newDiffCursor = getDifferential().getElement().indexOf(diffMatches.get(0)); 1251 ElementDefinition elementToRemove = null; 1252 boolean shortCut = (!typeList.isEmpty() && typeList.get(0).type != null) || (diffMatches.get(0).hasSliceName() && !diffMatches.get(0).hasSlicing()); 1253 // we come here whether they are sliced in the diff, or whether the short cut is used. 1254 if (shortCut) { 1255 // this is the short cut method, we've just dived in and specified a type slice. 1256 // in R3 (and unpatched R4, as a workaround right now... 1257 if (!VersionUtilities.isR4Plus(profileUtilities.getContext().getVersion()) || !profileUtilities.isNewSlicingProcessing()) { // newSlicingProcessing is a work around for editorial loop dependency 1258 // we insert a cloned element with the right types at the start of the diffMatches 1259 ElementDefinition ed = new ElementDefinition(); 1260 ed.setPath(profileUtilities.determineTypeSlicePath(diffMatches.get(0).getPath(), currentBasePath)); 1261 for (TypeSlice ts : typeList) 1262 ed.addType().setCode(ts.type); 1263 ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); 1264 ed.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); 1265 ed.getSlicing().setRules(ElementDefinition.SlicingRules.CLOSED); 1266 ed.getSlicing().setOrdered(false); 1267 diffMatches.add(0, ed); 1268 getDifferential().getElement().add(newDiffCursor, ed); 1269 elementToRemove = ed; 1270 } else { 1271 // as of R4, this changed; if there's no slice, there's no constraint on the slice types, only one the type. 1272 // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type. 1273 // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element 1274 ElementDefinition ed = new ElementDefinition(); 1275 ed.setPath(profileUtilities.determineTypeSlicePath(diffMatches.get(0).getPath(), currentBasePath)); 1276 ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); 1277 ed.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); 1278 ed.getSlicing().setRules(ElementDefinition.SlicingRules.CLOSED); 1279 ed.getSlicing().setOrdered(false); 1280 diffMatches.add(0, ed); 1281 getDifferential().getElement().add(newDiffCursor, ed); 1282 elementToRemove = ed; 1283 } 1284 } 1285 int newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor); 1286 // the first element is setting up the slicing 1287 1288 if (diffMatches.get(0).getSlicing().hasOrdered()) { 1289 if (diffMatches.get(0).getSlicing().getOrdered()) { 1290 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGORDERED__TRUE, currentBasePath, getUrl())); 1291 } 1292 } 1293 if (diffMatches.get(0).getSlicing().hasDiscriminator()) { 1294 if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1) { 1295 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORCOUNT__1, currentBasePath, getUrl())); 1296 } 1297 if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != ElementDefinition.DiscriminatorType.TYPE) { 1298 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORTYPE__TYPE, currentBasePath, getUrl())); 1299 } 1300 if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath())) { 1301 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORPATH__THIS, currentBasePath, getUrl())); 1302 } 1303 } 1304 // check the slice names too while we're at it... 1305 for (TypeSlice ts : typeList) { 1306 if (ts.type != null) { 1307 String tn = profileUtilities.rootName(currentBasePath) + Utilities.capitalize(ts.type); 1308 if (!ts.defn.hasSliceName()) { 1309 ts.defn.setSliceName(tn); 1310 } else if (!ts.defn.getSliceName().equals(tn)) { 1311 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_NAME_MUST_BE__BUT_IS_, (!Utilities.noString(getContextPathSource()) ? getContextPathSource() : currentBasePath), tn, ts.defn.getSliceName())); 1312 } 1313 if (!ts.defn.hasType()) { 1314 ts.defn.addType().setCode(ts.type); 1315 } else if (ts.defn.getType().size() > 1) { 1316 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_MORE_THAN_ONE_TYPE_, (!Utilities.noString(getContextPathSource()) ? getContextPathSource() : currentBasePath), tn, ts.defn.typeSummary())); 1317 } else if (!ts.defn.getType().get(0).getCode().equals(ts.type)) { 1318 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_WRONG_TYPE_, (!Utilities.noString(getContextPathSource()) ? getContextPathSource() : currentBasePath), tn, ts.defn.typeSummary())); 1319 } 1320 } 1321 } 1322 1323 // ok passed the checks. 1324 // copy the root diff, and then process any children it has 1325 ElementDefinition e = 1326 this 1327 .incrementDebugIndent() 1328 .withBaseLimit(newBaseLimit) 1329 .withDiffLimit(newDiffLimit) 1330 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches,0)) 1331 .withSlicing(new PathSlicingParams(true, null, currentBasePath)).processPaths( 1332 new ProfilePathProcessorState(cursors.base, cursors.baseCursor, newDiffCursor, 1333 cursors.contextName, cursors.resultPathBase), mapHelper); 1334 if (e == null) 1335 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, diffMatches.get(0).getPath())); 1336 // now set up slicing on the e (cause it was wiped by what we called. 1337 e.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); 1338 e.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); 1339 e.getSlicing().setRules(ElementDefinition.SlicingRules.CLOSED); // type slicing is always closed; the differential might call it open, but that just means it's not constraining the slices it doesn't mention 1340 e.getSlicing().setOrdered(false); 1341 start++; 1342 1343 String fixedType = null; 1344 List<BaseTypeSlice> baseSlices = profileUtilities.findBaseSlices(cursors.base, newBaseLimit); 1345 // now process the siblings, which should each be type constrained - and may also have their own children. they may match existing slices 1346 // now we process the base scope repeatedly for each instance of the item in the differential list 1347 for (int i = start; i < diffMatches.size(); i++) { 1348 String type = profileUtilities.determineFixedType(diffMatches, fixedType, i); 1349 // our processing scope for the differential is the item in the list, and all the items before the next one in the list 1350 if (diffMatches.get(i).getMin() > 0) { 1351 if (diffMatches.size() > i + 1) { 1352 throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.INVALID_SLICING__THERE_IS_MORE_THAN_ONE_TYPE_SLICE_AT__BUT_ONE_OF_THEM__HAS_MIN__1_SO_THE_OTHER_SLICES_CANNOT_EXIST, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 1353 } 1354 fixedType = type; 1355 } 1356 newDiffCursor = getDifferential().getElement().indexOf(diffMatches.get(i)); 1357 newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor); 1358 int sStart = cursors.baseCursor; 1359 int sEnd = newBaseLimit; 1360 BaseTypeSlice bs = profileUtilities.chooseMatchingBaseSlice(baseSlices, type); 1361 if (bs != null) { 1362 sStart = bs.getStart(); 1363 sEnd = bs.getEnd(); 1364 bs.setHandled(true); 1365 } 1366 1367 this 1368 .incrementDebugIndent() 1369 .withBaseLimit(sEnd) 1370 .withDiffLimit(newDiffLimit) 1371 .withProfileName(getProfileName() + profileUtilities.pathTail(diffMatches, i)) 1372 .withSlicing(new PathSlicingParams(true, e, currentBasePath)).processPaths( 1373 new ProfilePathProcessorState(cursors.base, sStart, newDiffCursor, cursors.contextName, cursors.resultPathBase), mapHelper); 1374 } 1375 if (elementToRemove != null) { 1376 getDifferential().getElement().remove(elementToRemove); 1377 newDiffLimit--; 1378 } 1379 if (fixedType != null) { 1380 for (Iterator<ElementDefinition.TypeRefComponent> iter = e.getType().iterator(); iter.hasNext(); ) { 1381 ElementDefinition.TypeRefComponent tr = iter.next(); 1382 if (!tr.getCode().equals(fixedType)) { 1383 iter.remove(); 1384 } 1385 } 1386 } 1387 for (BaseTypeSlice bs : baseSlices) { 1388 if (!bs.isHandled()) { 1389 // ok we gimme up a fake differential that says nothing, and run that against the slice. 1390 StructureDefinition.StructureDefinitionDifferentialComponent fakeDiff = new StructureDefinition.StructureDefinitionDifferentialComponent(); 1391 fakeDiff.getElementFirstRep().setPath(bs.getDefn().getPath()); 1392 1393 this 1394 .incrementDebugIndent() 1395 .withDifferential(fakeDiff) 1396 .withBaseLimit(bs.getEnd()) 1397 .withDiffLimit(0) 1398 .withProfileName(getProfileName() + profileUtilities.tail(bs.getDefn().getPath())).withSlicing(new PathSlicingParams(true, e, currentBasePath)).processPaths( 1399 new ProfilePathProcessorState(cursors.base, bs.getStart(), 0, cursors.contextName, cursors.resultPathBase), mapHelper); 1400 1401 } 1402 } 1403 // ok, done with that - next in the base list 1404 cursors.baseCursor = baseSlices.get(baseSlices.size() - 1).getEnd() + 1; 1405 cursors.diffCursor = newDiffLimit + 1; 1406 //throw new Error("not done yet - slicing / types @ "+cpath); 1407 } 1408 1409 private void processPathWithSlicedBaseAndEmptyDiffMatches(ElementDefinition currentBase, String currentBasePath, List<ElementDefinition> diffMatches, ProfilePathProcessorState cursors, String path, MappingAssistant mapHelper) { 1410 if (profileUtilities.hasInnerDiffMatches(getDifferential(), path, cursors.diffCursor, getDiffLimit(), cursors.base.getElement(), true)) { 1411 // so we just copy it in 1412 ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true); 1413 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 1414 profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); 1415 profileUtilities.markDerived(outcome); 1416 if (cursors.resultPathBase == null) 1417 cursors.resultPathBase = outcome.getPath(); 1418 else if (!outcome.getPath().startsWith(cursors.resultPathBase)) 1419 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1420 debugCheck(outcome); 1421 getResult().getElement().add(outcome); 1422 // the profile walks into this, so we need to as well 1423 // did we implicitly step into a new type? 1424 if (baseHasChildren(cursors.base, currentBase)) { // not a new type here 1425 1426 this 1427 .incrementDebugIndent() 1428 .withSlicing(new PathSlicingParams()).processPaths( 1429 new ProfilePathProcessorState(cursors.base, cursors.baseCursor + 1, cursors.diffCursor, cursors.contextName, cursors.resultPathBase), mapHelper); 1430 cursors.baseCursor = indexOfFirstNonChild(cursors.base, currentBase, cursors.baseCursor, getBaseLimit()); 1431 } else { 1432 StructureDefinition dt = profileUtilities.getTypeForElement(getDifferential(), cursors.diffCursor, getProfileName(), diffMatches, outcome, getWebUrl(), getDerived()); 1433 cursors.contextName = dt.getUrl(); 1434 int start = cursors.diffCursor; 1435 if (getDifferential().getElement().get(cursors.diffCursor).getPath().equals(currentBasePath)) { 1436 cursors.diffCursor++; 1437 } 1438 while (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + ".")) { 1439 cursors.diffCursor++; 1440 } 1441 if (cursors.diffCursor > start) { 1442 1443 this 1444 .incrementDebugIndent() 1445 .withBaseLimit(dt.getSnapshot().getElement().size() - 1) 1446 .withDiffLimit(cursors.diffCursor - 1) 1447 .withWebUrl( profileUtilities.getWebUrl(dt, getWebUrl())) 1448 .withContextPathSource(currentBasePath) 1449 .withContextPathTarget(outcome.getPath()).withSlicing(new PathSlicingParams()).processPaths( /* starting again on the data type, but skip the root */ 1450 new ProfilePathProcessorState(dt.getSnapshot(), 1 /* starting again on the data type, but skip the root */, start, 1451 cursors.contextName, cursors.resultPathBase), mapHelper); 1452 } 1453 } 1454 cursors.baseCursor++; 1455 } 1456 else { 1457 // the differential doesn't say anything about this item 1458 // copy across the currentbase, and all of its children and siblings 1459 while (cursors.baseCursor < cursors.base.getElement().size() && cursors.base.getElement().get(cursors.baseCursor).getPath().startsWith(path)) { 1460 ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(cursors.baseCursor).copy(), true); 1461 outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); 1462 if (!outcome.getPath().startsWith(cursors.resultPathBase)) 1463 throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH_IN_PROFILE___VS_, getProfileName(), outcome.getPath(), cursors.resultPathBase)); 1464 debugCheck(outcome); 1465 getResult().getElement().add(outcome); // so we just copy it in 1466 outcome.setUserData(profileUtilities.UD_BASE_MODEL, getSourceStructureDefinition().getUrl()); 1467 outcome.setUserData(profileUtilities.UD_BASE_PATH, cursors.resultPathBase); 1468 cursors.baseCursor++; 1469 } 1470 } 1471 } 1472 1473 private boolean oneMatchingElementInDifferential(boolean slicingDone, String path, List<ElementDefinition> diffMatches) { 1474 if (diffMatches.size() != 1) { 1475 return false; 1476 } 1477 if (slicingDone) { 1478 return true; 1479 } 1480 if (profileUtilities.isImplicitSlicing(diffMatches.get(0), path)) { 1481 return false; 1482 } 1483 return !(diffMatches.get(0).hasSlicing() 1484 || (profileUtilities.isExtension(diffMatches.get(0)) 1485 && diffMatches.get(0).hasSliceName())); 1486 } 1487 1488 1489}