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