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