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