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