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}