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