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