001package org.hl7.fhir.r5.conformance.profile;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033import java.io.IOException;
034import java.io.OutputStream;
035import java.util.ArrayList;
036import java.util.Arrays;
037import java.util.Collections;
038import java.util.Comparator;
039import java.util.Date;
040import java.util.HashMap;
041import java.util.HashSet;
042import java.util.Iterator;
043import java.util.List;
044import java.util.Map;
045import java.util.Set;
046
047import lombok.extern.slf4j.Slf4j;
048import org.hl7.fhir.exceptions.DefinitionException;
049import org.hl7.fhir.exceptions.FHIRException;
050import org.hl7.fhir.exceptions.FHIRFormatError;
051import org.hl7.fhir.r5.conformance.ElementRedirection;
052import org.hl7.fhir.r5.conformance.profile.MappingAssistant.MappingMergeModeOption;
053import org.hl7.fhir.r5.context.IWorkerContext;
054import org.hl7.fhir.r5.elementmodel.ObjectConverter;
055import org.hl7.fhir.r5.elementmodel.Property;
056import org.hl7.fhir.r5.fhirpath.ExpressionNode;
057import org.hl7.fhir.r5.fhirpath.ExpressionNode.Kind;
058import org.hl7.fhir.r5.fhirpath.ExpressionNode.Operation;
059import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
060import org.hl7.fhir.r5.model.Base;
061import org.hl7.fhir.r5.model.BooleanType;
062import org.hl7.fhir.r5.model.CanonicalType;
063import org.hl7.fhir.r5.model.Coding;
064import org.hl7.fhir.r5.model.DataType;
065import org.hl7.fhir.r5.model.Element;
066import org.hl7.fhir.r5.model.ElementDefinition;
067import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
068import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBaseComponent;
069import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
070import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
071import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
072import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent;
073import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
074import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
075import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
076import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
077import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
078import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
079import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
080import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
081import org.hl7.fhir.r5.model.Extension;
082import org.hl7.fhir.r5.model.IdType;
083import org.hl7.fhir.r5.model.MarkdownType;
084import org.hl7.fhir.r5.model.Resource;
085import org.hl7.fhir.r5.model.StringType;
086import org.hl7.fhir.r5.model.StructureDefinition;
087import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType;
088import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
089import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent;
090import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
091import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent;
092import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
093import org.hl7.fhir.r5.model.UriType;
094import org.hl7.fhir.r5.model.UsageContext;
095import org.hl7.fhir.r5.model.ValueSet;
096import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
097import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
098import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
099import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
100import org.hl7.fhir.r5.utils.ToolingExtensions;
101import org.hl7.fhir.r5.utils.UserDataNames;
102import org.hl7.fhir.r5.utils.XVerExtensionManager;
103import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
104import org.hl7.fhir.r5.utils.formats.CSVWriter;
105import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
106import org.hl7.fhir.utilities.FhirPublication;
107import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
108import org.hl7.fhir.utilities.Utilities;
109import org.hl7.fhir.utilities.VersionUtilities;
110import org.hl7.fhir.utilities.i18n.I18nConstants;
111import org.hl7.fhir.utilities.validation.ValidationMessage;
112import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
113import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
114import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
115import org.hl7.fhir.utilities.validation.ValidationOptions;
116import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
117import org.hl7.fhir.utilities.xml.SchematronWriter;
118import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
119import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
120import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
121
122
123/** 
124 * This class provides a set of utility operations for working with Profiles.
125 * Key functionality:
126 *  * getChildMap --?
127 *  * getChildList
128 *  * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
129 *  * closeDifferential: fill out a differential by excluding anything not mentioned
130 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
131 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
132 *  * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point
133 *  * summarize: describe the contents of a profile
134 *  
135 * note to maintainers: Do not make modifications to the snapshot generation without first changing the snapshot generation test cases to demonstrate the grounds for your change
136 *  
137 * @author Grahame
138 *
139 */
140@MarkedToMoveToAdjunctPackage
141@Slf4j
142public class ProfileUtilities {
143
144  private static boolean suppressIgnorableExceptions;
145
146  
147  public class ElementDefinitionCounter {
148    int countMin = 0;
149    int countMax = 0;
150    int index = 0;
151    ElementDefinition focus;
152    Set<String> names = new HashSet<>();
153
154    public ElementDefinitionCounter(ElementDefinition ed, int i) {
155      focus = ed;
156      index = i;
157    }
158
159    public int checkMin() {
160      if (countMin > focus.getMin()) {
161        return countMin;
162      } else {     
163        return -1;
164      }
165    }
166
167    public int checkMax() {
168      if (countMax > max(focus.getMax())) {
169        return countMax;
170      } else {     
171        return -1;
172      }
173    }
174
175    private int max(String max) {
176      if ("*".equals(max)) {
177        return Integer.MAX_VALUE;
178      } else {
179        return Integer.parseInt(max);
180      }
181    }
182
183    public boolean count(ElementDefinition ed, String name) {
184      countMin = countMin + ed.getMin();
185      if (countMax < Integer.MAX_VALUE) {
186        int m = max(ed.getMax());
187        if (m == Integer.MAX_VALUE) {
188          countMax = m;
189        } else {
190          countMax = countMax + m;
191        }
192      }
193      boolean ok = !names.contains(name);
194      names.add(name);
195      return ok;
196    }
197
198    public ElementDefinition getFocus() {
199      return focus;
200    }
201
202    public boolean checkMinMax() {
203      return countMin <= countMax;
204    }
205
206    public int getIndex() {
207      return index;
208    }
209    
210  }
211
212  public enum AllowUnknownProfile {
213    NONE, // exception if there's any unknown profiles (the default)
214    NON_EXTNEIONS, // don't raise an exception except on Extension (because more is going on there
215    ALL_TYPES // allow any unknow profile
216  }
217
218  /**
219   * These extensions are stripped in inherited profiles (and may be replaced by 
220   */
221  
222  public static final List<String> NON_INHERITED_ED_URLS = Arrays.asList(
223      "http://hl7.org/fhir/tools/StructureDefinition/binding-definition",
224      "http://hl7.org/fhir/tools/StructureDefinition/no-binding",
225      "http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding",
226      "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status",  
227      "http://hl7.org/fhir/StructureDefinition/structuredefinition-category",
228      "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm",
229      "http://hl7.org/fhir/StructureDefinition/structuredefinition-implements",
230      "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name",
231      "http://hl7.org/fhir/StructureDefinition/structuredefinition-security-category",
232      "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg",
233      "http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version",
234      "http://hl7.org/fhir/tools/StructureDefinition/obligation-profile",
235      "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status-reason",
236      ToolingExtensions.EXT_SUMMARY/*,
237      ToolingExtensions.EXT_OBLIGATION_CORE,
238      ToolingExtensions.EXT_OBLIGATION_TOOLS*/);
239
240  public static final List<String> DEFAULT_INHERITED_ED_URLS = Arrays.asList(
241      "http://hl7.org/fhir/StructureDefinition/questionnaire-optionRestriction",
242      "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceProfile",
243      "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource",
244      "http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption",
245
246      "http://hl7.org/fhir/StructureDefinition/mimeType");
247
248  /**
249   * These extensions are ignored when found in differentials
250   */  
251  public static final List<String> NON_OVERRIDING_ED_URLS = Arrays.asList(
252      "http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable",
253      ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED,
254      "http://hl7.org/fhir/tools/StructureDefinition/implied-string-prefix",
255      "http://hl7.org/fhir/tools/StructureDefinition/json-empty-behavior",
256      "http://hl7.org/fhir/tools/StructureDefinition/json-nullable",
257      "http://hl7.org/fhir/tools/StructureDefinition/json-primitive-choice",
258      "http://hl7.org/fhir/tools/StructureDefinition/json-property-key",
259      "http://hl7.org/fhir/tools/StructureDefinition/type-specifier",
260      "http://hl7.org/fhir/tools/StructureDefinition/xml-choice-group",
261      ToolingExtensions.EXT_XML_NAMESPACE, ToolingExtensions.EXT_XML_NAMESPACE_DEPRECATED,
262      ToolingExtensions.EXT_XML_NAME, ToolingExtensions.EXT_XML_NAME_DEPRECATED,
263      "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"
264      );
265
266  /**
267   * When these extensions are found, they override whatever is set on the ancestor element 
268   */  
269  public static final List<String> OVERRIDING_ED_URLS = Arrays.asList(
270      "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-date-format",
271      ToolingExtensions.EXT_DATE_RULES,
272      "http://hl7.org/fhir/StructureDefinition/designNote",
273      "http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits",
274      "http://hl7.org/fhir/StructureDefinition/elementdefinition-question",
275      "http://hl7.org/fhir/StructureDefinition/entryFormat",
276      "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces",
277      "http://hl7.org/fhir/StructureDefinition/maxSize",
278      "http://hl7.org/fhir/StructureDefinition/minLength",
279      "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation",
280      "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
281      "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
282      "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
283      "http://hl7.org/fhir/StructureDefinition/questionnaire-signatureRequired",
284      "http://hl7.org/fhir/StructureDefinition/questionnaire-sliderStepValue",
285      "http://hl7.org/fhir/StructureDefinition/questionnaire-supportLink",
286      "http://hl7.org/fhir/StructureDefinition/questionnaire-unit",
287      "http://hl7.org/fhir/StructureDefinition/questionnaire-unitValueSet",
288      "http://hl7.org/fhir/StructureDefinition/questionnaire-usageMode",
289      "http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint",
290      "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"
291      );
292  
293  public IWorkerContext getContext() {
294    return this.context;
295  }
296
297  public static class SourcedChildDefinitions {
298    private StructureDefinition source;
299    private List<ElementDefinition> list;
300    private String path;
301    public SourcedChildDefinitions(StructureDefinition source, List<ElementDefinition> list) {
302      super();
303      this.source = source;
304      this.list = list;
305    }
306    public SourcedChildDefinitions(StructureDefinition source, List<ElementDefinition> list, String path) {
307      super();
308      this.source = source;
309      this.list = list;
310      this.path = path;
311    }
312    public StructureDefinition getSource() {
313      return source;
314    }
315    public List<ElementDefinition> getList() {
316      return list;
317    }
318    public String getPath() {
319      return path;
320    }
321    
322  }
323
324  public class ElementDefinitionResolution {
325
326    private StructureDefinition source;
327    private ElementDefinition element;
328
329    public ElementDefinitionResolution(StructureDefinition source, ElementDefinition element) {
330      this.source = source;
331      this.element = element;
332    }
333
334    public StructureDefinition getSource() {
335      return source;
336    }
337
338    public ElementDefinition getElement() {
339      return element;
340    }
341
342  }
343
344  public static class ElementChoiceGroup {
345    private Row row;
346    private String name;
347    private boolean mandatory;
348    private List<String> elements = new ArrayList<>();
349    
350    public ElementChoiceGroup(String name, boolean mandatory) {
351      super();
352      this.name = name;
353      this.mandatory = mandatory;
354    }
355    public Row getRow() {
356      return row;
357    }
358    public List<String> getElements() {
359      return elements;
360    }
361    public void setRow(Row row) {
362      this.row = row;      
363    }
364    public String getName() {
365      return name;
366    }
367    public boolean isMandatory() {
368      return mandatory;
369    }
370    public void setMandatory(boolean mandatory) {
371      this.mandatory = mandatory;
372    }
373    
374  }
375  
376  private static final int MAX_RECURSION_LIMIT = 10;
377  
378  public static class ExtensionContext {
379
380    private ElementDefinition element;
381    private StructureDefinition defn;
382
383    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
384      this.defn = ext;
385      this.element = ed;
386    }
387
388    public ElementDefinition getElement() {
389      return element;
390    }
391
392    public StructureDefinition getDefn() {
393      return defn;
394    }
395
396    public String getUrl() {
397      if (element == defn.getSnapshot().getElement().get(0))
398        return defn.getUrl();
399      else
400        return element.getSliceName();
401    }
402
403    public ElementDefinition getExtensionValueDefinition() {
404      int i = defn.getSnapshot().getElement().indexOf(element)+1;
405      while (i < defn.getSnapshot().getElement().size()) {
406        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
407        if (ed.getPath().equals(element.getPath()))
408          return null;
409        if (ed.getPath().startsWith(element.getPath()+".value") && !ed.hasSlicing())
410          return ed;
411        i++;
412      }
413      return null;
414    }
415  }
416  
417  private static final boolean COPY_BINDING_EXTENSIONS = false;
418  private static final boolean DONT_DO_THIS = false;
419  
420  private boolean debug;
421  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
422  private final IWorkerContext context;
423  private FHIRPathEngine fpe;
424  private List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
425  private List<String> snapshotStack = new ArrayList<String>();
426  private ProfileKnowledgeProvider pkp;
427//  private boolean igmode;
428  private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R5);
429  private boolean newSlicingProcessing;
430  private String defWebRoot;
431  private boolean autoFixSliceNames;
432  private XVerExtensionManager xver;
433  private boolean wantFixDifferentialFirstElementType;
434  private Set<String> masterSourceFileNames;
435  private Set<String> localFileNames;
436  private Map<String, SourcedChildDefinitions> childMapCache = new HashMap<>();
437  private AllowUnknownProfile allowUnknownProfile = AllowUnknownProfile.ALL_TYPES;
438  private MappingMergeModeOption mappingMergeMode = MappingMergeModeOption.APPEND;
439  private boolean forPublication;
440  private List<StructureDefinition> obligationProfiles = new ArrayList<>();
441  private boolean wantThrowExceptions;
442  private List<String> suppressedMappings= new ArrayList<>();
443  
444  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) {
445    super();
446    this.context = context;
447    if (messages != null) {
448      this.messages = messages;
449    } else {
450      wantThrowExceptions = true;
451    }
452    this.pkp = pkp;
453
454    this.fpe = fpe;
455    if (context != null && this.fpe == null) {
456      this.fpe = new FHIRPathEngine(context, this);
457    }
458  }
459
460  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
461    super();
462    this.context = context;
463    if (messages != null) {
464      this.messages = messages;
465    } else {
466      wantThrowExceptions = true;
467    }
468    this.pkp = pkp;
469    if (context != null) {
470      this.fpe = new FHIRPathEngine(context, this);
471    }
472  }
473  
474  public boolean isWantFixDifferentialFirstElementType() {
475    return wantFixDifferentialFirstElementType;
476  }
477
478  public void setWantFixDifferentialFirstElementType(boolean wantFixDifferentialFirstElementType) {
479    this.wantFixDifferentialFirstElementType = wantFixDifferentialFirstElementType;
480  }
481
482  public boolean isAutoFixSliceNames() {
483    return autoFixSliceNames;
484  }
485
486  public ProfileUtilities setAutoFixSliceNames(boolean autoFixSliceNames) {
487    this.autoFixSliceNames = autoFixSliceNames;
488    return this;
489  }
490
491  public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes) throws DefinitionException {
492    return getChildMap(profile, element, chaseTypes, null);
493  }
494  public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes, String type) throws DefinitionException {
495    String cacheKey = "cm."+profile.getVersionedUrl()+"#"+(element.hasId() ? element.getId() : element.getPath())+"."+chaseTypes;
496    if (childMapCache.containsKey(cacheKey)) {
497      return childMapCache.get(cacheKey);
498    }
499    StructureDefinition src = profile;
500    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
501    List<ElementDefinition> elements = profile.getSnapshot().getElement();
502    int iOffs = elements.indexOf(element) + 1;
503    boolean walksIntoElement = elements.size() > iOffs && elements.get(iOffs).getPath().startsWith(element.getPath());
504    if (element.getContentReference() != null && !walksIntoElement) {
505      List<ElementDefinition> list = null;
506      String id = null;
507      if (element.getContentReference().startsWith("#")) {
508        // internal reference
509        id = element.getContentReference().substring(1);
510        list = profile.getSnapshot().getElement();
511      } else if (element.getContentReference().contains("#")) {
512        // external reference
513        String ref = element.getContentReference();
514        StructureDefinition sd = context.fetchResource(StructureDefinition.class, ref.substring(0, ref.indexOf("#")), profile);
515        if (sd == null) {
516          throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
517        }
518        src = sd;
519        list = sd.getSnapshot().getElement();
520        id = ref.substring(ref.indexOf("#")+1);        
521      } else {
522        throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
523      }
524        
525      for (ElementDefinition e : list) {
526        if (id.equals(e.getId()))
527          return getChildMap(src, e, true);
528      }
529      throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath()));
530
531    } else {
532      String path = element.getPath();
533      for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
534        ElementDefinition e = elements.get(index);
535        if (e.getPath().startsWith(path + ".")) {
536          // We only want direct children, not all descendants
537          if (!e.getPath().substring(path.length()+1).contains("."))
538            res.add(e);
539        } else
540          break;
541      }
542      if (res.isEmpty() && chaseTypes) {
543        // we've got no in-line children. Some consumers of this routine will figure this out for themselves but most just want to walk into 
544        // the type children.
545        src = null;
546        if (type != null) {
547          src = context.fetchTypeDefinition(type);
548        } else if (element.getType().isEmpty()) {
549          throw new DefinitionException("No defined children and no type information on element '"+element.getId()+"'");
550        } else if (element.getType().size() > 1) {
551          throw new DefinitionException("No defined children and multiple possible types '"+element.typeSummary()+"' on element '"+element.getId()+"'");
552        } else if (element.getType().get(0).getProfile().size() > 1) {
553          throw new DefinitionException("No defined children and multiple possible type profiles '"+element.typeSummary()+"' on element '"+element.getId()+"'");
554        } else if (element.getType().get(0).hasProfile()) {
555          src = context.fetchResource(StructureDefinition.class, element.getType().get(0).getProfile().get(0).getValue());
556          if (src == null) {
557            throw new DefinitionException("No defined children and unknown type profile '"+element.typeSummary()+"' on element '"+element.getId()+"'");
558          }
559        } else {
560          src = context.fetchTypeDefinition(element.getType().get(0).getWorkingCode());
561          if (src == null) {
562            throw new DefinitionException("No defined children and unknown type '"+element.typeSummary()+"' on element '"+element.getId()+"'");
563          }
564        }
565        SourcedChildDefinitions scd  = getChildMap(src, src.getSnapshot().getElementFirstRep(), false);
566        res = scd.list;
567      }
568      SourcedChildDefinitions result  = new SourcedChildDefinitions(src, res);
569      childMapCache.put(cacheKey, result);
570      return result;
571    }
572  }
573
574
575  public List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
576    if (!element.hasSlicing())
577      throw new Error(context.formatMessage(I18nConstants.GETSLICELIST_SHOULD_ONLY_BE_CALLED_WHEN_THE_ELEMENT_HAS_SLICING));
578
579    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
580    List<ElementDefinition> elements = profile.getSnapshot().getElement();
581    String path = element.getPath();
582    int start = findElementIndex(elements, element);
583    for (int index = start + 1; index < elements.size(); index++) {
584      ElementDefinition e = elements.get(index);
585      if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
586        // We want elements with the same path (until we hit an element that doesn't start with the same path)
587        if (e.getPath().equals(element.getPath()))
588          res.add(e);
589      } else
590        break;
591    }
592    return res;
593  }
594
595
596  private int findElementIndex(List<ElementDefinition> elements, ElementDefinition element) {
597    int res = elements.indexOf(element);
598    if (res == -1) {
599      for (int i = 0; i < elements.size(); i++) {
600        Element t  = elements.get(i);
601        if (t.getId().equals(element.getId())) {
602          res = i;
603        }
604      }
605    }
606    return res;
607  }
608
609  /**
610   * Given a Structure, navigate to the element given by the path and return the direct children of that element
611   *
612   * @param profile The structure to navigate into
613   * @param path The path of the element within the structure to get the children for
614   * @return A List containing the element children (all of them are Elements)
615   */
616  public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) {
617    return getChildList(profile, path, id, false);
618  }
619  
620  public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) {
621    return getChildList(profile, path, id, diff, false);
622  }
623  
624  public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff, boolean refs) {
625    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
626
627    boolean capturing = id==null;
628    if (id==null && !path.contains("."))
629      capturing = true;
630  
631    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
632    for (ElementDefinition e : list) {
633      if (e == null)
634        throw new Error(context.formatMessage(I18nConstants.ELEMENT__NULL_, profile.getUrl()));
635//      if (e.getId() == null) // this is sort of true, but in some corner cases it's not, and in those cases, we don't care
636//        throw new Error(context.formatMessage(I18nConstants.ELEMENT_ID__NULL__ON_, e.toString(), profile.getUrl()));
637      
638      if (!capturing && id!=null && e.getId().equals(id)) {
639        capturing = true;
640      }
641      
642      // If our element is a slice, stop capturing children as soon as we see the next slice
643      if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path))
644        break;
645      
646      if (capturing) {
647        String p = e.getPath();
648  
649        if (refs && !Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
650          if (path.length() > p.length()) {
651            return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff);
652          } else if (e.getContentReference().startsWith("#")) {
653            return getChildList(profile, e.getContentReference().substring(1), null, diff);            
654          } else if (e.getContentReference().contains("#")) {
655            String url = e.getContentReference().substring(0, e.getContentReference().indexOf("#"));
656            StructureDefinition sd = context.fetchResource(StructureDefinition.class, url, profile);
657            if (sd == null) {
658              throw new DefinitionException("Unable to find Structure "+url);
659            }
660            return getChildList(sd, e.getContentReference().substring(e.getContentReference().indexOf("#")+1), null, diff);            
661          } else {
662            return getChildList(profile, e.getContentReference(), null, diff);
663          }
664          
665        } else if (p.startsWith(path+".") && !p.equals(path)) {
666          String tail = p.substring(path.length()+1);
667          if (!tail.contains(".")) {
668            res.add(e);
669          }
670        }
671      }
672    }
673
674    return res;
675  }
676
677  public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff, boolean refs) {
678    return getChildList(structure, element.getPath(), element.getId(), diff, refs);
679  }
680
681  public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) {
682    return getChildList(structure, element.getPath(), element.getId(), diff);
683  }
684
685  public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
686    if (element.hasContentReference()) {
687      ElementDefinition target = element;
688      for (ElementDefinition t : structure.getSnapshot().getElement()) {
689        if (t.getId().equals(element.getContentReference().substring(1))) {
690          target = t;
691        }
692      }      
693      return getChildList(structure, target.getPath(), target.getId(), false);
694    } else {
695      return getChildList(structure, element.getPath(), element.getId(), false);
696    }
697        }
698
699  /**
700   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
701   *
702   * @param base - the base structure on which the differential will be applied
703   * @param derived - the differential to apply to the base
704   * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL (e.g. the canonical URL)
705   * @param webUrl - where the base has relative urls in markdown, these need to be converted to absolutes by prepending this URL (this is not the same as the canonical URL)
706   * @return
707   * @throws FHIRException 
708   * @throws DefinitionException 
709   * @throws Exception
710   */
711  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException {
712    if (base == null) {
713      throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED));
714    }
715    if (derived == null) {
716      throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED));
717    }
718    checkNotGenerating(base, "Base for generating a snapshot for the profile "+derived.getUrl());
719    checkNotGenerating(derived, "Focus for generating a snapshot");
720
721    if (!base.hasType()) {
722      throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl()));
723    }
724    if (!derived.hasType()) {
725      throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl()));
726    }
727    if (!derived.hasDerivation()) {
728      throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl()));
729    }
730    if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) {
731      throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType()));
732    }
733    if (!base.hasSnapshot()) {
734      StructureDefinition sdb = context.fetchResource(StructureDefinition.class, base.getBaseDefinition());
735      if (sdb == null)
736        throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE__FOR_, base.getBaseDefinition(), base.getUrl()));
737      checkNotGenerating(sdb, "an extension base");
738      generateSnapshot(sdb, base, base.getUrl(), (sdb.hasWebPath()) ? Utilities.extractBaseUrl(sdb.getWebPath()) : webUrl, base.getName());
739    }
740    fixTypeOfResourceId(base);
741    if (base.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
742      checkTypeParameters(base, derived);
743    }
744    
745    if (snapshotStack.contains(derived.getUrl())) {
746      throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString()));
747    }
748    derived.setGeneratingSnapshot(true);
749    snapshotStack.add(derived.getUrl());
750    boolean oldCopyUserData = Base.isCopyUserData();
751    Base.setCopyUserData(true);
752    try {
753
754      if (!Utilities.noString(webUrl) && !webUrl.endsWith("/"))
755        webUrl = webUrl + '/';
756
757      if (defWebRoot == null)
758        defWebRoot = webUrl;
759      derived.setSnapshot(new StructureDefinitionSnapshotComponent());
760
761      try {
762        checkDifferential(derived.getDifferential().getElement(), derived.getTypeName(), derived.getUrl());
763        checkDifferentialBaseType(derived);
764
765          log.debug("Differential: ");
766          int debugPadding = 0;
767          for (ElementDefinition ed : derived.getDifferential().getElement()) {
768            log.debug(" "+Utilities.padLeft(Integer.toString(debugPadding), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
769            debugPadding++;
770          }
771          log.debug("Snapshot: ");
772          debugPadding = 0;
773          for (ElementDefinition ed : base.getSnapshot().getElement()) {
774            log.debug(" "+Utilities.padLeft(Integer.toString(debugPadding), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
775            debugPadding++;
776          }
777
778
779        copyInheritedExtensions(base, derived, webUrl);
780
781        findInheritedObligationProfiles(derived);
782        // so we have two lists - the base list, and the differential list
783        // the differential list is only allowed to include things that are in the base list, but
784        // is allowed to include them multiple times - thereby slicing them
785
786        // our approach is to walk through the base list, and see whether the differential
787        // says anything about them.
788        // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
789
790
791        for (ElementDefinition e : derived.getDifferential().getElement()) 
792          e.clearUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT);
793
794        // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
795        StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here because we're sometimes going to hack the differential while processing it. Have to migrate user data back afterwards
796        new SnapshotGenerationPreProcessor(this).process(diff, derived);
797        
798        StructureDefinitionSnapshotComponent baseSnapshot  = base.getSnapshot();
799        if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
800          String derivedType = derived.getTypeName();
801
802          baseSnapshot = cloneSnapshot(baseSnapshot, base.getTypeName(), derivedType);
803        }
804        //      if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) {
805        //        debug = true;
806        //      }
807
808        MappingAssistant mappingDetails = new MappingAssistant(mappingMergeMode, base, derived, context.getVersion(), suppressedMappings);
809        
810        ProfilePathProcessor.processPaths(this, base, derived, url, webUrl, diff, baseSnapshot, mappingDetails);
811
812        checkGroupConstraints(derived);
813        if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
814          int i = 0;
815          for (ElementDefinition e : diff.getElement()) {
816            if (!e.hasUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT) && e.getPath().contains(".")) {
817              ElementDefinition existing = getElementInCurrentContext(e.getPath(), derived.getSnapshot().getElement());
818              if (existing != null) {
819                updateFromDefinition(existing, e, profileName, false, url, base, derived, "StructureDefinition.differential.element["+i+"]", mappingDetails, false);
820              } else {
821                ElementDefinition outcome = updateURLs(url, webUrl, e.copy(), true);
822                e.setUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT, outcome);
823                markExtensions(outcome, true, derived);
824                derived.getSnapshot().addElement(outcome);
825                if (walksInto(diff.getElement(), e)) {
826                  if (e.getType().size() > 1) {
827                    throw new DefinitionException("Unsupported scenario: specialization walks into multiple types at "+e.getId()); 
828                  } else {
829                    addInheritedElementsForSpecialization(derived.getSnapshot(), outcome, outcome.getTypeFirstRep().getWorkingCode(), outcome.getPath(), url, webUrl);
830                  }
831                }
832              }
833            }
834            i++;
835          }
836        }
837
838        for (int i = 0; i < derived.getSnapshot().getElement().size(); i++) {
839          ElementDefinition ed = derived.getSnapshot().getElement().get(i);
840          if (ed.getType().size() > 1) {
841            List<TypeRefComponent> toRemove = new ArrayList<ElementDefinition.TypeRefComponent>();
842            for (TypeRefComponent tr : ed.getType()) {
843              ElementDefinition typeSlice = findTypeSlice(derived.getSnapshot().getElement(), i, ed.getPath(), tr.getWorkingCode());
844              if (typeSlice != null && typeSlice.prohibited()) {
845                toRemove.add(tr);
846              }
847            }
848            ed.getType().removeAll(toRemove);
849          }
850        }
851        if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty())
852          throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl()));
853        mappingDetails.update();
854
855        setIds(derived, false);
856
857          log.debug("Differential: ");
858          int debugPad = 0;
859          for (ElementDefinition ed : derived.getDifferential().getElement()) {
860            log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed));
861            debugPad++;
862          }
863          log.debug("Diff (processed): ");
864          debugPad = 0;
865          for (ElementDefinition ed : diff.getElement()) {
866            log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+
867                " -> "+(destInfo(ed, derived.getSnapshot().getElement())));
868            debugPad++;
869          }
870          log.debug("Snapshot: ");
871          debugPad = 0;
872          for (ElementDefinition ed : derived.getSnapshot().getElement()) {
873            log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
874            debugPad++;
875          }
876
877        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
878        //Check that all differential elements have a corresponding snapshot element
879        int ce = 0;
880        int i = 0;
881        for (ElementDefinition e : diff.getElement()) {
882          if (!e.hasUserData(UserDataNames.SNAPSHOT_diff_source)) {
883            // was injected during preprocessing - this is ok
884          } else {
885            if (e.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS))
886              ((Base) e.getUserData(UserDataNames.SNAPSHOT_diff_source)).setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, e.getUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS));
887            if (e.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER))
888              ((Base) e.getUserData(UserDataNames.SNAPSHOT_diff_source)).setUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER, e.getUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER));
889          }
890          if (!e.hasUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT)) {
891            b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath());
892            ce++;
893            if (e.hasId()) {
894              String msg = "No match found for "+e.getId()+" in the generated snapshot: check that the path and definitions are legal in the differential (including order)";
895              addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, "StructureDefinition.differential.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR));
896            }
897          } else {
898            ElementDefinition sed = (ElementDefinition) e.getUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT);
899            sed.setUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF, e); // note: this means diff/snapshot are cross-linked
900          }
901          i++;
902        }
903        if (!Utilities.noString(b.toString())) {
904          String msg = "The profile "+derived.getUrl()+" has "+ce+" "+Utilities.pluralize("element", ce)+" in the differential ("+b.toString()+") that don't have a matching element in the snapshot: check that the path and definitions are legal in the differential (including order)";
905
906            log.debug("Error in snapshot generation: "+msg);
907
908          log.debug("Differential: ");
909              for (ElementDefinition ed : derived.getDifferential().getElement())
910                log.debug("  "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
911          log.debug("Snapshot: ");
912              for (ElementDefinition ed : derived.getSnapshot().getElement())
913                log.debug("  "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
914
915
916          handleError(url, msg);
917        }
918        // hack around a problem in R4 definitions (somewhere?)
919        for (ElementDefinition ed : derived.getSnapshot().getElement()) {
920          for (ElementDefinitionMappingComponent mm : ed.getMapping()) {
921            if (mm.hasMap()) {
922              mm.setMap(mm.getMap().trim());
923            }
924          }
925          for (ElementDefinitionConstraintComponent s : ed.getConstraint()) {
926            if (s.hasSource()) {
927              String ref = s.getSource();
928              if (!Utilities.isAbsoluteUrl(ref)) {
929                if (ref.contains(".")) {
930                  s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref.substring(0, ref.indexOf("."))+"#"+ref);
931                } else {
932                  s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref);
933                }
934              }  
935            }
936          }
937        }
938        if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
939          for (ElementDefinition ed : derived.getSnapshot().getElement()) {
940            if (!ed.hasBase()) {
941              ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax());
942            }
943          }
944        }
945        // check slicing is ok while we're at it. and while we're doing this. update the minimum count if we need to
946        String tn = derived.getType();
947        if (tn.contains("/")) {
948          tn = tn.substring(tn.lastIndexOf("/")+1);
949        }
950        Map<String, ElementDefinitionCounter> slices = new HashMap<>();
951        i = 0;
952        for (ElementDefinition ed : derived.getSnapshot().getElement()) {
953          if (ed.hasSlicing()) {
954            slices.put(ed.getPath(), new ElementDefinitionCounter(ed, i));            
955          } else {
956            Set<String> toRemove = new HashSet<>();
957            for (String s : slices.keySet()) {
958              if (Utilities.charCount(s, '.') >= Utilities.charCount(ed.getPath(), '.') && !s.equals(ed.getPath())) {
959                toRemove.add(s);
960              }
961            }
962            for (String s : toRemove) {
963              ElementDefinitionCounter slice = slices.get(s);
964              int count = slice.checkMin();
965              boolean repeats = !"1".equals(slice.getFocus().getBase().getMax()); // type slicing if repeats = 1
966              if (count > -1 && repeats) {
967                if (slice.getFocus().hasUserData(UserDataNames.SNAPSHOT_auto_added_slicing)) {
968                  slice.getFocus().setMin(count);
969                } else {
970                  String msg = "The slice definition for "+slice.getFocus().getId()+" has a minimum of "+slice.getFocus().getMin()+" but the slices add up to a minimum of "+count; 
971                  addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
972                      "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION).setIgnorableError(true));
973                }
974              }
975              count = slice.checkMax();
976              if (count > -1 && repeats) {
977                String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" but the slices add up to a maximum of "+count+". Check that this is what is intended"; 
978                addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
979                    "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.INFORMATION));                                            
980              }
981              if (!slice.checkMinMax()) {
982                String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" which is less than the minimum of "+slice.getFocus().getMin(); 
983                addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
984                    "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.WARNING));                                                            
985              }
986              slices.remove(s);
987            }            
988          }
989          if (ed.getPath().contains(".") && !ed.getPath().startsWith(tn+".")) {
990            throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" doesn't have the right path (should start with "+tn+".");
991          }
992          if (ed.hasSliceName() && !slices.containsKey(ed.getPath())) {
993            String msg = "The element "+ed.getId()+" launches straight into slicing without the slicing being set up properly first";
994            addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
995                "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true));            
996          }
997          if (ed.hasSliceName() && slices.containsKey(ed.getPath())) {
998            if (!slices.get(ed.getPath()).count(ed, ed.getSliceName())) {
999              String msg = "Duplicate slice name "+ed.getSliceName()+" on "+ed.getId()+" (["+i+"])";
1000              addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
1001                  "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true));            
1002            }
1003          }
1004          i++;
1005        }
1006        
1007        i = 0;
1008        // last, check for wrong profiles or target profiles, or unlabeled extensions
1009        for (ElementDefinition ed : derived.getSnapshot().getElement()) {
1010          for (TypeRefComponent t : ed.getType()) {
1011            for (UriType u : t.getProfile()) {
1012              StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue(), derived);
1013              if (sd == null) {
1014                if (makeXVer().matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) {
1015                  sd = xver.makeDefinition(u.getValue());              
1016                }
1017              }
1018              if (sd == null) {
1019                addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
1020                      "StructureDefinition.snapshot.element["+i+"]", "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING));
1021              } else {
1022                String wt = t.getWorkingCode();
1023                if (ed.getPath().equals("Bundle.entry.response.outcome")) {
1024                  wt = "OperationOutcome";
1025                }
1026                String tt = sd.getType();
1027                boolean elementProfile = u.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT);
1028                if (elementProfile) {
1029                  ElementDefinition edt = sd.getSnapshot().getElementById(u.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT));
1030                  if (edt == null) {
1031                    handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt);
1032                  } else {
1033                    tt = edt.typeSummary();
1034                  }
1035                }
1036                if (!tt.equals(wt)) {
1037                  boolean ok = !elementProfile && isCompatibleType(wt, sd);
1038                  if (!ok) {
1039                    handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt);
1040                  }
1041                }
1042              }
1043            }
1044          }
1045          i++;
1046        }
1047      } catch (Exception e) {
1048        log.error("Exception generating snapshot for "+derived.getVersionedUrl()+": " +e.getMessage());
1049        log.debug(e.getMessage(), e);
1050        // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
1051        derived.setSnapshot(null);
1052        derived.setGeneratingSnapshot(false);
1053        throw e;
1054      }
1055    } finally {
1056      Base.setCopyUserData(oldCopyUserData);          
1057      derived.setGeneratingSnapshot(false);
1058      snapshotStack.remove(derived.getUrl());
1059    }
1060    if (base.getVersion() != null) {
1061      derived.getSnapshot().addExtension(ToolingExtensions.EXT_VERSION_BASE, new StringType(base.getVersion()));
1062    }
1063    derived.setGeneratedSnapshot(true);
1064    //derived.setUserData(UserDataNames.SNAPSHOT_GENERATED, true); // used by the publisher
1065    derived.setUserData(UserDataNames.SNAPSHOT_GENERATED_MESSAGES, messages); // used by the publisher
1066  }
1067
1068
1069  private String destInfo(ElementDefinition ed, List<ElementDefinition> snapshot) {
1070    ElementDefinition sed = (ElementDefinition) ed.getUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT);
1071    if (sed == null) {
1072      return "(null)";
1073    } else {
1074      int index = snapshot.indexOf(sed);
1075      return ""+index+" "+sed.getId();
1076    }
1077  }
1078
1079  private ElementDefinition findTypeSlice(List<ElementDefinition> list, int i, String path, String typeCode) {
1080    for (int j = i+1; j < list.size(); j++) {
1081      ElementDefinition ed = list.get(j);
1082      if (pathMatches(path, ed) && typeMatches(ed, typeCode)) {
1083        return ed;
1084      }
1085    }
1086    return null;
1087  }
1088
1089  private boolean pathMatches(String path, ElementDefinition ed) {
1090    String p = ed.getPath();
1091    if (path.equals(p)) {
1092      return true;
1093    }
1094    if (path.endsWith("[x]")) { // it should
1095      path = path.substring(0, path.length()-3);
1096      if (p.startsWith(path) && p.length() > path.length() && !p.substring(path.length()).contains(".")) {
1097        return true;
1098      }
1099    }
1100    return false;
1101  }
1102
1103  private boolean typeMatches(ElementDefinition ed, String typeCode) {
1104    return ed.getType().size() == 1 && typeCode.equals(ed.getTypeFirstRep().getWorkingCode());
1105  }
1106
1107  private void checkTypeParameters(StructureDefinition base, StructureDefinition derived) {
1108    String bt = ToolingExtensions.readStringSubExtension(base, ToolingExtensions.EXT_TYPE_PARAMETER, "type");
1109    if (!derived.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
1110      throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_MISSING, base.getVersionedUrl(), bt, derived.getVersionedUrl()));
1111    }
1112    String dt = ToolingExtensions.readStringSubExtension(derived, ToolingExtensions.EXT_TYPE_PARAMETER, "type");
1113    StructureDefinition bsd = context.fetchTypeDefinition(bt);
1114    StructureDefinition dsd = context.fetchTypeDefinition(dt);
1115    if (bsd == null) {
1116      throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, base.getVersionedUrl(), bt));
1117    }
1118    if (dsd == null) {
1119      throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, derived.getVersionedUrl(), dt));
1120    }
1121    StructureDefinition t = dsd;
1122    while (t != bsd && t != null) {
1123      t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
1124    }
1125    if (t == null) {
1126      throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_INVALID, base.getVersionedUrl(), bt, derived.getVersionedUrl(), dt));
1127    }
1128  }
1129
1130  private XVerExtensionManager makeXVer() {
1131    if (xver == null) {
1132      xver = new XVerExtensionManager(context);
1133    }
1134    return xver;
1135  }
1136
1137  private ElementDefinition getElementInCurrentContext(String path, List<ElementDefinition> list) {
1138    for (int i = list.size() -1; i >= 0; i--) {
1139      ElementDefinition t = list.get(i);
1140      if (t.getPath().equals(path)) {
1141        return t;
1142      } else if (!path.startsWith(head(t.getPath()))) {
1143        return null;
1144      }
1145    }
1146    return null;
1147  }
1148
1149  private String head(String path) {
1150    return path.contains(".") ? path.substring(0, path.lastIndexOf(".")+1) : path;
1151  }
1152
1153  private void findInheritedObligationProfiles(StructureDefinition derived) {
1154    for (Extension ext : derived.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_INHERITS)) {
1155      StructureDefinition op = context.fetchResource(StructureDefinition.class, ext.getValueCanonicalType().primitiveValue());
1156      if (op != null && ToolingExtensions.readBoolExtension(op, ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) {
1157        if (derived.getBaseDefinition().equals(op.getBaseDefinition())) {
1158          obligationProfiles.add(op);
1159        }
1160      }
1161    }
1162  }
1163
1164  private void handleError(String url, String msg) {
1165    addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR));
1166  }
1167
1168  private void addMessage(ValidationMessage msg) {
1169    messages.add(msg);
1170    if (msg.getLevel() == IssueSeverity.ERROR && wantThrowExceptions) {
1171      throw new DefinitionException(msg.getMessage());   
1172    }
1173  }
1174
1175  private void copyInheritedExtensions(StructureDefinition base, StructureDefinition derived, String webUrl) {
1176    for (Extension ext : base.getExtension()) {
1177      if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS)) {
1178        String action = getExtensionAction(ext.getUrl());
1179        if (!"ignore".equals(action)) {
1180          boolean exists = derived.hasExtension(ext.getUrl());
1181          if ("add".equals(action) || !exists) {
1182            Extension next = ext.copy();
1183            if (next.hasValueMarkdownType()) {
1184              MarkdownType md = next.getValueMarkdownType();
1185              md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
1186            }
1187            derived.getExtension().add(next);            
1188          } else if ("overwrite".equals(action)) {
1189            Extension oext = derived.getExtensionByUrl(ext.getUrl());
1190            Extension next = ext.copy();
1191            if (next.hasValueMarkdownType()) {
1192              MarkdownType md = next.getValueMarkdownType();
1193              md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
1194            }
1195            oext.setValue(next.getValue());  
1196          }
1197        }
1198      }
1199    }
1200  }
1201
1202  private String getExtensionAction(String url) {
1203    StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
1204    if (sd != null && sd.hasExtension(ToolingExtensions.EXT_SNAPSHOT_BEHAVIOR)) {
1205      return ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_SNAPSHOT_BEHAVIOR);
1206    }
1207    return "defer";
1208  }
1209
1210  private void addInheritedElementsForSpecialization(StructureDefinitionSnapshotComponent snapshot, ElementDefinition focus, String type, String path, String url, String weburl) {
1211     StructureDefinition sd = context.fetchTypeDefinition(type);
1212     if (sd != null) {
1213       // don't do this. should already be in snapshot ... addInheritedElementsForSpecialization(snapshot, focus, sd.getBaseDefinition(), path, url, weburl);
1214       for (ElementDefinition ed : sd.getSnapshot().getElement()) {
1215         if (ed.getPath().contains(".")) {
1216           ElementDefinition outcome = updateURLs(url, weburl, ed.copy(), true);
1217           outcome.setPath(outcome.getPath().replace(sd.getTypeName(), path));
1218           markExtensions(outcome, false, sd);
1219           snapshot.getElement().add(outcome);
1220         } else {
1221           focus.getConstraint().addAll(ed.getConstraint());
1222           for (Extension ext : ed.getExtension()) {
1223             if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) && !focus.hasExtension(ext.getUrl())) {
1224               focus.getExtension().add(markExtensionSource(ext.copy(), false, sd));
1225             }
1226           }
1227         }
1228       }
1229     }
1230  }
1231
1232  private boolean walksInto(List<ElementDefinition> list, ElementDefinition ed) {
1233    int i = list.indexOf(ed);
1234    return (i < list.size() - 1) && list.get(i + 1).getPath().startsWith(ed.getPath()+".");
1235  }
1236
1237  private void fixTypeOfResourceId(StructureDefinition base) {
1238    if (base.getKind() == StructureDefinitionKind.RESOURCE && (base.getFhirVersion() == null || VersionUtilities.isR4Plus(base.getFhirVersion().toCode()))) {
1239      fixTypeOfResourceId(base.getSnapshot().getElement());
1240      fixTypeOfResourceId(base.getDifferential().getElement());      
1241    }
1242  }
1243
1244  private void fixTypeOfResourceId(List<ElementDefinition> list) {
1245    for (ElementDefinition ed : list) {
1246      if (ed.hasBase() && ed.getBase().getPath().equals("Resource.id")) {
1247        for (TypeRefComponent tr : ed.getType()) {
1248          tr.setCode("http://hl7.org/fhirpath/System.String");
1249          tr.removeExtension(ToolingExtensions.EXT_FHIR_TYPE);
1250          ToolingExtensions.addUrlExtension(tr, ToolingExtensions.EXT_FHIR_TYPE, "id");
1251        }
1252      }
1253    }    
1254  }
1255
1256  /**
1257   * Check if derived has the correct base type
1258   *
1259   * Clear first element of differential under certain conditions.
1260   *
1261   * @param derived
1262   * @throws Error
1263   */
1264  private void checkDifferentialBaseType(StructureDefinition derived) throws Error {
1265    if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) {
1266      if (wantFixDifferentialFirstElementType && typeMatchesAncestor(derived.getDifferential().getElementFirstRep().getType(), derived.getBaseDefinition(), derived)) {
1267        derived.getDifferential().getElementFirstRep().getType().clear();
1268      } else if (derived.getKind() != StructureDefinitionKind.LOGICAL) {
1269        throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_DIFFERENTIAL_ELEMENT));
1270      }
1271    }
1272  }
1273
1274  private boolean typeMatchesAncestor(List<TypeRefComponent> type, String baseDefinition, Resource src) {
1275    StructureDefinition sd = context.fetchResource(StructureDefinition.class, baseDefinition, src);
1276    return sd != null && type.size() == 1 && sd.getType().equals(type.get(0).getCode()); 
1277  }
1278
1279
1280  private void checkGroupConstraints(StructureDefinition derived) {
1281    List<ElementDefinition> toRemove = new ArrayList<>();
1282//    List<ElementDefinition> processed = new ArrayList<>();
1283    for (ElementDefinition element : derived.getSnapshot().getElement()) {
1284      if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) {
1285        checkForChildrenInGroup(derived, toRemove, element);
1286      }
1287    }
1288    derived.getSnapshot().getElement().removeAll(toRemove);
1289  }
1290
1291  private void checkForChildrenInGroup(StructureDefinition derived, List<ElementDefinition> toRemove, ElementDefinition element) throws Error {
1292    List<ElementDefinition> children = getChildren(derived, element);
1293    List<ElementChoiceGroup> groups = readChoices(element, children);
1294    for (ElementChoiceGroup group : groups) {
1295      String mandated = null;
1296      Set<String> names = new HashSet<>();
1297      for (ElementDefinition ed : children) {
1298        String name = tail(ed.getPath());
1299        if (names.contains(name)) {
1300          throw new Error("huh?");
1301        } else {
1302          names.add(name);
1303        }
1304        if (group.getElements().contains(name)) {
1305          if (ed.getMin() == 1) {
1306            if (mandated == null) {
1307              mandated = name;
1308            } else {
1309              throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name);
1310            }
1311          }
1312        }
1313      }
1314      if (mandated != null) {
1315        for (ElementDefinition ed : children) {
1316          String name = tail(ed.getPath());
1317          if (group.getElements().contains(name) && !mandated.equals(name)) {
1318            ed.setMax("0");
1319            addAllChildren(derived, ed, toRemove);
1320          }
1321        }
1322      }
1323    }
1324  }
1325
1326  private List<ElementDefinition> getChildren(StructureDefinition derived, ElementDefinition element) {
1327    List<ElementDefinition> elements = derived.getSnapshot().getElement();
1328    int index = elements.indexOf(element) + 1;
1329    String path = element.getPath()+".";
1330    List<ElementDefinition> list = new ArrayList<>();
1331    while (index < elements.size()) {
1332      ElementDefinition e = elements.get(index);
1333      String p = e.getPath();
1334      if (p.startsWith(path) && !e.hasSliceName()) {
1335        if (!p.substring(path.length()).contains(".")) {
1336          list.add(e);
1337        }
1338        index++;
1339      } else  {
1340        break;
1341      }
1342    }
1343    return list;
1344  }
1345
1346  private void addAllChildren(StructureDefinition derived, ElementDefinition element, List<ElementDefinition> toRemove) {
1347    List<ElementDefinition> children = getChildList(derived, element);
1348    for (ElementDefinition child : children) {
1349      toRemove.add(child);
1350      addAllChildren(derived, child, toRemove);
1351    }
1352  }
1353
1354  /**
1355   * Check that a differential is valid.
1356   * @param elements
1357   * @param type
1358   * @param url
1359   */
1360  private void checkDifferential(List<ElementDefinition> elements, String type, String url) {
1361    boolean first = true;
1362    String t = urlTail(type);
1363    for (ElementDefinition ed : elements) {
1364      if (!ed.hasPath()) {
1365        throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url));
1366      }
1367      String p = ed.getPath();
1368      if (p == null) {
1369        throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_VALUE_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url));
1370      }
1371      if (!((first && t.equals(p)) || p.startsWith(t+"."))) {
1372        throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__MUST_START_WITH_, p, url, t, (first ? " (or be '"+t+"')" : "")));
1373      }
1374      if (p.contains(".")) {
1375        // Element names (the parts of a path delineated by the '.' character) SHALL NOT contain whitespace (i.e. Unicode characters marked as whitespace)
1376        // Element names SHALL NOT contain the characters ,:;'"/|?!@#$%^&*()[]{}
1377        // Element names SHOULD not contain non-ASCII characters
1378        // Element names SHALL NOT exceed 64 characters in length
1379        String[] pl = p.split("\\.");
1380        for (String pp : pl) {
1381          if (pp.length() < 1) {
1382            throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_MISING_, p, url));
1383          }
1384          if (pp.length() > 64) {
1385            throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_EXCEEDS_64_CHARS_IN_LENGTH, p, url));
1386          }
1387          for (char ch : pp.toCharArray()) {
1388            if (Utilities.isWhitespace(ch)) {
1389              throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NO_UNICODE_WHITESPACE, p, url));
1390            }
1391            if (Utilities.existsInList(ch, ',', ':', ';', '\'', '"', '/', '|', '?', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '{', '}')) {
1392              throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch));
1393            }
1394            if (ch < ' ' || ch > 'z') {
1395              throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch));
1396            }
1397          }
1398          if (pp.contains("[") || pp.contains("]")) {
1399            if (!pp.endsWith("[x]") || (pp.substring(0, pp.length()-3).contains("[") || (pp.substring(0, pp.length()-3).contains("]")))) {
1400              throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTERS_, p, url));
1401            }
1402          }
1403        }
1404      }
1405    }    
1406  }
1407
1408
1409  private boolean isCompatibleType(String base, StructureDefinition sdt) {
1410    StructureDefinition sdb = context.fetchTypeDefinition(base);
1411    if (sdb.getType().equals(sdt.getType())) {
1412      return true;
1413    }
1414    StructureDefinition sd = context.fetchTypeDefinition(sdt.getType());
1415    while (sd != null) {
1416      if (sd.getType().equals(sdb.getType())) {
1417        return true;
1418      }
1419      if (sd.getUrl().equals(sdb.getUrl())) {
1420        return true;
1421      }
1422      sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 
1423    }
1424    return false;
1425  }
1426
1427
1428  private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) {
1429    StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent();
1430    for (ElementDefinition sed : source.getElement()) {
1431      ElementDefinition ted = sed.copy();
1432      diff.getElement().add(ted);
1433      ted.setUserData(UserDataNames.SNAPSHOT_diff_source, sed);
1434    }
1435    return diff;
1436  }
1437
1438  private StructureDefinitionSnapshotComponent cloneSnapshot(StructureDefinitionSnapshotComponent source, String baseType, String derivedType) {
1439        StructureDefinitionSnapshotComponent diff = new StructureDefinitionSnapshotComponent();
1440    for (ElementDefinition sed : source.getElement()) {
1441      ElementDefinition ted = sed.copy();
1442      ted.setId(ted.getId().replaceFirst(baseType,derivedType));
1443      ted.setPath(ted.getPath().replaceFirst(baseType,derivedType));
1444      diff.getElement().add(ted);
1445    }
1446    return diff;
1447  }
1448
1449  private String constraintSummary(ElementDefinition ed) {
1450    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1451    if (ed.hasPattern())
1452      b.append("pattern="+ed.getPattern().fhirType());
1453    if (ed.hasFixed())
1454      b.append("fixed="+ed.getFixed().fhirType());
1455    if (ed.hasConstraint())
1456      b.append("constraints="+ed.getConstraint().size());
1457    return b.toString();
1458  }
1459
1460
1461  private String sliceSummary(ElementDefinition ed) {
1462    if (!ed.hasSlicing() && !ed.hasSliceName())
1463      return "";
1464    if (ed.hasSliceName())
1465      return " (slicename = "+ed.getSliceName()+")";
1466    
1467    StringBuilder b = new StringBuilder();
1468    boolean first = true;
1469    for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) {
1470      if (first) 
1471        first = false;
1472      else
1473        b.append("|");
1474      b.append(d.getPath());
1475    }
1476    return " (slicing by "+b.toString()+")";
1477  }
1478
1479
1480//  private String typeSummary(ElementDefinition ed) {
1481//    StringBuilder b = new StringBuilder();
1482//    boolean first = true;
1483//    for (TypeRefComponent tr : ed.getType()) {
1484//      if (first) 
1485//        first = false;
1486//      else
1487//        b.append("|");
1488//      b.append(tr.getWorkingCode());
1489//    }
1490//    return b.toString();
1491//  }
1492
1493  private String typeSummaryWithProfile(ElementDefinition ed) {
1494    StringBuilder b = new StringBuilder();
1495    boolean first = true;
1496    for (TypeRefComponent tr : ed.getType()) {
1497      if (first) 
1498        first = false;
1499      else
1500        b.append("|");
1501      b.append(tr.getWorkingCode());
1502      if (tr.hasProfile()) {
1503        b.append("(");
1504        b.append(tr.getProfile());
1505        b.append(")");
1506        
1507      }
1508    }
1509    return b.toString();
1510  }
1511
1512
1513//  private boolean findMatchingElement(String id, List<ElementDefinition> list) {
1514//    for (ElementDefinition ed : list) {
1515//      if (ed.getId().equals(id))
1516//        return true;
1517//      if (id.endsWith("[x]")) {
1518//        if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains("."))
1519//          return true;
1520//      }
1521//    }
1522//    return false;
1523//  }
1524
1525  protected ElementDefinition getById(List<ElementDefinition> list, String baseId) {
1526    for (ElementDefinition t : list) {
1527      if (baseId.equals(t.getId())) {
1528        return t;
1529      }
1530    }
1531    return null;
1532  }
1533
1534  protected void updateConstraintSources(ElementDefinition ed, String url) {
1535    for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
1536      if (!c.hasSource()) {
1537        c.setSource(url);
1538      }
1539    }
1540    
1541  }
1542
1543  protected Set<String> getListOfTypes(ElementDefinition e) {
1544    Set<String> result = new HashSet<>();
1545    for (TypeRefComponent t : e.getType()) {
1546      result.add(t.getCode());
1547    }
1548    return result;
1549  }
1550
1551  StructureDefinition getTypeForElement(StructureDefinitionDifferentialComponent differential, int diffCursor, String profileName,
1552      List<ElementDefinition> diffMatches, ElementDefinition outcome, String webUrl, Resource srcSD) {
1553    if (outcome.getType().size() == 0) {
1554      if (outcome.hasContentReference()) { 
1555        throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_CONTENT_REFERENCE_IN_THIS_CONTEXT, outcome.getContentReference(), outcome.getId(), outcome.getPath()));
1556      } else {
1557        throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), profileName));
1558      }
1559    }
1560    if (outcome.getType().size() > 1) {
1561      for (TypeRefComponent t : outcome.getType()) {
1562        if (!t.getWorkingCode().equals("Reference"))
1563          throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName));
1564      }
1565    }
1566    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0), webUrl, srcSD);
1567    if (dt == null)
1568      throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath()));
1569    return dt;
1570  }
1571
1572  protected String sliceNames(List<ElementDefinition> diffMatches) {
1573    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1574    for (ElementDefinition ed : diffMatches) {
1575      if (ed.hasSliceName()) {
1576        b.append(ed.getSliceName());
1577      }
1578    }
1579    return b.toString();
1580  }
1581
1582  protected boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types, String inner) {
1583    StructureDefinition tsd = sd;
1584    while (tsd != null) {
1585      for (TypeRefComponent tr : types) {
1586        if (tsd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && tsd.getType().equals(tr.getCode())) {
1587          return true;
1588        }
1589        if (inner == null && tsd.getUrl().equals(tr.getCode())) {
1590          return true;
1591        }
1592        if (inner != null) {
1593          ElementDefinition ed = null;
1594          for (ElementDefinition t : tsd.getSnapshot().getElement()) {
1595            if (inner.equals(t.getId())) {
1596              ed = t;
1597            }
1598          }
1599          if (ed != null) {
1600            return isMatchingType(ed.getType(), types);
1601          }
1602        }
1603      }
1604      tsd = context.fetchResource(StructureDefinition.class, tsd.getBaseDefinition(), tsd);    
1605    }
1606    return false;
1607  }
1608
1609  private boolean isMatchingType(List<TypeRefComponent> test, List<TypeRefComponent> desired) {
1610    for (TypeRefComponent t : test) {
1611      for (TypeRefComponent d : desired) {
1612        if (t.getCode().equals(d.getCode())) {
1613          return true;          
1614        }
1615      }
1616    }
1617    return false;
1618  }
1619
1620  protected boolean isValidType(TypeRefComponent t, ElementDefinition base) {
1621    for (TypeRefComponent tr : base.getType()) {
1622      if (tr.getCode().equals(t.getCode())) {
1623        return true;
1624      }
1625      if (tr.getWorkingCode().equals(t.getCode())) {
1626        log.error("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath());
1627        return true;
1628      }
1629    }
1630    return false;
1631  }
1632
1633  protected void checkNotGenerating(StructureDefinition sd, String role) {
1634    if (sd.isGeneratingSnapshot()) {
1635      throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), role));
1636    }
1637  }
1638
1639  protected boolean isBaseResource(List<TypeRefComponent> types) {
1640    if (types.isEmpty())
1641      return false;
1642    for (TypeRefComponent type : types) {
1643      String t = type.getWorkingCode();
1644      if ("Resource".equals(t))
1645        return false;
1646    }
1647    return true;
1648    
1649  }
1650
1651  String determineFixedType(List<ElementDefinition> diffMatches, String fixedType, int i) {
1652    if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) {
1653      String n = tail(diffMatches.get(i).getPath()).replace("[x]", "");
1654      String t = diffMatches.get(i).getSliceName().substring(n.length());
1655      if (isDataType(t)) {
1656        fixedType = t;
1657      } else if (isPrimitive(Utilities.uncapitalize(t))) {
1658        fixedType = Utilities.uncapitalize(t);
1659      } else {
1660        throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__10_AND_IMPLICIT_SLICE_NAME_DOES_NOT_CONTAIN_A_VALID_TYPE__AT_, t, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName()));
1661      }                
1662    } else if (diffMatches.get(i).getType().size() == 1) {
1663      fixedType = diffMatches.get(i).getType().get(0).getCode();
1664    } else {
1665      throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__1_AT_, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName()));
1666    }
1667    return fixedType;
1668  }
1669
1670
1671  protected BaseTypeSlice chooseMatchingBaseSlice(List<BaseTypeSlice> baseSlices, String type) {
1672    for (BaseTypeSlice bs : baseSlices) {
1673      if (bs.getType().equals(type)) {
1674        return bs;
1675      }
1676    }
1677    return null;
1678  }
1679
1680
1681  protected List<BaseTypeSlice> findBaseSlices(StructureDefinitionSnapshotComponent list, int start) {
1682    List<BaseTypeSlice> res = new ArrayList<>();
1683    ElementDefinition base = list.getElement().get(start);
1684    int i = start + 1;
1685    while (i <  list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) {
1686      i++;      
1687    };
1688    while (i <  list.getElement().size() && list.getElement().get(i).getPath().equals(base.getPath()) && list.getElement().get(i).hasSliceName()) {
1689      int s = i;
1690      i++;
1691      while (i <  list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) {
1692        i++;      
1693      };
1694      res.add(new BaseTypeSlice(list.getElement().get(s), list.getElement().get(s).getTypeFirstRep().getCode(), s, i-1));
1695    }
1696    return res;
1697  }
1698
1699
1700  protected String getWebUrl(StructureDefinition dt, String webUrl) {
1701    if (dt.hasWebPath()) {
1702      // this is a hack, but it works for now, since we don't have deep folders
1703      String url = dt.getWebPath();
1704      int i = url.lastIndexOf("/");
1705      if (i < 1) {
1706        return defWebRoot;
1707      } else {
1708        return url.substring(0, i+1);
1709      }
1710    } else {  
1711      return webUrl;
1712    }
1713  }
1714
1715  protected String descED(List<ElementDefinition> list, int index) {
1716    return index >=0 && index < list.size() ? list.get(index).present() : "X";
1717  }
1718
1719  
1720 
1721  protected String rootName(String cpath) {
1722    String t = tail(cpath);
1723    return t.replace("[x]", "");
1724  }
1725
1726
1727  protected String determineTypeSlicePath(String path, String cpath) {
1728    String headP = path.substring(0, path.lastIndexOf("."));
1729//    String tailP = path.substring(path.lastIndexOf(".")+1);
1730    String tailC = cpath.substring(cpath.lastIndexOf(".")+1);
1731    return headP+"."+tailC;
1732  }
1733
1734
1735  protected boolean isImplicitSlicing(ElementDefinition ed, String path) {
1736    if (ed == null || ed.getPath() == null || path == null)
1737      return false;
1738    if (path.equals(ed.getPath()))
1739      return false;
1740    boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3));
1741    return ok;
1742  }
1743
1744
1745  protected boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) {
1746//    if (diffMatches.size() < 2)
1747    //      return false;
1748    String p = diffMatches.get(0).getPath();
1749    if (!p.endsWith("[x]") && !cPath.endsWith("[x]"))
1750      return false;
1751    typeList.clear();
1752    String rn = tail(cPath);
1753    rn = rn.substring(0, rn.length()-3);
1754    for (int i = 0; i < diffMatches.size(); i++) {
1755      ElementDefinition ed = diffMatches.get(i);
1756      String n = tail(ed.getPath());
1757      if (!n.startsWith(rn))
1758        return false;
1759      String s = n.substring(rn.length());
1760      if (!s.contains(".")) {
1761        if (ed.hasSliceName() && ed.getType().size() == 1) {
1762          typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode()));
1763        } else if (ed.hasSliceName() && ed.getType().size() == 0) {
1764          if (isDataType(s)) {
1765            typeList.add(new TypeSlice(ed, s));
1766          } else if (isPrimitive(Utilities.uncapitalize(s))) {
1767            typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
1768          } else {
1769            String tn = ed.getSliceName().substring(n.length());
1770            if (isDataType(tn)) {
1771              typeList.add(new TypeSlice(ed, tn));
1772            } else if (isPrimitive(Utilities.uncapitalize(tn))) {
1773              typeList.add(new TypeSlice(ed, Utilities.uncapitalize(tn)));
1774            }
1775          }
1776        } else if (!ed.hasSliceName() && !s.equals("[x]")) {
1777          if (isDataType(s))
1778            typeList.add(new TypeSlice(ed, s));
1779          else if (isConstrainedDataType(s))
1780            typeList.add(new TypeSlice(ed, baseType(s)));
1781          else if (isPrimitive(Utilities.uncapitalize(s)))
1782            typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
1783        } else if (!ed.hasSliceName() && s.equals("[x]"))
1784            typeList.add(new TypeSlice(ed, null));
1785          }
1786        }
1787    return true;
1788  }
1789
1790
1791  protected List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) {
1792    List<ElementRedirection> result = new ArrayList<ElementRedirection>();
1793    result.addAll(redirector);
1794    result.add(new ElementRedirection(outcome, path));
1795    return result;
1796  }
1797
1798
1799  protected List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) {
1800    List<TypeRefComponent> res = new ArrayList<TypeRefComponent>();
1801    for (TypeRefComponent tr : type) {
1802      if (t.equals(tr.getWorkingCode()))
1803          res.add(tr);
1804    }
1805    return res;
1806  }
1807
1808
1809  protected void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
1810    outcome.setContentReference(null);
1811    outcome.getType().clear(); // though it should be clear anyway
1812    outcome.getType().addAll(tgt.getType());    
1813  }
1814
1815
1816  protected boolean baseWalksInto(List<ElementDefinition> elements, int cursor) {
1817    if (cursor >= elements.size())
1818      return false;
1819    String path = elements.get(cursor).getPath();
1820    String prevPath = elements.get(cursor - 1).getPath();
1821    return path.startsWith(prevPath + ".");
1822  }
1823
1824
1825  protected  ElementDefinition fillOutFromBase(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError {
1826    ElementDefinition res = profile.copy();
1827    if (!res.hasSliceName())
1828      res.setSliceName(usage.getSliceName());
1829    if (!res.hasLabel())
1830      res.setLabel(usage.getLabel());
1831    for (Coding c : usage.getCode())
1832      if (!res.hasCode(c))
1833        res.addCode(c);
1834    
1835    if (!res.hasDefinition())
1836      res.setDefinition(usage.getDefinition());
1837    if (!res.hasShort() && usage.hasShort())
1838      res.setShort(usage.getShort());
1839    if (!res.hasComment() && usage.hasComment())
1840      res.setComment(usage.getComment());
1841    if (!res.hasRequirements() && usage.hasRequirements())
1842      res.setRequirements(usage.getRequirements());
1843    for (StringType c : usage.getAlias())
1844      if (!res.hasAlias(c.getValue()))
1845        res.addAlias(c.getValue());
1846    if (!res.hasMin() && usage.hasMin())
1847      res.setMin(usage.getMin());
1848    if (!res.hasMax() && usage.hasMax())
1849      res.setMax(usage.getMax());
1850     
1851    if (!res.hasFixed() && usage.hasFixed())
1852      res.setFixed(usage.getFixed());
1853    if (!res.hasPattern() && usage.hasPattern())
1854      res.setPattern(usage.getPattern());
1855    if (!res.hasExample() && usage.hasExample())
1856      res.setExample(usage.getExample());
1857    if (!res.hasMinValue() && usage.hasMinValue())
1858      res.setMinValue(usage.getMinValue());
1859    if (!res.hasMaxValue() && usage.hasMaxValue())
1860      res.setMaxValue(usage.getMaxValue());     
1861    if (!res.hasMaxLength() && usage.hasMaxLength())
1862      res.setMaxLength(usage.getMaxLength());
1863    if (!res.hasMustSupport() && usage.hasMustSupport())
1864      res.setMustSupport(usage.getMustSupport());
1865    if (!res.hasIsSummary() && usage.hasIsSummary())
1866      res.setIsSummary(usage.getIsSummary());
1867    if (!res.hasIsModifier() && usage.hasIsModifier())
1868      res.setIsModifier(usage.getIsModifier());
1869    if (!res.hasIsModifierReason() && usage.hasIsModifierReason())
1870      res.setIsModifierReason(usage.getIsModifierReason());
1871    if (!res.hasMustHaveValue() && usage.hasMustHaveValue())
1872      res.setMustHaveValue(usage.getMustHaveValue());
1873    if (!res.hasBinding() && usage.hasBinding())
1874      res.setBinding(usage.getBinding().copy());
1875    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
1876      if (!res.hasConstraint(c.getKey()))
1877        res.addConstraint(c);
1878    for (Extension e : usage.getExtension()) {
1879      if (!res.hasExtension(e.getUrl()))
1880        res.addExtension(e.copy());
1881    }
1882    
1883    return res;
1884  }
1885
1886
1887  protected boolean checkExtensionDoco(ElementDefinition base) {
1888    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
1889    boolean isExtension = (base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension")) &&
1890          (!base.hasBase() || !"II.extension".equals(base.getBase().getPath()));
1891    if (isExtension) {
1892      base.setDefinition("An Extension");
1893      base.setShort("Extension");
1894      base.setCommentElement(null);
1895      base.setRequirementsElement(null);
1896      base.getAlias().clear();
1897      base.getMapping().clear();
1898    }
1899    return isExtension;
1900  }
1901
1902
1903  protected String pathTail(List<ElementDefinition> diffMatches, int i) {
1904    
1905    ElementDefinition d = diffMatches.get(i);
1906    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
1907    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : "");
1908  }
1909
1910
1911  protected void markDerived(ElementDefinition outcome) {
1912    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
1913      inv.setUserData(UserDataNames.SNAPSHOT_IS_DERIVED, true);
1914  }
1915
1916
1917  static String summarizeSlicing(ElementDefinitionSlicingComponent slice) {
1918    StringBuilder b = new StringBuilder();
1919    boolean first = true;
1920    for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
1921      if (first)
1922        first = false;
1923      else
1924        b.append(", ");
1925      b.append(d.getType().toCode()+":"+d.getPath());
1926    }
1927    b.append(" (");
1928    if (slice.hasOrdered())
1929      b.append(slice.getOrdered() ? "ordered" : "unordered");
1930    b.append("/");
1931    if (slice.hasRules())
1932      b.append(slice.getRules().toCode());
1933    b.append(")");
1934    if (slice.hasDescription()) {
1935      b.append(" \"");
1936      b.append(slice.getDescription());
1937      b.append("\"");
1938    }
1939    return b.toString();
1940  }
1941
1942
1943  protected void updateFromBase(ElementDefinition derived, ElementDefinition base, String baseProfileUrl) {
1944    derived.setUserData(UserDataNames.SNAPSHOT_BASE_MODEL, baseProfileUrl);
1945    derived.setUserData(UserDataNames.SNAPSHOT_BASE_PATH, base.getPath());
1946    if (base.hasBase()) {
1947      if (!derived.hasBase())
1948        derived.setBase(new ElementDefinitionBaseComponent());
1949      derived.getBase().setPath(base.getBase().getPath());
1950      derived.getBase().setMin(base.getBase().getMin());
1951      derived.getBase().setMax(base.getBase().getMax());
1952    } else {
1953      if (!derived.hasBase())
1954        derived.setBase(new ElementDefinitionBaseComponent());
1955      derived.getBase().setPath(base.getPath());
1956      derived.getBase().setMin(base.getMin());
1957      derived.getBase().setMax(base.getMax());
1958    }
1959  }
1960
1961
1962  protected boolean pathStartsWith(String p1, String p2) {
1963    return p1.startsWith(p2) || (p2.endsWith("[x].") && p1.startsWith(p2.substring(0, p2.length()-4)));
1964  }
1965
1966  private boolean pathMatches(String p1, String p2) {
1967    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
1968  }
1969
1970
1971  protected String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) {
1972    if (contextPath == null)
1973      return pathSimple;
1974//    String ptail = pathSimple.substring(contextPath.length() + 1);
1975    if (redirector != null && redirector.size() > 0) {
1976      String ptail = null;
1977      if (contextPath.length() >= pathSimple.length()) {
1978        ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1979      } else {
1980        ptail = pathSimple.substring(contextPath.length()+1);
1981      }
1982      return redirector.get(redirector.size()-1).getPath()+"."+ptail;
1983//      return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1);
1984    } else {
1985      String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1986      return contextPath+"."+ptail;
1987    }
1988  }
1989  
1990  protected String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) {
1991    String s;
1992    if (contextPath == null)
1993      s = pathSimple;
1994    else {
1995      if (redirector != null && redirector.size() > 0) {
1996        String ptail = null;
1997        if (redirectSource.length() >= pathSimple.length()) {
1998          ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1999        } else {
2000          ptail = pathSimple.substring(redirectSource.length()+1);
2001        }
2002  //      ptail = ptail.substring(ptail.indexOf(".")+1);
2003        s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail;
2004      } else {
2005        String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
2006        s = contextPath+"."+ptail;
2007      }
2008    }
2009    return s;
2010  }  
2011
2012  protected StructureDefinition getProfileForDataType(TypeRefComponent type, String webUrl, Resource src)  {
2013    StructureDefinition sd = null;
2014    if (type.hasProfile()) {
2015      sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue(), src);
2016      if (sd == null) {
2017        if (makeXVer().matchingUrl(type.getProfile().get(0).getValue()) && xver.status(type.getProfile().get(0).getValue()) == XVerExtensionStatus.Valid) {
2018          sd = xver.makeDefinition(type.getProfile().get(0).getValue());              
2019          generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), webUrl, sd.getName());
2020        }
2021      }
2022      if (sd == null) {
2023          log.debug("Failed to find referenced profile: " + type.getProfile());
2024      }
2025        
2026    }
2027    if (sd == null)
2028      sd = context.fetchTypeDefinition(type.getWorkingCode());
2029    if (sd == null)
2030      log.warn("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM
2031    return sd;
2032  }
2033
2034  protected StructureDefinition getProfileForDataType(String type)  {
2035    StructureDefinition sd = context.fetchTypeDefinition(type);
2036    if (sd == null)
2037      log.warn("XX: failed to find profle for type: " + type); // debug GJM
2038    return sd;
2039  }
2040
2041  static String typeCode(List<TypeRefComponent> types) {
2042    StringBuilder b = new StringBuilder();
2043    boolean first = true;
2044    for (TypeRefComponent type : types) {
2045      if (first) first = false; else b.append(", ");
2046      b.append(type.getWorkingCode());
2047      if (type.hasTargetProfile())
2048        b.append("{"+type.getTargetProfile()+"}");
2049      else if (type.hasProfile())
2050        b.append("{"+type.getProfile()+"}");
2051    }
2052    return b.toString();
2053  }
2054
2055
2056  protected boolean isDataType(List<TypeRefComponent> types) {
2057    if (types.isEmpty())
2058      return false;
2059    for (TypeRefComponent type : types) {
2060      String t = type.getWorkingCode();
2061      if (!isDataType(t) && !isPrimitive(t))
2062        return false;
2063    }
2064    return true;
2065  }
2066
2067
2068  /**
2069   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
2070   * @param url - the base url to use to turn internal references into absolute references
2071   * @param element - the Element to update
2072   * @return - the updated Element
2073   */
2074  public ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element, boolean processRelatives) {
2075    if (element != null) {
2076      ElementDefinition defn = element;
2077      if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#"))
2078        defn.getBinding().setValueSet(url+defn.getBinding().getValueSet());
2079      for (TypeRefComponent t : defn.getType()) {
2080        for (UriType u : t.getProfile()) {
2081          if (u.getValue().startsWith("#"))
2082            u.setValue(url+t.getProfile());
2083        }
2084        for (UriType u : t.getTargetProfile()) {
2085          if (u.getValue().startsWith("#"))
2086            u.setValue(url+t.getTargetProfile());
2087        }
2088      }
2089      if (webUrl != null) {
2090        // also, must touch up the markdown
2091        if (element.hasDefinition()) {
2092          element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
2093        }
2094        if (element.hasComment()) {
2095          element.setComment(processRelativeUrls(element.getComment(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
2096        }
2097        if (element.hasRequirements()) {
2098          element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
2099        }
2100        if (element.hasMeaningWhenMissing()) {
2101          element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
2102        }
2103        if (element.hasBinding() && element.getBinding().hasDescription()) {
2104          element.getBinding().setDescription(processRelativeUrls(element.getBinding().getDescription(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
2105        }
2106        for (Extension ext : element.getExtension()) {
2107          if (ext.hasValueMarkdownType()) {
2108            MarkdownType md = ext.getValueMarkdownType();
2109            md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
2110          }
2111        }
2112      }
2113    }
2114    return element;
2115  }
2116
2117  
2118  public static String processRelativeUrls(String markdown, String webUrl, String basePath, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, boolean processRelatives) {
2119    if (markdown == null) {
2120      return "";
2121    }
2122    Set<String> anchorRefs = new HashSet<>();
2123    markdown = markdown+" ";
2124    
2125    StringBuilder b = new StringBuilder();
2126    int i = 0;
2127    int left = -1;
2128    boolean processingLink = false;
2129    int linkLeft = -1;
2130    while (i < markdown.length()) {
2131      if (markdown.charAt(i) == '[') {
2132        if (left == -1) {
2133          left = i;
2134        } else {
2135          left = Integer.MAX_VALUE;
2136        }
2137      }
2138      if (markdown.charAt(i) == ']') {
2139        if (left != -1 && left != Integer.MAX_VALUE && markdown.length() > i && markdown.charAt(i+1) != '(') {
2140          String n = markdown.substring(left+1, i);
2141          if (anchorRefs.contains(n) && markdown.length() > i && markdown.charAt(i+1) == ':') {
2142            processingLink = true;            
2143          } else {
2144            anchorRefs.add(n);
2145          }
2146        }
2147        left = -1;
2148      }
2149      if (processingLink) {
2150        char ch = markdown.charAt(i);
2151        if (linkLeft == -1) {
2152          if (ch != ']' && ch != ':' && !Character.isWhitespace(ch)) {
2153            linkLeft = i;
2154          } else {
2155            b.append(ch);
2156          }
2157        } else {
2158          if (Character.isWhitespace(ch)) {
2159            // found the end of the processible link:
2160            String url = markdown.substring(linkLeft, i);
2161            if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) {
2162              b.append(basePath);
2163              if (!Utilities.noString(basePath) && !basePath.endsWith("/")) {
2164                b.append("/");
2165              }
2166            }
2167            b.append(url);
2168            b.append(ch);
2169            linkLeft = -1;
2170          }
2171        }
2172      } else {
2173        if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) {
2174
2175          int j = i + 2;
2176          while (j < markdown.length() && markdown.charAt(j) != ')')
2177            j++;
2178          if (j < markdown.length()) {
2179            String url = markdown.substring(i+2, j);
2180            if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) {
2181              // 
2182              // In principle, relative URLs are supposed to be converted to absolute URLs in snapshots. 
2183              // that's what this code is doing. 
2184              // 
2185              // But that hasn't always happened and there's packages out there where the snapshots 
2186              // contain relative references that actually are references to the main specification 
2187              // 
2188              // This code is trying to guess which relative references are actually to the
2189              // base specification.
2190              // 
2191              if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) {
2192                b.append("](");
2193                b.append(basePath);
2194                if (!Utilities.noString(basePath) && !basePath.endsWith("/")) {
2195                  b.append("/");
2196                }
2197                i = i + 1;
2198              } else {
2199                b.append("](");
2200                // disabled 7-Dec 2021 GDG - we don't want to fool with relative URLs at all? 
2201                // re-enabled 11-Feb 2022 GDG - we do want to do this. At least, $assemble in davinci-dtr, where the markdown comes from the SDC IG, and an SDC local reference must be changed to point to SDC. in this case, it's called when generating snapshots
2202                // added processRelatives parameter to deal with this (well, to try)
2203                if (processRelatives && webUrl != null && !issLocalFileName(url, localFilenames)) {
2204
2205                  b.append(webUrl);
2206                } else {
2207                  //DO NOTHING
2208                }
2209                i = i + 1;
2210              }
2211            } else
2212              b.append(markdown.charAt(i));
2213          } else 
2214            b.append(markdown.charAt(i));
2215        } else {
2216          b.append(markdown.charAt(i));
2217        }
2218      }
2219      i++;
2220    }
2221    String s = b.toString();
2222    return Utilities.rightTrim(s);
2223  }
2224
2225  private static boolean issLocalFileName(String url, Set<String> localFilenames) {
2226    if (localFilenames != null) {
2227      for (String n : localFilenames) {
2228        if (url.startsWith(n.toLowerCase())) {
2229          return true;
2230        }
2231      }
2232    }
2233    return false; 
2234  }
2235
2236
2237  private static boolean isLikelySourceURLReference(String url, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, String baseUrl) {
2238    if (url == null) {
2239      return false;
2240    }
2241    if (baseUrl != null && !baseUrl.startsWith("http://hl7.org/fhir/R")) {
2242      if (resourceNames != null) {
2243        for (String n : resourceNames) {
2244          if (n != null && url.startsWith(n.toLowerCase()+".html")) {
2245            return true;
2246          }
2247          if (n != null && url.startsWith(n.toLowerCase()+"-definitions.html")) {
2248            return true;
2249          }
2250        }
2251      }
2252      if (localFilenames != null) {
2253        for (String n : localFilenames) {
2254          if (n != null && url.startsWith(n.toLowerCase())) {
2255            return false;
2256          }
2257        }
2258      }
2259      if (baseFilenames != null) {
2260        for (String n : baseFilenames) {
2261          if (n != null && url.startsWith(n.toLowerCase())) {
2262            return true;
2263          }
2264        }
2265      }
2266    }
2267    return 
2268        url.startsWith("extensibility.html") || 
2269        url.startsWith("terminologies.html") || 
2270        url.startsWith("observation.html") || 
2271        url.startsWith("codesystem.html") || 
2272        url.startsWith("fhirpath.html") || 
2273        url.startsWith("datatypes.html") || 
2274        url.startsWith("operations.html") || 
2275        url.startsWith("resource.html") || 
2276        url.startsWith("elementdefinition.html") ||
2277        url.startsWith("element-definitions.html") ||
2278        url.startsWith("snomedct.html") ||
2279        url.startsWith("loinc.html") ||
2280        url.startsWith("http.html") ||
2281        url.startsWith("references") ||
2282        url.startsWith("license.html") ||
2283        url.startsWith("narrative.html") || 
2284        url.startsWith("search.html") ||
2285        url.startsWith("security.html") ||
2286        url.startsWith("versions.html") ||
2287        url.startsWith("patient-operation-match.html") ||
2288        (url.startsWith("extension-") && url.contains(".html")) || 
2289        url.startsWith("resource-definitions.html");
2290  }
2291
2292  protected List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
2293    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2294    String path = current.getPath();
2295    int cursor = list.indexOf(current)+1;
2296    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
2297      if (pathMatches(list.get(cursor).getPath(), path))
2298        result.add(list.get(cursor));
2299      cursor++;
2300    }
2301    return result;
2302  }
2303
2304  protected void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
2305    if (src.hasOrderedElement())
2306      dst.setOrderedElement(src.getOrderedElement().copy());
2307    if (src.hasDiscriminator()) {
2308      //    dst.getDiscriminator().addAll(src.getDiscriminator());  Can't use addAll because it uses object equality, not string equality
2309      for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
2310        boolean found = false;
2311        for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
2312          if (matches(d, s)) {
2313            found = true;
2314            break;
2315          }
2316        }
2317        if (!found)
2318          dst.getDiscriminator().add(s);
2319      }
2320    }
2321    if (src.hasRulesElement())
2322      dst.setRulesElement(src.getRulesElement().copy());
2323  }
2324
2325  protected boolean orderMatches(BooleanType diff, BooleanType base) {
2326    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
2327  }
2328
2329  protected boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) {
2330    if (diff.isEmpty() || base.isEmpty())
2331        return true;
2332    if (diff.size() < base.size())
2333        return false;
2334    for (int i = 0; i < base.size(); i++)
2335        if (!matches(diff.get(i), base.get(i)))
2336                return false;
2337    return true;
2338  }
2339
2340  private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) {
2341    return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
2342  }
2343
2344
2345  protected boolean ruleMatches(SlicingRules diff, SlicingRules base) {
2346    return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) ||
2347        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
2348  }
2349
2350  protected boolean isSlicedToOneOnly(ElementDefinition e) {
2351    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
2352  }
2353
2354  protected boolean isTypeSlicing(ElementDefinition e) {
2355    return (e.hasSlicing() && e.getSlicing().getDiscriminator().size() == 1 && 
2356        e.getSlicing().getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE &&
2357        "$this".equals(e.getSlicing().getDiscriminatorFirstRep().getPath()));
2358  }
2359
2360  
2361  protected ElementDefinitionSlicingComponent makeExtensionSlicing() {
2362        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
2363    slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
2364    slice.setOrdered(false);
2365    slice.setRules(SlicingRules.OPEN);
2366    return slice;
2367  }
2368
2369  protected boolean isExtension(ElementDefinition currentBase) {
2370    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
2371  }
2372
2373  boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException {
2374    end = Math.min(context.getElement().size(), end);
2375    start = Math.max(0,  start);
2376  
2377    for (int i = start; i <= end; i++) {
2378      ElementDefinition ed = context.getElement().get(i);
2379      String statedPath = ed.getPath();
2380      if (!allowSlices && statedPath.equals(path) && ed.hasSliceName()) {
2381        return false;
2382      } else if (statedPath.startsWith(path+".")) {
2383        return true;
2384      } else if (path.endsWith("[x]") && statedPath.startsWith(path.substring(0, path.length() -3))) {
2385        return true;
2386      } else if (i != start && !allowSlices && !statedPath.startsWith(path+".")) {
2387        return false;
2388      } else if (i != start && allowSlices && !statedPath.startsWith(path)) {
2389        return false;
2390      } else {
2391        // not sure why we get here, but returning false at this point makes a bunch of tests fail
2392      }
2393    }
2394    return false;
2395  }
2396
2397  protected List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException {
2398    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2399    String[] p = path.split("\\.");
2400    for (int i = start; i <= end; i++) {
2401      String statedPath = context.getElement().get(i).getPath();
2402      String[] sp = statedPath.split("\\.");
2403      boolean ok = sp.length == p.length;
2404      for (int j = 0; j < p.length; j++) {
2405        ok = ok && sp.length > j && (p[j].equals(sp[j]) || isSameBase(p[j], sp[j]));
2406      }
2407// don't need this debug check - everything is ok
2408//      if (ok != (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 &&
2409//            statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) &&
2410//            (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains("."))))) {
2411//
2412//      }
2413      if (ok) {
2414        /*
2415         * Commenting this out because it raises warnings when profiling inherited elements.  For example,
2416         * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry')
2417         * Not sure we have enough information here to do the check properly.  Might be better done when we're sorting the profile?
2418
2419        if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath()))
2420          addMessage(new ValidationMessage(Source.ProfileValidator, IssueType.VALUE, "StructureDefinition.differential.element["+Integer.toString(start)+"]", "Error: unknown element '"+context.getElement().get(start).getPath()+"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", IssueSeverity.WARNING));
2421
2422         */
2423        result.add(context.getElement().get(i));
2424      }
2425    }
2426    if (debug) {
2427      Set<String> ids = new HashSet<>();
2428      for (ElementDefinition ed : result) {
2429        ids.add(ed.getIdOrPath());
2430      }
2431    }
2432    return result;
2433  }
2434
2435
2436  private boolean isSameBase(String p, String sp) {
2437    return (p.endsWith("[x]") && sp.startsWith(p.substring(0, p.length()-3))) || (sp.endsWith("[x]") && p.startsWith(sp.substring(0, sp.length()-3))) ;
2438  }
2439
2440  protected int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
2441            int result = cursor;
2442            if (cursor >= context.getElement().size())
2443              return result;
2444            String path = context.getElement().get(cursor).getPath()+".";
2445            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
2446              result++;
2447            return result;
2448          }
2449
2450  protected int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
2451      int result = cursor;
2452      String path = context.getElement().get(cursor).getPath()+".";
2453      while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
2454        result++;
2455      return result;
2456  }
2457  
2458  protected int findEndOfElementNoSlices(StructureDefinitionSnapshotComponent context, int cursor) {
2459    int result = cursor;
2460    String path = context.getElement().get(cursor).getPath()+".";
2461    while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path) && !context.getElement().get(result+1).hasSliceName())
2462      result++;
2463    return result;
2464  }
2465
2466  protected boolean unbounded(ElementDefinition definition) {
2467    StringType max = definition.getMaxElement();
2468    if (max == null)
2469      return false; // this is not valid
2470    if (max.getValue().equals("1"))
2471      return false;
2472    if (max.getValue().equals("0"))
2473      return false;
2474    return true;
2475  }
2476
2477
2478  public void updateFromObligationProfiles(ElementDefinition base) {
2479    List<ElementDefinition> obligationProfileElements = new ArrayList<>();
2480    for (StructureDefinition sd : obligationProfiles) {
2481      ElementDefinition ed = sd.getSnapshot().getElementById(base.getId());
2482      if (ed != null) {
2483        obligationProfileElements.add(ed);
2484      }
2485    }
2486    for (ElementDefinition ed : obligationProfileElements) {
2487      for (Extension ext : ed.getExtension()) {
2488        if (Utilities.existsInList(ext.getUrl(), ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
2489          base.getExtension().add(ext.copy());
2490        }      
2491      }
2492    }
2493    boolean hasMustSupport = false;
2494    for (ElementDefinition ed : obligationProfileElements) {
2495      hasMustSupport = hasMustSupport || ed.hasMustSupportElement();
2496    }
2497    if (hasMustSupport) {
2498      for (ElementDefinition ed : obligationProfileElements) {
2499        mergeExtensions(base.getMustSupportElement(), ed.getMustSupportElement());
2500        if (ed.getMustSupport()) {
2501          base.setMustSupport(true);
2502        }
2503      }
2504    }
2505    boolean hasBinding = false;
2506    for (ElementDefinition ed : obligationProfileElements) {
2507      hasBinding = hasBinding || ed.hasBinding();
2508    }
2509    if (hasBinding) {
2510      ElementDefinitionBindingComponent binding = base.getBinding();
2511      for (ElementDefinition ed : obligationProfileElements) {
2512        for (Extension ext : ed.getBinding().getExtension()) {
2513          if (ToolingExtensions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) {
2514            String p = ext.getExtensionString("purpose");
2515            if (!Utilities.existsInList(p, "maximum", "required", "extensible")) {
2516              if (!binding.hasExtension(ext)) {
2517                binding.getExtension().add(ext.copy());
2518              }
2519            }
2520          }
2521        }
2522        for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
2523          if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) {
2524            if (!binding.hasAdditional(ab)) {
2525              binding.getAdditional().add(ab.copy());
2526            }
2527          }
2528        }
2529      }
2530    }
2531  }
2532
2533  
2534  protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc, String path, MappingAssistant mappings, boolean fromSlicer) throws DefinitionException, FHIRException {
2535    source.setUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT, dest);
2536    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
2537    // over the top for anything the source has
2538    ElementDefinition base = dest;
2539    ElementDefinition derived = source;
2540    derived.setUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER, base);
2541    boolean isExtension = checkExtensionDoco(base);
2542    List<ElementDefinition> obligationProfileElements = new ArrayList<>();
2543    for (StructureDefinition sd : obligationProfiles) {
2544      ElementDefinition ed = sd.getSnapshot().getElementById(base.getId());
2545      if (ed != null) {
2546        obligationProfileElements.add(ed);
2547      }
2548    }
2549
2550    // hack workaround for problem in R5 snapshots
2551    List<Extension> elist = dest.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATABLE);
2552    if (elist.size() == 2) {
2553      dest.getExtension().remove(elist.get(1));
2554    }
2555    updateExtensionsFromDefinition(dest, source, derivedSrc, srcSD);
2556
2557    for (ElementDefinition ed : obligationProfileElements) {
2558      for (Extension ext : ed.getExtension()) {
2559        if (Utilities.existsInList(ext.getUrl(), ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
2560          dest.getExtension().add(new Extension(ToolingExtensions.EXT_OBLIGATION_CORE, ext.getValue().copy()));
2561        }      
2562      }
2563    }
2564    
2565    // Before applying changes, apply them to what's in the profile
2566    // but only if it's an extension or a resource
2567
2568    StructureDefinition profile = null;
2569    boolean msg = true;
2570    if (base.hasSliceName()) {
2571      profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null;
2572    }
2573    if (profile == null && source.getTypeFirstRep().hasProfile()) {
2574      String pu = source.getTypeFirstRep().getProfile().get(0).getValue();
2575      profile = context.fetchResource(StructureDefinition.class, pu, derivedSrc);
2576      if (profile == null) {
2577        if (makeXVer().matchingUrl(pu)) {
2578          switch (xver.status(pu)) {
2579            case BadVersion:
2580              throw new FHIRException("Reference to invalid version in extension url " + pu);
2581            case Invalid:
2582              throw new FHIRException("Reference to invalid extension " + pu);
2583            case Unknown:
2584              throw new FHIRException("Reference to unknown extension " + pu);
2585            case Valid:
2586              profile = xver.makeDefinition(pu);
2587              generateSnapshot(context.fetchTypeDefinition("Extension"), profile, profile.getUrl(), context.getSpecUrl(), profile.getName());
2588          }
2589        }
2590        
2591      }
2592      if (profile != null && !"Extension".equals(profile.getType()) && profile.getKind() != StructureDefinitionKind.RESOURCE && profile.getKind() != StructureDefinitionKind.LOGICAL) {
2593        // this is a problem - we're kind of hacking things here. The problem is that we sometimes want the details from the profile to override the 
2594        // inherited attributes, and sometimes not
2595        profile = null;
2596        msg = false;
2597      }
2598    }
2599    if (profile != null && (profile.getKind() == StructureDefinitionKind.RESOURCE || "Extension".equals(profile.getType()))) {
2600      if (profile.getSnapshot().getElement().isEmpty()) {
2601        throw new DefinitionException(context.formatMessage(I18nConstants.SNAPSHOT_IS_EMPTY, profile.getVersionedUrl()));
2602      }
2603      ElementDefinition e = profile.getSnapshot().getElement().get(0);
2604      String webroot = profile.getUserString(UserDataNames.render_webroot);
2605
2606      if (e.hasDefinition()) {
2607        base.setDefinition(processRelativeUrls(e.getDefinition(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true));
2608      }
2609      if (e.getBinding().hasDescription()) {
2610        base.getBinding().setDescription(processRelativeUrls(e.getBinding().getDescription(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true));
2611      }
2612      base.setShort(e.getShort());
2613      if (e.hasCommentElement())
2614        base.setCommentElement(e.getCommentElement());
2615      if (e.hasRequirementsElement())
2616        base.setRequirementsElement(e.getRequirementsElement());
2617      base.getAlias().clear();
2618      base.getAlias().addAll(e.getAlias());
2619      base.getMapping().clear();
2620      base.getMapping().addAll(e.getMapping());
2621    } else if (source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() && !source.getTypeFirstRep().getProfile().get(0).hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) {
2622      // todo: should we change down the profile_element if there's one?
2623      String type = source.getTypeFirstRep().getWorkingCode();
2624      if (msg) {
2625        if ("Extension".equals(type)) {
2626          log.warn("Can't find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on");
2627          if (allowUnknownProfile != AllowUnknownProfile.ALL_TYPES) {
2628            throw new DefinitionException("Unable to find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue());          
2629          }
2630        } else {
2631          log.warn("Can't find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on");
2632          if (allowUnknownProfile == AllowUnknownProfile.NONE) {
2633            throw new DefinitionException("Unable to find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue());          
2634          }
2635        }
2636      }
2637    }
2638    if (derived != null) {
2639      if (derived.hasSliceName()) {
2640        base.setSliceName(derived.getSliceName());
2641      }
2642
2643      if (derived.hasShortElement()) {
2644        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
2645          base.setShortElement(derived.getShortElement().copy());
2646        else if (trimDifferential)
2647          derived.setShortElement(null);
2648        else if (derived.hasShortElement())
2649          derived.getShortElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2650      }
2651
2652      if (derived.hasDefinitionElement()) {
2653        if (derived.getDefinition() != null && derived.getDefinition().startsWith("..."))
2654          base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition()));
2655        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) {
2656          base.setDefinitionElement(derived.getDefinitionElement().copy());
2657        } else if (trimDifferential)
2658          derived.setDefinitionElement(null);
2659        else if (derived.hasDefinitionElement())
2660          derived.getDefinitionElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2661      }
2662
2663      if (derived.hasCommentElement()) {
2664        if (derived.getComment().startsWith("..."))
2665          base.setComment(Utilities.appendDerivedTextToBase(base.getComment(), derived.getComment()));
2666        else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
2667          base.setCommentElement(derived.getCommentElement().copy());
2668        else if (trimDifferential)
2669          base.setCommentElement(derived.getCommentElement().copy());
2670        else if (derived.hasCommentElement())
2671          derived.getCommentElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2672      }
2673
2674      if (derived.hasLabelElement()) {
2675        if (derived.getLabel().startsWith("..."))
2676          base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel()));
2677        else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
2678          base.setLabelElement(derived.getLabelElement().copy());
2679        else if (trimDifferential)
2680          base.setLabelElement(derived.getLabelElement().copy());
2681        else if (derived.hasLabelElement())
2682          derived.getLabelElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2683      }
2684
2685      if (derived.hasRequirementsElement()) {
2686        if (derived.getRequirements().startsWith("..."))
2687          base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements()));
2688        else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
2689          base.setRequirementsElement(derived.getRequirementsElement().copy());
2690        else if (trimDifferential)
2691          base.setRequirementsElement(derived.getRequirementsElement().copy());
2692        else if (derived.hasRequirementsElement())
2693          derived.getRequirementsElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2694      }
2695      // sdf-9
2696      if (derived.hasRequirements() && !base.getPath().contains("."))
2697        derived.setRequirements(null);
2698      if (base.hasRequirements() && !base.getPath().contains("."))
2699        base.setRequirements(null);
2700
2701      if (derived.hasAlias()) {
2702        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
2703          for (StringType s : derived.getAlias()) {
2704            if (!base.hasAlias(s.getValue()))
2705              base.getAlias().add(s.copy());
2706          }
2707        else if (trimDifferential)
2708          derived.getAlias().clear();
2709        else
2710          for (StringType t : derived.getAlias())
2711            t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2712      }
2713
2714      if (derived.hasMinElement()) {
2715        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
2716          if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply
2717            addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than the base min ("+Integer.toString(base.getMin())+") in "+srcSD.getVersionedUrl(), ValidationMessage.IssueSeverity.ERROR));
2718          base.setMinElement(derived.getMinElement().copy());
2719        } else if (trimDifferential)
2720          derived.setMinElement(null);
2721        else
2722          derived.getMinElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2723      }
2724
2725      if (derived.hasMaxElement()) {
2726        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
2727          if (isLargerMax(derived.getMax(), base.getMax()))
2728            addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than the base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR));
2729          base.setMaxElement(derived.getMaxElement().copy());
2730        } else if (trimDifferential)
2731          derived.setMaxElement(null);
2732        else
2733          derived.getMaxElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2734      }
2735
2736      if (derived.hasFixed()) {
2737        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
2738          base.setFixed(derived.getFixed().copy());
2739        } else if (trimDifferential)
2740          derived.setFixed(null);
2741        else
2742          derived.getFixed().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2743      }
2744
2745      if (derived.hasPattern()) {
2746        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
2747          base.setPattern(derived.getPattern().copy());
2748        } else
2749          if (trimDifferential)
2750            derived.setPattern(null);
2751          else
2752            derived.getPattern().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2753      }
2754
2755      List<ElementDefinitionExampleComponent> toDelB = new ArrayList<>();
2756      List<ElementDefinitionExampleComponent> toDelD = new ArrayList<>();
2757      for (ElementDefinitionExampleComponent ex : derived.getExample()) {
2758        boolean delete = ex.hasExtension(ToolingExtensions.EXT_ED_SUPPRESS);
2759        if (delete && "$all".equals(ex.getLabel())) {
2760          toDelB.addAll(base.getExample());
2761        } else {
2762          boolean found = false;
2763          for (ElementDefinitionExampleComponent exS : base.getExample()) {
2764            if (Base.compareDeep(ex.getLabel(), exS.getLabel(), false) && Base.compareDeep(ex.getValue(), exS.getValue(), false)) {
2765              if (delete) {
2766                toDelB.add(exS);
2767              } else {
2768                found = true;
2769              }
2770            }
2771          }
2772          if (delete) {
2773            toDelD.add(ex);
2774          } else if (!found) {
2775            base.addExample(ex.copy());
2776          } else if (trimDifferential) {
2777            derived.getExample().remove(ex);
2778          } else {
2779            ex.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2780          }
2781        }
2782      }
2783      base.getExample().removeAll(toDelB);
2784      derived.getExample().removeAll(toDelD);
2785
2786      if (derived.hasMaxLengthElement()) {
2787        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
2788          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
2789        else if (trimDifferential)
2790          derived.setMaxLengthElement(null);
2791        else
2792          derived.getMaxLengthElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2793      }
2794  
2795      if (derived.hasMaxValue()) {
2796        if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false))
2797          base.setMaxValue(derived.getMaxValue().copy());
2798        else if (trimDifferential)
2799          derived.setMaxValue(null);
2800        else
2801          derived.getMaxValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2802      }
2803  
2804      if (derived.hasMinValue()) {
2805        if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false))
2806          base.setMinValue(derived.getMinValue().copy());
2807        else if (trimDifferential)
2808          derived.setMinValue(null);
2809        else
2810          derived.getMinValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2811      }
2812
2813      // todo: what to do about conditions?
2814      // condition : id 0..*
2815
2816      boolean hasMustSupport = derived.hasMustSupportElement();
2817      for (ElementDefinition ed : obligationProfileElements) {
2818        hasMustSupport = hasMustSupport || ed.hasMustSupportElement();
2819      }
2820      if (hasMustSupport) {
2821        BooleanType mse = derived.getMustSupportElement().copy();
2822        for (ElementDefinition ed : obligationProfileElements) {
2823          mergeExtensions(mse, ed.getMustSupportElement());
2824          if (ed.getMustSupport()) {
2825            mse.setValue(true);
2826          }
2827        }
2828        if (!(base.hasMustSupportElement() && Base.compareDeep(base.getMustSupportElement(), mse, false))) {
2829          if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport() && !fromSlicer) {
2830            addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-support = false] when [must-support = true] in the base profile", ValidationMessage.IssueSeverity.ERROR));
2831          }
2832          base.setMustSupportElement(mse);
2833        } else if (trimDifferential)
2834          derived.setMustSupportElement(null);
2835        else
2836          derived.getMustSupportElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2837      }
2838      
2839      if (derived.hasMustHaveValueElement()) {
2840        if (!(base.hasMustHaveValueElement() && Base.compareDeep(derived.getMustHaveValueElement(), base.getMustHaveValueElement(), false))) {
2841          if (base.hasMustHaveValue() && base.getMustHaveValue() && !derived.getMustHaveValue() && !fromSlicer) {
2842            addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-have-value = false] when [must-have-value = true] in the base profile", ValidationMessage.IssueSeverity.ERROR));
2843          }
2844          base.setMustHaveValueElement(derived.getMustHaveValueElement().copy());
2845        } else if (trimDifferential)
2846          derived.setMustHaveValueElement(null);
2847        else
2848          derived.getMustHaveValueElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2849      }
2850      if (derived.hasValueAlternatives()) {
2851        if (!Base.compareDeep(derived.getValueAlternatives(), base.getValueAlternatives(), false))
2852          for (CanonicalType s : derived.getValueAlternatives()) {
2853            if (!base.hasValueAlternatives(s.getValue()))
2854              base.getValueAlternatives().add(s.copy());
2855          }
2856        else if (trimDifferential)
2857          derived.getValueAlternatives().clear();
2858        else
2859          for (CanonicalType t : derived.getValueAlternatives())
2860            t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2861      }
2862
2863      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
2864      // but extensions can change isModifier
2865      if (isExtension) {
2866        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) {
2867          base.setIsModifierElement(derived.getIsModifierElement().copy());
2868        } else if (trimDifferential) {
2869          derived.setIsModifierElement(null);
2870        } else if (derived.hasIsModifierElement()) {
2871          derived.getIsModifierElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2872        }
2873        if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) {
2874          base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy());
2875        } else if (trimDifferential) {
2876          derived.setIsModifierReasonElement(null);
2877        } else if (derived.hasIsModifierReasonElement()) {
2878          derived.getIsModifierReasonElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2879        }
2880        if (base.getIsModifier() && !base.hasIsModifierReason()) {
2881          // we get here because modifier extensions don't get a modifier reason from the type
2882          base.setIsModifierReason("Modifier extensions are labelled as such because they modify the meaning or interpretation of the resource or element that contains them");
2883        }
2884      }
2885
2886      boolean hasBinding = derived.hasBinding();
2887      for (ElementDefinition ed : obligationProfileElements) {
2888        hasBinding = hasBinding || ed.hasBinding();
2889      }
2890      if (hasBinding) {
2891        updateExtensionsFromDefinition(dest.getBinding(), source.getBinding(), derivedSrc, srcSD);
2892        ElementDefinitionBindingComponent binding = derived.getBinding();
2893        for (ElementDefinition ed : obligationProfileElements) {
2894          for (Extension ext : ed.getBinding().getExtension()) {
2895            if (ToolingExtensions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) {
2896              String p = ext.getExtensionString("purpose");
2897              if (!Utilities.existsInList(p, "maximum", "required", "extensible")) {
2898                if (!binding.hasExtension(ext)) {
2899                  binding.getExtension().add(ext.copy());
2900                }
2901              }
2902            }
2903          }
2904          for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
2905            if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) {
2906              if (binding.hasAdditional(ab)) {
2907                binding.getAdditional().add(ab.copy());
2908              }
2909            }
2910          }
2911        }
2912        
2913        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
2914          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
2915            addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR));
2916//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
2917          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) {
2918            ValueSet baseVs = context.findTxResource(ValueSet.class, base.getBinding().getValueSet(), srcSD);
2919            ValueSet contextVs = context.findTxResource(ValueSet.class, derived.getBinding().getValueSet(), derivedSrc);
2920            if (baseVs == null) {
2921              addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
2922            } else if (contextVs == null) {
2923              addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
2924            } else {
2925              ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false);
2926              ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false);
2927              if (expBase.getValueset() == null)
2928                addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
2929              else if (expDerived.getValueset() == null)
2930                addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
2931              else if (ToolingExtensions.hasExtension(expBase.getValueset().getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY)) {
2932                if (ToolingExtensions.hasExtension(expDerived.getValueset().getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY) || expDerived.getValueset().getExpansion().getContains().size() > 100) {
2933                  addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - base value set is too large to check", ValidationMessage.IssueSeverity.WARNING));
2934                } else {
2935                  boolean ok = true;
2936                  for (ValueSetExpansionContainsComponent cc : expDerived.getValueset().getExpansion().getContains()) {
2937                    ValidationResult vr = context.validateCode(new ValidationOptions(), cc.getSystem(), cc.getVersion(), cc.getCode(), null, baseVs);
2938                    if (!vr.isOk()) {
2939                      ok = false;
2940                      break;                      
2941                    }
2942                  }
2943                  if (!ok) {
2944                    addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR));
2945                  }
2946                }
2947              } else if (expBase.getValueset().getExpansion().getContains().size() == 1000 || 
2948                  expDerived.getValueset().getExpansion().getContains().size() == 1000) {
2949                addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - value set is too large to check", ValidationMessage.IssueSeverity.WARNING));
2950              } else {
2951                 String msgs = checkSubset(expBase.getValueset(), expDerived.getValueset());
2952                 if (msgs != null) {
2953                  addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet()+" because "+msgs, ValidationMessage.IssueSeverity.ERROR));
2954                }
2955              }
2956            }
2957          }
2958          ElementDefinitionBindingComponent d = derived.getBinding();
2959          ElementDefinitionBindingComponent nb = base.getBinding().copy();
2960          if (!COPY_BINDING_EXTENSIONS) {
2961            nb.getExtension().clear();
2962          }
2963          nb.setDescription(null);
2964          for (Extension dex : d.getExtension()) {
2965            nb.getExtension().add(markExtensionSource(dex.copy(), false, srcSD));
2966          }
2967          if (d.hasStrength()) {
2968            nb.setStrength(d.getStrength());
2969          }
2970          if (d.hasDescription()) {
2971            nb.setDescription(d.getDescription());
2972          }
2973          if (d.hasValueSet()) {
2974            nb.setValueSet(d.getValueSet());
2975          }
2976          for (ElementDefinitionBindingAdditionalComponent ab : d.getAdditional()) {
2977            ElementDefinitionBindingAdditionalComponent eab = getMatchingAdditionalBinding(nb, ab);
2978            if (eab != null) {
2979              mergeAdditionalBinding(eab, ab);
2980            } else {
2981              nb.getAdditional().add(ab);
2982            }
2983          }
2984          base.setBinding(nb); 
2985        } else if (trimDifferential)
2986          derived.setBinding(null);
2987        else
2988          derived.getBinding().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
2989      } else if (base.hasBinding()) {
2990         base.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));
2991         for (Extension ex : base.getBinding().getExtension()) {
2992           markExtensionSource(ex, false, srcSD);
2993         }
2994      }
2995
2996      if (derived.hasIsSummaryElement()) {
2997        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
2998          if (base.hasIsSummary() && !context.getVersion().equals("1.4.0")) // work around a known issue with some 1.4.0 cosntraints
2999            throw new Error(context.formatMessage(I18nConstants.ERROR_IN_PROFILE__AT__BASE_ISSUMMARY___DERIVED_ISSUMMARY__, purl, derived.getPath(), base.getIsSummaryElement().asStringValue(), derived.getIsSummaryElement().asStringValue()));
3000          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
3001        } else if (trimDifferential)
3002          derived.setIsSummaryElement(null);
3003        else
3004          derived.getIsSummaryElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
3005      }
3006
3007      // this would make sense but blows up the process later, so we let it happen anyway, and sort out the business rule elsewhere
3008      //if (!derived.hasContentReference() && !base.hasContentReference()) {
3009
3010      if (derived.hasType()) {
3011        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
3012          if (base.hasType()) {
3013            for (TypeRefComponent ts : derived.getType()) {
3014              checkTypeDerivation(purl, derivedSrc, base, derived, ts, path);
3015            }
3016          }
3017          base.getType().clear();
3018          for (TypeRefComponent t : derived.getType()) {
3019            TypeRefComponent tt = t.copy();
3020            //            tt.setUserData(DERIVATION_EQUALS, true);
3021            base.getType().add(tt);
3022            for (Extension ex : tt.getExtension()) {
3023              markExtensionSource(ex, false, srcSD);
3024            }
3025          }
3026        }
3027        else if (trimDifferential)
3028          derived.getType().clear();
3029        else {
3030          for (TypeRefComponent t : derived.getType()) {
3031            t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
3032            for (Extension ex : t.getExtension()) {
3033              markExtensionSource(ex, true, derivedSrc);
3034            }
3035          }
3036        }
3037      }
3038      
3039      mappings.merge(derived, base); // note reversal of names to be correct in .merge()
3040
3041      // todo: constraints are cumulative. there is no replacing
3042      for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 
3043        s.setUserData(UserDataNames.SNAPSHOT_IS_DERIVED, true);
3044        if (!s.hasSource()) {
3045          s.setSource(srcSD.getUrl());
3046        } 
3047      }
3048      if (derived.hasConstraint()) {
3049        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
3050          if (!base.hasConstraint(s.getKey())) {
3051            ElementDefinitionConstraintComponent inv = s.copy();
3052            base.getConstraint().add(inv);
3053          }
3054        }
3055      }
3056      for (IdType id : derived.getCondition()) {
3057        if (!base.hasCondition(id)) {
3058          base.getCondition().add(id);
3059        }
3060      }
3061      
3062      // now, check that we still have a bindable type; if not, delete the binding - see task 8477
3063      if (dest.hasBinding() && !hasBindableType(dest)) {
3064        dest.setBinding(null);
3065      }
3066        
3067//      // finally, we copy any extensions from source to dest
3068      //no, we already did.
3069//      for (Extension ex : derived.getExtension()) {
3070//        !
3071//        StructureDefinition sd  = context.fetchResource(StructureDefinition.class, ex.getUrl(), derivedSrc);
3072//        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) {
3073//          ToolingExtensions.removeExtension(dest, ex.getUrl());
3074//        }
3075//        dest.addExtension(ex.copy());
3076//      }
3077    }
3078    if (dest.hasFixed()) {
3079      checkTypeOk(dest, dest.getFixed().fhirType(), srcSD, "fixed");
3080    }
3081    if (dest.hasPattern()) {
3082      checkTypeOk(dest, dest.getPattern().fhirType(), srcSD, "pattern");
3083    }
3084    //updateURLs(url, webUrl, dest);
3085  }
3086
3087  private static Extension markExtensionSource(Extension extension, boolean overrideSource, StructureDefinition srcSD) {
3088    if (overrideSource || !extension.hasUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE)) {
3089      extension.setUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE, srcSD);
3090    }
3091    if (Utilities.existsInList(extension.getUrl(), ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
3092      Extension sub = extension.getExtensionByUrl(ToolingExtensions.EXT_OBLIGATION_SOURCE, ToolingExtensions.EXT_OBLIGATION_SOURCE_SHORT);
3093      if (sub == null || overrideSource) {
3094        ToolingExtensions.setUriExtension(extension, ToolingExtensions.EXT_OBLIGATION_SOURCE, srcSD.getVersionedUrl());
3095      }
3096    }
3097    return extension;
3098  }
3099
3100  private void updateExtensionsFromDefinition(Element dest, Element source, StructureDefinition destSD, StructureDefinition srcSD) {
3101    dest.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) || (Utilities.existsInList(ext.getUrl(), DEFAULT_INHERITED_ED_URLS) && source.hasExtension(ext.getUrl())));
3102
3103    for (Extension ext : source.getExtension()) {
3104      if (!dest.hasExtension(ext.getUrl())) {
3105        dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD));
3106      } else if (Utilities.existsInList(ext.getUrl(), NON_OVERRIDING_ED_URLS)) {
3107        // do nothing
3108        for (Extension ex2 : dest.getExtensionsByUrl(ext.getUrl())) {
3109          markExtensionSource(ex2, true, destSD);
3110        }
3111      } else if (Utilities.existsInList(ext.getUrl(), OVERRIDING_ED_URLS)) {
3112        dest.getExtensionByUrl(ext.getUrl()).setValue(ext.getValue());
3113        markExtensionSource(dest.getExtensionByUrl(ext.getUrl()), false, srcSD);
3114      } else {
3115        dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD));  
3116      }
3117    }
3118  }
3119
3120  private void mergeAdditionalBinding(ElementDefinitionBindingAdditionalComponent dest, ElementDefinitionBindingAdditionalComponent source) {
3121    for (UsageContext t : source.getUsage()) {
3122      if (!hasUsage(dest, t)) {
3123        dest.addUsage(t);
3124      }
3125    }
3126    if (source.getAny()) {
3127      source.setAny(true);
3128    }
3129    if (source.hasShortDoco()) {
3130      dest.setShortDoco(source.getShortDoco());
3131    }
3132    if (source.hasDocumentation()) {
3133      dest.setDocumentation(source.getDocumentation());
3134    }
3135    
3136  }
3137
3138  private boolean hasUsage(ElementDefinitionBindingAdditionalComponent dest, UsageContext tgt) {
3139    for (UsageContext t : dest.getUsage()) {
3140      if (t.getCode() != null && t.getCode().matches(tgt.getCode()) && t.getValue() != null && t.getValue().equals(tgt.getValue())) {
3141        return true;
3142      }
3143    }
3144    return false;
3145  }
3146
3147  private ElementDefinitionBindingAdditionalComponent getMatchingAdditionalBinding(ElementDefinitionBindingComponent nb,ElementDefinitionBindingAdditionalComponent ab) {
3148    for (ElementDefinitionBindingAdditionalComponent t : nb.getAdditional()) {
3149      if (t.getValueSet() != null && t.getValueSet().equals(ab.getValueSet()) && t.getPurpose() == ab.getPurpose() && !ab.hasUsage()) {
3150        return t;
3151      }
3152    }
3153    return null;
3154  }
3155
3156  private void mergeExtensions(Element tgt, Element src) {
3157     tgt.getExtension().addAll(src.getExtension());
3158  }
3159
3160  private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts, String path) {
3161    boolean ok = false;
3162    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3163    String t = ts.getWorkingCode();
3164    String tDesc = ts.toString();
3165    for (TypeRefComponent td : base.getType()) {;
3166      boolean matchType = false;
3167      String tt = td.getWorkingCode();
3168      b.append(td.toString());
3169      if (td.hasCode() && (tt.equals(t))) {
3170        matchType = true;
3171      }
3172      if (!matchType) {
3173        StructureDefinition sdt = context.fetchTypeDefinition(tt);
3174        if (sdt != null && (sdt.getAbstract() || sdt.getKind() == StructureDefinitionKind.LOGICAL)) {
3175          StructureDefinition sdb = context.fetchTypeDefinition(t);
3176          while (sdb != null && !matchType) {
3177            matchType = sdb.getType().equals(sdt.getType());
3178            sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition(), sdb);
3179          }
3180        }
3181      }
3182     // work around for old badly generated SDs
3183//      if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) {
3184//        matchType = true;
3185//      }
3186//      if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) {
3187//        matchType = true;
3188//      }
3189      if (matchType) {
3190        ts.copyExtensions(td, "http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support", "http://hl7.org/fhir/StructureDefinition/elementdefinition-pattern", "http://hl7.org/fhir/StructureDefinition/obligation");
3191        if (ts.hasTargetProfile()) {
3192          // check that any derived target has a reference chain back to one of the base target profiles
3193          for (UriType u : ts.getTargetProfile()) {
3194            String url = u.getValue();
3195            boolean tgtOk = !td.hasTargetProfile() || sdConformsToTargets(path, derived.getPath(), url, td);            
3196            if (tgtOk) {
3197              ok = true;
3198            } else {
3199              addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, derived.getPath(), context.formatMessage(I18nConstants.ERROR_AT__THE_TARGET_PROFILE__IS_NOT__VALID_CONSTRAINT_ON_THE_BASE_, purl, derived.getPath(), url, td.getTargetProfile()), IssueSeverity.ERROR));
3200            }
3201          }
3202        } else {
3203          ok = true;
3204        }
3205      }
3206    }
3207    if (!ok && !isSuppressIgnorableExceptions()) {
3208      throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), tDesc, b.toString(), srcSD.getUrl()));
3209    }
3210  }
3211
3212
3213  private boolean sdConformsToTargets(String path, String dPath, String url, TypeRefComponent td) {
3214    if (td.hasTargetProfile(url)) {
3215      return true;
3216    }
3217    if (url != null && url.contains("|") && td.hasTargetProfile(url.substring(0, url.indexOf("|")))) {
3218      return true;
3219    }
3220    StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
3221    if (sd == null) {
3222      addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, "Cannot check whether the target profile " + url + " on "+dPath+" is valid constraint on the base because it is not known", IssueSeverity.WARNING));
3223      return true;
3224    } else {
3225      if (sd.hasBaseDefinition() && sdConformsToTargets(path, dPath, sd.getBaseDefinition(), td)) {
3226        return true;
3227      }
3228      for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {
3229        if (sdConformsToTargets(path, dPath, ext.getValueCanonicalType().asStringValue(), td)) {
3230          return true;
3231        }
3232      }
3233    }
3234    return false;
3235  }
3236
3237  private void checkTypeOk(ElementDefinition dest, String ft, StructureDefinition sd, String fieldName) {
3238    boolean ok = false;
3239    Set<String> types = new HashSet<>();
3240    if (dest.getPath().contains(".")) {
3241      for (TypeRefComponent t : dest.getType()) {
3242        if (t.hasCode()) {
3243          types.add(t.getWorkingCode());
3244        }
3245        ok = ok || ft.equals(t.getWorkingCode());
3246      }
3247    } else {
3248      types.add(sd.getType());
3249      ok = ok || ft.equals(sd.getType());
3250
3251    }
3252    if (!ok) {
3253      addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.CONFLICT, dest.getId(), "The "+fieldName+" value has type '"+ft+"' which is not valid (valid "+Utilities.pluralize("type", dest.getType().size())+": "+types.toString()+")", IssueSeverity.ERROR));
3254    }
3255  }
3256
3257  private boolean hasBindableType(ElementDefinition ed) {
3258    for (TypeRefComponent tr : ed.getType()) {
3259      if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code", "CodeableReference")) {
3260        return true;
3261      }
3262      StructureDefinition sd = context.fetchTypeDefinition(tr.getCode());
3263      if (sd != null && sd.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) {
3264        return true;
3265      }
3266      if (sd != null && sd.hasExtension(ToolingExtensions.EXT_TYPE_CHARACTERISTICS) &&
3267          "can-bind".equals(ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_TYPE_CHARACTERISTICS))) {
3268        return true;
3269      }
3270    }
3271    return false;
3272  }
3273
3274
3275  private boolean isLargerMax(String derived, String base) {
3276    if ("*".equals(base)) {
3277      return false;
3278    }
3279    if ("*".equals(derived)) {
3280      return true;
3281    }
3282    return Integer.parseInt(derived) > Integer.parseInt(base);
3283  }
3284
3285
3286  private String checkSubset(ValueSet expBase, ValueSet expDerived) {
3287    Set<String> codes = new HashSet<>();
3288    checkCodesInExpansion(codes, expDerived.getExpansion().getContains(), expBase.getExpansion());
3289    if (codes.isEmpty()) {
3290      return null;
3291    } else {
3292      return "The codes '"+CommaSeparatedStringBuilder.join(",", codes)+"' are not in the base valueset";
3293    }
3294  }
3295
3296
3297  private void checkCodesInExpansion(Set<String> codes, List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
3298    for (ValueSetExpansionContainsComponent cc : contains) {
3299      if (!inExpansion(cc, expansion.getContains())) {
3300        codes.add(cc.getCode());
3301      }
3302      checkCodesInExpansion(codes, cc.getContains(), expansion);
3303    }
3304  }
3305
3306
3307  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
3308    for (ValueSetExpansionContainsComponent cc1 : contains) {
3309      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) {
3310        return true;
3311      }
3312      if (inExpansion(cc,  cc1.getContains())) {
3313        return true;
3314      }
3315    }
3316    return false;
3317  }
3318  
3319  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
3320    for (ElementDefinition edb : base.getSnapshot().getElement()) {
3321      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
3322        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
3323        if (edm == null) {
3324          ElementDefinition edd = derived.getDifferential().addElement();
3325          edd.setPath(edb.getPath());
3326          edd.setMax("0");
3327        } else if (edb.hasSlicing()) {
3328          closeChildren(base, edb, derived, edm);
3329        }
3330      }
3331    }
3332    sortDifferential(base, derived, derived.getName(), new ArrayList<String>(), false);
3333  }
3334
3335  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
3336//    String path = edb.getPath()+".";
3337    int baseStart = base.getSnapshot().getElement().indexOf(edb);
3338    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
3339    int diffStart = derived.getDifferential().getElement().indexOf(edm);
3340    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
3341    
3342    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
3343      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
3344      if (isImmediateChild(edBase, edb)) {
3345        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
3346        if (edMatch == null) {
3347          ElementDefinition edd = derived.getDifferential().addElement();
3348          edd.setPath(edBase.getPath());
3349          edd.setMax("0");
3350        } else {
3351          closeChildren(base, edBase, derived, edMatch);
3352        }        
3353      }
3354    }
3355  }
3356
3357  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
3358    String path = ed.getPath()+".";
3359    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) {
3360      cursor++;
3361    }
3362    return cursor;
3363  }
3364
3365
3366  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
3367    for (ElementDefinition t : list) {
3368      if (t.getPath().equals(ed.getPath())) {
3369        return t;
3370      }
3371    }
3372    return null;
3373  }
3374
3375  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
3376    for (int i = start; i < end; i++) {
3377      ElementDefinition t = list.get(i);
3378      if (t.getPath().equals(ed.getPath())) {
3379        return t;
3380      }
3381    }
3382    return null;
3383  }
3384
3385
3386  private boolean isImmediateChild(ElementDefinition ed) {
3387    String p = ed.getPath();
3388    if (!p.contains(".")) {
3389      return false;
3390    }
3391    p = p.substring(p.indexOf(".")+1);
3392    return !p.contains(".");
3393  }
3394
3395  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
3396    String p = candidate.getPath();
3397    if (!p.contains("."))
3398      return false;
3399    if (!p.startsWith(base.getPath()+"."))
3400      return false;
3401    p = p.substring(base.getPath().length()+1);
3402    return !p.contains(".");
3403  }
3404
3405
3406
3407  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
3408    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
3409    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
3410      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
3411        return ed.getSnapshot().getElement().get(i);
3412      i++;
3413    }
3414    return null;
3415  }
3416
3417
3418
3419  protected ElementDefinitionResolution getElementById(StructureDefinition source, List<ElementDefinition> elements, String contentReference) {
3420    if (!contentReference.startsWith("#") && contentReference.contains("#")) {
3421      String url = contentReference.substring(0, contentReference.indexOf("#"));
3422      contentReference = contentReference.substring(contentReference.indexOf("#"));
3423      if (!url.equals(source.getUrl())){
3424        source = context.fetchResource(StructureDefinition.class, url, source);
3425        if (source == null) {
3426          return null;
3427        }
3428        elements = source.getSnapshot().getElement();
3429      }      
3430    }
3431    for (ElementDefinition ed : elements)
3432      if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
3433        return new ElementDefinitionResolution(source, ed);
3434    return null;
3435  }
3436
3437
3438  public static String describeExtensionContext(StructureDefinition ext) {
3439    StringBuilder b = new StringBuilder();
3440    b.append("Use on ");
3441    for (int i = 0; i < ext.getContext().size(); i++) {
3442      StructureDefinitionContextComponent ec = ext.getContext().get(i);
3443      if (i > 0) 
3444        b.append(i < ext.getContext().size() - 1 ? ", " : " or ");
3445      b.append(ec.getType().getDisplay());
3446      b.append(" ");
3447      b.append(ec.getExpression());
3448    }
3449    if (ext.hasContextInvariant()) {
3450      b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = ");
3451      boolean first = true;
3452      for (StringType s : ext.getContextInvariant()) {
3453        if (first)
3454          first = false;
3455        else
3456          b.append(", ");
3457        b.append("<code>"+s.getValue()+"</code>");
3458      }
3459    }
3460    return b.toString(); 
3461  }
3462
3463
3464
3465 
3466//  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath,
3467//                                 boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc) throws IOException, FHIRException {
3468//    return generateTable(defFile, profile, diff, imageFolder, inlineGraphics, profileBaseFileName, snapshot, corePath, imagePath, logicalModel, allInvariants, outputTracker, mustSupport, rc, "");
3469//  }
3470
3471 
3472
3473
3474 
3475  protected String tail(String path) {
3476    if (path == null) {
3477      return "";
3478    } else if (path.contains("."))
3479      return path.substring(path.lastIndexOf('.')+1);
3480    else
3481      return path;
3482  }
3483
3484  private boolean isDataType(String value) {
3485    StructureDefinition sd = context.fetchTypeDefinition(value);
3486    if (sd == null) // might be running before all SDs are available
3487      return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", 
3488            "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
3489    else 
3490      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
3491  }
3492
3493  private boolean isConstrainedDataType(String value) {
3494    StructureDefinition sd = context.fetchTypeDefinition(value);
3495    if (sd == null) // might be running before all SDs are available
3496      return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity");
3497    else 
3498      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT;
3499  }
3500
3501  private String baseType(String value) {
3502    StructureDefinition sd = context.fetchTypeDefinition(value);
3503    if (sd != null) // might be running before all SDs are available
3504      return sd.getTypeName();
3505    if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"))
3506      return "Quantity";
3507    throw new Error(context.formatMessage(I18nConstants.INTERNAL_ERROR___TYPE_NOT_KNOWN_, value));
3508  }
3509
3510
3511  protected boolean isPrimitive(String value) {
3512    StructureDefinition sd = context.fetchTypeDefinition(value);
3513    if (sd == null) // might be running before all SDs are available
3514      return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid");
3515    else 
3516      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
3517  }
3518
3519//  private static String listStructures(StructureDefinition p) {
3520//    StringBuilder b = new StringBuilder();
3521//    boolean first = true;
3522//    for (ProfileStructureComponent s : p.getStructure()) {
3523//      if (first)
3524//        first = false;
3525//      else
3526//        b.append(", ");
3527//      if (pkp != null && pkp.hasLinkFor(s.getType()))
3528//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
3529//      else
3530//        b.append(s.getType());
3531//    }
3532//    return b.toString();
3533//  }
3534
3535
3536  public StructureDefinition getProfile(StructureDefinition source, String url) {
3537        StructureDefinition profile = null;
3538        String code = null;
3539        if (url.startsWith("#")) {
3540                profile = source;
3541                code = url.substring(1);
3542        } else if (context != null) {
3543                String[] parts = url.split("\\#");
3544                profile = context.fetchResource(StructureDefinition.class, parts[0], source);
3545      code = parts.length == 1 ? null : parts[1];
3546        }         
3547        if (profile == null)
3548                return null;
3549        if (code == null)
3550                return profile;
3551        for (Resource r : profile.getContained()) {
3552                if (r instanceof StructureDefinition && r.getId().equals(code))
3553                        return (StructureDefinition) r;
3554        }
3555        return null;
3556  }
3557
3558
3559
3560  private static class ElementDefinitionHolder {
3561    private String name;
3562    private ElementDefinition self;
3563    private int baseIndex = 0;
3564    private List<ElementDefinitionHolder> children;
3565    private boolean placeHolder = false;
3566
3567    public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) {
3568      super();
3569      this.self = self;
3570      this.name = self.getPath();
3571      this.placeHolder = isPlaceholder;
3572      children = new ArrayList<ElementDefinitionHolder>();      
3573    }
3574
3575    public ElementDefinitionHolder(ElementDefinition self) {
3576      this(self, false);
3577    }
3578
3579    public ElementDefinition getSelf() {
3580      return self;
3581    }
3582
3583    public List<ElementDefinitionHolder> getChildren() {
3584      return children;
3585    }
3586
3587    public int getBaseIndex() {
3588      return baseIndex;
3589    }
3590
3591    public void setBaseIndex(int baseIndex) {
3592      this.baseIndex = baseIndex;
3593    }
3594
3595    public boolean isPlaceHolder() {
3596      return this.placeHolder;
3597    }
3598
3599    @Override
3600    public String toString() {
3601      if (self.hasSliceName())
3602        return self.getPath()+"("+self.getSliceName()+")";
3603      else
3604        return self.getPath();
3605    }
3606  }
3607
3608  private static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
3609
3610    private boolean inExtension;
3611    private List<ElementDefinition> snapshot;
3612    private int prefixLength;
3613    private String base;
3614    private String name;
3615    private String baseName;
3616    private Set<String> errors = new HashSet<String>();
3617
3618    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name, String baseName) {
3619      this.inExtension = inExtension;
3620      this.snapshot = snapshot;
3621      this.prefixLength = prefixLength;
3622      this.base = base;
3623      if (Utilities.isAbsoluteUrl(base)) {
3624        this.base = urlTail(base);
3625      }
3626      this.name = name;
3627      this.baseName = baseName;
3628    }
3629
3630    @Override
3631    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
3632      if (o1.getBaseIndex() == 0) {
3633        o1.setBaseIndex(find(o1.getSelf().getPath(), true));
3634      }
3635      if (o2.getBaseIndex() == 0) {
3636        o2.setBaseIndex(find(o2.getSelf().getPath(), true));
3637      }
3638      return o1.getBaseIndex() - o2.getBaseIndex();
3639    }
3640
3641    private int find(String path, boolean mandatory) {
3642      String op = path;
3643      int lc = 0;
3644      String actual = base+path.substring(prefixLength);
3645      for (int i = 0; i < snapshot.size(); i++) {
3646        String p = snapshot.get(i).getPath();
3647        if (p.equals(actual)) {
3648          return i;
3649        }
3650        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
3651          return i;
3652        }
3653        if (actual.endsWith("[x]") && p.startsWith(actual.substring(0, actual.length()-3)) && !p.substring(actual.length()-3).contains(".")) {
3654          return i;
3655        }
3656        if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
3657          String ref = snapshot.get(i).getContentReference();
3658          if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) {
3659            actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3660            path = actual;
3661          } else if (ref.startsWith("http:")) {
3662            actual = base+(ref.substring(ref.indexOf("#")+1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3663            path = actual;            
3664          } else {
3665            // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that
3666            actual = base+(path.substring(0,  path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3667            path = actual;
3668          }
3669            
3670          i = 0;
3671          lc++;
3672          if (lc > MAX_RECURSION_LIMIT)
3673            throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")");
3674        }
3675      }
3676      if (mandatory) {
3677        if (prefixLength == 0)
3678          errors.add("Differential contains path "+path+" which is not found in the base "+baseName);
3679        else
3680          errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+ baseName);
3681      }
3682      return 0;
3683    }
3684
3685    public void checkForErrors(List<String> errorList) {
3686      if (errors.size() > 0) {
3687//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3688//        for (String s : errors)
3689//          b.append("StructureDefinition "+name+": "+s);
3690//        throw new DefinitionException(b.toString());
3691        for (String s : errors)
3692          if (s.startsWith("!"))
3693            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
3694          else
3695            errorList.add("StructureDefinition "+name+": "+s);
3696      }
3697    }
3698  }
3699
3700
3701  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors, boolean errorIfChanges) throws FHIRException  {
3702    int index = 0;
3703    for (ElementDefinition ed : diff.getDifferential().getElement()) {
3704      ed.setUserData(UserDataNames.SNAPSHOT_SORT_ed_index, Integer.toString(index));
3705      index++;
3706    }
3707    List<ElementDefinition> original = new ArrayList<>();
3708    original.addAll(diff.getDifferential().getElement());
3709    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
3710    int lastCount = diffList.size();
3711    // first, we move the differential elements into a tree
3712    if (diffList.isEmpty())
3713      return;
3714    
3715    ElementDefinitionHolder edh = null;
3716    int i = 0;
3717    if (diffList.get(0).getPath().contains(".")) {
3718      String newPath = diffList.get(0).getPath().split("\\.")[0];
3719      ElementDefinition e = new ElementDefinition(newPath);
3720      edh = new ElementDefinitionHolder(e, true);
3721    } else {
3722      edh = new ElementDefinitionHolder(diffList.get(0));
3723      i = 1;
3724    }
3725
3726    boolean hasSlicing = false;
3727    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
3728    for(ElementDefinition elt : diffList) {
3729      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
3730        hasSlicing = true;
3731        break;
3732      }
3733      paths.add(elt.getPath());
3734    }
3735
3736    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
3737
3738    // now, we sort the siblings throughout the tree
3739    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name, base.getType());
3740    sortElements(edh, cmp, errors);
3741
3742    // now, we serialise them back to a list
3743    List<ElementDefinition> newDiff = new ArrayList<>();
3744    writeElements(edh, newDiff);
3745    if (errorIfChanges) {
3746      compareDiffs(original, newDiff, errors);
3747    }
3748    diffList.clear();
3749    diffList.addAll(newDiff);
3750    
3751    if (lastCount != diffList.size())
3752      errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal");
3753  }
3754
3755  private void compareDiffs(List<ElementDefinition> diffList, List<ElementDefinition> newDiff, List<String> errors) {
3756    if (diffList.size() != newDiff.size()) {
3757      errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size()+
3758          " ["+CommaSeparatedStringBuilder.buildObjects(diffList)+"]/["+CommaSeparatedStringBuilder.buildObjects(newDiff)+"]");
3759    } else {
3760      for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) {
3761        ElementDefinition e = diffList.get(i);
3762        ElementDefinition n = newDiff.get(i);
3763        if (!n.getPath().equals(e.getPath())) {
3764          errors.add("The element "+(e.hasId() ? e.getId() : e.getPath())+" @diff["+e.getUserString(UserDataNames.SNAPSHOT_SORT_ed_index)+"] is out of order (and maybe others after it)");
3765          return;
3766        }   
3767      }
3768    }
3769  }
3770
3771
3772  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
3773    String path = edh.getSelf().getPath();
3774    final String prefix = path + ".";
3775    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
3776      if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) {
3777        String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0];
3778        ElementDefinition e = new ElementDefinition(newPath);
3779        ElementDefinitionHolder child = new ElementDefinitionHolder(e, true);
3780        edh.getChildren().add(child);
3781        i = processElementsIntoTree(child, i, list);
3782        
3783      } else {
3784        ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
3785        edh.getChildren().add(child);
3786        i = processElementsIntoTree(child, i+1, list);
3787      }
3788    }
3789    return i;
3790  }
3791
3792  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
3793    if (edh.getChildren().size() == 1)
3794      // special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly
3795      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false);
3796    else
3797      Collections.sort(edh.getChildren(), cmp);
3798    if (debug) {
3799      cmp.checkForErrors(errors);
3800    }
3801
3802    for (ElementDefinitionHolder child : edh.getChildren()) {
3803      if (child.getChildren().size() > 0) {
3804        ElementDefinitionComparer ccmp = getComparer(cmp, child);
3805        if (ccmp != null) {
3806          sortElements(child, ccmp, errors);
3807        }
3808      }
3809    }
3810  }
3811
3812
3813  public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error {
3814    // what we have to check for here is running off the base profile into a data type profile
3815    ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
3816    ElementDefinitionComparer ccmp;
3817    if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
3818      if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) {
3819        if (child.getSelf().getType().get(0).getProfile().size() > 1) {
3820          throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE));
3821        }
3822        StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
3823        while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
3824          profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition());          
3825        }
3826        if (profile==null) {
3827          ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
3828        } else {
3829          ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name, profile.present());
3830        }
3831      } else {
3832        ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name, cmp.name);
3833      }
3834    } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
3835      StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
3836      if (profile==null)
3837        ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
3838      else
3839        ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present());
3840    } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
3841      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3842      if (profile==null)
3843        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
3844      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present());
3845    } else if (child.getSelf().getType().size() == 1) {
3846      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode()));
3847      if (profile==null)
3848        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
3849      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
3850    } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
3851      String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
3852      String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
3853      String p = childLastNode.substring(edLastNode.length()-3);
3854      if (isPrimitive(Utilities.uncapitalize(p)))
3855        p = Utilities.uncapitalize(p);
3856      StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p));
3857      if (sd == null)
3858        throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId()));
3859      ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name, sd.present());
3860    } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
3861      for (TypeRefComponent t: child.getSelf().getType()) {
3862        if (!t.getWorkingCode().equals("Reference")) {
3863          throw new Error(context.formatMessage(I18nConstants.CANT_HAVE_CHILDREN_ON_AN_ELEMENT_WITH_A_POLYMORPHIC_TYPE__YOU_MUST_SLICE_AND_CONSTRAIN_THE_TYPES_FIRST_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType())));
3864        }
3865      }
3866      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3867      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
3868    } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
3869      for (TypeRefComponent t: ed.getType()) {
3870        if (!t.getWorkingCode().equals("Reference")) {
3871          throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType())));
3872        }
3873      }
3874      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3875      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
3876    } else {
3877      // this is allowed if we only profile the extensions
3878      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element"));
3879      if (profile==null)
3880        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
3881      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name, profile.present());
3882//      throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3883    }
3884    return ccmp;
3885  }
3886
3887  private String resolveType(String code) {
3888    if (Utilities.isAbsoluteUrl(code)) {
3889      StructureDefinition sd = context.fetchResource(StructureDefinition.class, code);
3890      if (sd != null) {
3891        return sd.getType();
3892      }
3893    }
3894    return code;
3895  }
3896
3897  private static String sdNs(String type) {
3898    return sdNs(type, null);
3899  }
3900  
3901  public static String sdNs(String type, String overrideVersionNs) {
3902    if (Utilities.isAbsoluteUrl(type))
3903      return type;
3904    else if (overrideVersionNs != null)
3905      return Utilities.pathURL(overrideVersionNs, type);
3906    else
3907      return "http://hl7.org/fhir/StructureDefinition/"+type;
3908  }
3909
3910
3911  private boolean isAbstract(String code) {
3912    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
3913  }
3914
3915
3916  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
3917    if (!edh.isPlaceHolder())
3918      list.add(edh.getSelf());
3919    for (ElementDefinitionHolder child : edh.getChildren()) {
3920      writeElements(child, list);
3921    }
3922  }
3923
3924  /**
3925   * First compare element by path then by name if same
3926   */
3927  private static class ElementNameCompare implements Comparator<ElementDefinition> {
3928
3929    @Override
3930    public int compare(ElementDefinition o1, ElementDefinition o2) {
3931      String path1 = normalizePath(o1);
3932      String path2 = normalizePath(o2);
3933      int cmp = path1.compareTo(path2);
3934      if (cmp == 0) {
3935        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
3936        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
3937        cmp = name1.compareTo(name2);
3938      }
3939      return cmp;
3940    }
3941
3942    private static String normalizePath(ElementDefinition e) {
3943      if (!e.hasPath()) return "";
3944      String path = e.getPath();
3945      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
3946      // so strip off the [x] suffix when comparing the path names.
3947      if (path.endsWith("[x]")) {
3948        path = path.substring(0, path.length()-3);
3949      }
3950      return path;
3951    }
3952
3953  }
3954
3955
3956  // generate schematrons for the rules in a structure definition
3957  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
3958    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
3959      throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR));
3960    if (!structure.hasSnapshot())
3961      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
3962
3963        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition(), structure);
3964
3965        if (base != null) {
3966          SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
3967
3968          ElementDefinition ed = structure.getSnapshot().getElement().get(0);
3969          generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
3970          sch.dump();
3971        }
3972  }
3973
3974  // generate a CSV representation of the structure definition
3975  public void generateCsv(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
3976    if (!structure.hasSnapshot())
3977      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
3978
3979    CSVWriter csv = new CSVWriter(dest, structure, asXml);
3980
3981    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3982      csv.processElement(null, child);
3983    }
3984    csv.dump();
3985  }
3986  
3987  // generate a CSV representation of the structure definition
3988  public void addToCSV(CSVWriter csv, StructureDefinition structure) throws IOException, DefinitionException, Exception {
3989    if (!structure.hasSnapshot())
3990      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
3991
3992    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3993      csv.processElement(structure, child);
3994    }
3995  }
3996  
3997  
3998  private class Slicer extends ElementDefinitionSlicingComponent {
3999    String criteria = "";
4000    String name = "";   
4001    boolean check;
4002    public Slicer(boolean cantCheck) {
4003      super();
4004      this.check = cantCheck;
4005    }
4006  }
4007  
4008  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
4009    // given a child in a structure, it's sliced. figure out the slicing xpath
4010    if (child.getPath().endsWith(".extension")) {
4011      ElementDefinition ued = getUrlFor(structure, child);
4012      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
4013        return new Slicer(false);
4014      else {
4015      Slicer s = new Slicer(true);
4016      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue();
4017      s.name = " with URL = '"+url+"'";
4018      s.criteria = "[@url = '"+url+"']";
4019      return s;
4020      }
4021    } else
4022      return new Slicer(false);
4023  }
4024
4025  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
4026    //    generateForChild(txt, structure, child);
4027    List<ElementDefinition> children = getChildList(structure, ed);
4028    String sliceName = null;
4029    ElementDefinitionSlicingComponent slicing = null;
4030    for (ElementDefinition child : children) {
4031      String name = tail(child.getPath());
4032      if (child.hasSlicing()) {
4033        sliceName = name;
4034        slicing = child.getSlicing();        
4035      } else if (!name.equals(sliceName))
4036        slicing = null;
4037
4038      ElementDefinition based = getByPath(base, child.getPath());
4039      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
4040      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
4041      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
4042      if (slicer.check) {
4043        if (doMin || doMax) {
4044          Section s = sch.section(xpath);
4045          Rule r = s.rule(xpath);
4046          if (doMin) 
4047            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
4048          if (doMax) 
4049            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
4050        }
4051      }
4052    }
4053/// xpath has been removed
4054//    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
4055//      if (inv.hasXpath()) {
4056//        Section s = sch.section(ed.getPath());
4057//        Rule r = s.rule(xpath);
4058//        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
4059//      }
4060//    }
4061    if (!ed.hasContentReference()) {
4062      for (ElementDefinition child : children) {
4063        String name = tail(child.getPath());
4064        generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
4065      }
4066    }
4067  }
4068
4069
4070
4071
4072  private ElementDefinition getByPath(StructureDefinition base, String path) {
4073                for (ElementDefinition ed : base.getSnapshot().getElement()) {
4074                        if (ed.getPath().equals(path))
4075                                return ed;
4076                        if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length()-3 &&  ed.getPath().substring(0, ed.getPath().length()-3).equals(path.substring(0, ed.getPath().length()-3)))
4077                                return ed;
4078    }
4079          return null;
4080  }
4081
4082
4083  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
4084    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
4085      if (!sd.hasDifferential())
4086        sd.setDifferential(new StructureDefinitionDifferentialComponent());
4087      generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType(), sd);
4088    }
4089    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
4090      if (!sd.hasSnapshot())
4091        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
4092      generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType(), sd);
4093    }
4094  }
4095
4096
4097  private boolean hasMissingIds(List<ElementDefinition> list) {
4098    for (ElementDefinition ed : list) {
4099      if (!ed.hasId())
4100        return true;
4101    }    
4102    return false;
4103  }
4104
4105  private class SliceList {
4106
4107    private Map<String, String> slices = new HashMap<>();
4108    
4109    public void seeElement(ElementDefinition ed) {
4110      Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator();
4111      while (iter.hasNext()) {
4112        Map.Entry<String,String> entry = iter.next();
4113        if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath()))
4114          iter.remove();
4115      }
4116      
4117      if (ed.hasSliceName()) 
4118        slices.put(ed.getPath(), ed.getSliceName());
4119    }
4120
4121    public String[] analyse(List<String> paths) {
4122      String s = paths.get(0);
4123      String[] res = new String[paths.size()];
4124      res[0] = null;
4125      for (int i = 1; i < paths.size(); i++) {
4126        s = s + "."+paths.get(i);
4127        if (slices.containsKey(s)) 
4128          res[i] = slices.get(s);
4129        else
4130          res[i] = null;
4131      }
4132      return res;
4133    }
4134
4135  }
4136
4137  protected void generateIds(List<ElementDefinition> list, String name, String type, StructureDefinition srcSD) throws DefinitionException  {
4138    if (list.isEmpty())
4139      return;
4140    
4141    Map<String, String> idList = new HashMap<String, String>();
4142    Map<String, String> replacedIds = new HashMap<String, String>();
4143    
4144    SliceList sliceInfo = new SliceList();
4145    // first pass, update the element ids
4146    for (ElementDefinition ed : list) {
4147      List<String> paths = new ArrayList<String>();
4148      if (!ed.hasPath())
4149        throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name));
4150      sliceInfo.seeElement(ed);
4151      String[] pl = ed.getPath().split("\\.");
4152      for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus
4153        paths.add(pl[i]);
4154      String slices[] = sliceInfo.analyse(paths);
4155      
4156      StringBuilder b = new StringBuilder();
4157      b.append(paths.get(0));
4158      for (int i = 1; i < paths.size(); i++) {
4159        b.append(".");
4160        String s = paths.get(i);
4161        String p = slices[i];
4162        b.append(fixChars(s));
4163        if (p != null) {
4164          b.append(":");
4165          b.append(p);
4166        }
4167      }
4168      String bs = b.toString();
4169      if (ed.hasId()) {
4170        replacedIds.put(ed.getId(), ed.getPath());
4171      }
4172      ed.setId(bs);
4173      if (idList.containsKey(bs)) {
4174        addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, context.formatMessage(I18nConstants.SAME_ID_ON_MULTIPLE_ELEMENTS__IN_, bs, idList.get(bs), ed.getPath(), name), ValidationMessage.IssueSeverity.ERROR));
4175      }
4176      idList.put(bs, ed.getPath());
4177      if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) {
4178        String s = ed.getContentReference();
4179        String typeURL = getUrlForSource(type, srcSD);
4180        if (replacedIds.containsKey(s.substring(1))) {
4181          ed.setContentReference(typeURL+"#"+replacedIds.get(s.substring(1)));
4182        } else {
4183          ed.setContentReference(typeURL+s);
4184        }
4185      }
4186    }  
4187    // second path - fix up any broken path based id references
4188    
4189  }
4190
4191
4192  private String getUrlForSource(String type, StructureDefinition srcSD) {
4193    if (srcSD.getKind() == StructureDefinitionKind.LOGICAL) {
4194      return srcSD.getUrl();
4195    } else {
4196      return "http://hl7.org/fhir/StructureDefinition/"+type;
4197    }
4198  }
4199
4200  private Object fixChars(String s) {
4201    return s.replace("_", "-");
4202  }
4203
4204
4205//  private String describeExtension(ElementDefinition ed) {
4206//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
4207//      return "";
4208//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
4209//  }
4210//
4211
4212  private static String urlTail(String profile) {
4213    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
4214  }
4215//
4216//
4217//  private String checkName(String name) {
4218////    if (name.contains("."))
4219//////      throw new Exception("Illegal name "+name+": no '.'");
4220////    if (name.contains(" "))
4221////      throw new Exception("Illegal name "+name+": no spaces");
4222//    StringBuilder b = new StringBuilder();
4223//    for (char c : name.toCharArray()) {
4224//      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
4225//        b.append(c);
4226//    }
4227//    return b.toString().toLowerCase();
4228//  }
4229//
4230//
4231//  private int charCount(String path, char t) {
4232//    int res = 0;
4233//    for (char ch : path.toCharArray()) {
4234//      if (ch == t)
4235//        res++;
4236//    }
4237//    return res;
4238//  }
4239
4240//
4241//private void generateForChild(TextStreamWriter txt,
4242//    StructureDefinition structure, ElementDefinition child) {
4243//  // TODO Auto-generated method stub
4244//
4245//}
4246
4247  private interface ExampleValueAccessor {
4248    DataType getExampleValue(ElementDefinition ed);
4249    String getId();
4250  }
4251
4252  private class BaseExampleValueAccessor implements ExampleValueAccessor {
4253    @Override
4254    public DataType getExampleValue(ElementDefinition ed) {
4255      if (ed.hasFixed())
4256        return ed.getFixed();
4257      if (ed.hasExample())
4258        return ed.getExample().get(0).getValue();
4259      else
4260        return null;
4261    }
4262
4263    @Override
4264    public String getId() {
4265      return "-genexample";
4266    }
4267  }
4268  
4269  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
4270    private String index;
4271
4272    public ExtendedExampleValueAccessor(String index) {
4273      this.index = index;
4274    }
4275    @Override
4276    public DataType getExampleValue(ElementDefinition ed) {
4277      if (ed.hasFixed())
4278        return ed.getFixed();
4279      for (Extension ex : ed.getExtension()) {
4280       String ndx = ToolingExtensions.readStringExtension(ex, "index");
4281       DataType value = ToolingExtensions.getExtension(ex, "exValue").getValue();
4282       if (index.equals(ndx) && value != null)
4283         return value;
4284      }
4285      return null;
4286    }
4287    @Override
4288    public String getId() {
4289      return "-genexample-"+index;
4290    }
4291  }
4292  
4293  public List<org.hl7.fhir.r5.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
4294    List<org.hl7.fhir.r5.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r5.elementmodel.Element>();
4295    if (sd.hasSnapshot()) {
4296      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
4297        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
4298      for (int i = 1; i <= 50; i++) {
4299        if (hasAnyExampleValues(sd, Integer.toString(i))) 
4300          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
4301      }
4302    }
4303    return examples;
4304  }
4305
4306  private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
4307    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
4308    org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
4309    SourcedChildDefinitions children = getChildMap(profile, ed, true);
4310    for (ElementDefinition child : children.getList()) {
4311      if (child.getPath().endsWith(".id")) {
4312        org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile));
4313        id.setValue(profile.getId()+accessor.getId());
4314        r.getChildren().add(id);
4315      } else { 
4316        org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor);
4317        if (e != null)
4318          r.getChildren().add(e);
4319      }
4320    }
4321    return r;
4322  }
4323
4324  private org.hl7.fhir.r5.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
4325    DataType v = accessor.getExampleValue(ed);
4326    if (v != null) {
4327      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
4328    } else {
4329      org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
4330      boolean hasValue = false;
4331      SourcedChildDefinitions children = getChildMap(profile, ed, true);
4332      for (ElementDefinition child : children.getList()) {
4333        if (!child.hasContentReference()) {
4334        org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor);
4335        if (e != null) {
4336          hasValue = true;
4337          res.getChildren().add(e);
4338        }
4339      }
4340      }
4341      if (hasValue)
4342        return res;
4343      else
4344        return null;
4345    }
4346  }
4347
4348  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
4349    for (ElementDefinition ed : sd.getSnapshot().getElement())
4350      for (Extension ex : ed.getExtension()) {
4351        String ndx = ToolingExtensions.readStringExtension(ex, "index");
4352        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
4353        if (exv != null) {
4354          DataType value = exv.getValue();
4355        if (index.equals(ndx) && value != null)
4356          return true;
4357        }
4358       }
4359    return false;
4360  }
4361
4362
4363  private boolean hasAnyExampleValues(StructureDefinition sd) {
4364    for (ElementDefinition ed : sd.getSnapshot().getElement())
4365      if (ed.hasExample())
4366        return true;
4367    return false;
4368  }
4369
4370
4371  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
4372    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
4373    
4374    if (sd.hasBaseDefinition()) {
4375    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
4376    if (base == null)
4377        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl()));
4378    copyElements(sd, base.getSnapshot().getElement());
4379    }
4380    copyElements(sd, sd.getDifferential().getElement());
4381  }
4382
4383
4384  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
4385    for (ElementDefinition ed : list) {
4386      if (ed.getPath().contains(".")) {
4387        ElementDefinition n = ed.copy();
4388        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
4389        sd.getSnapshot().addElement(n);
4390      }
4391    }
4392  }
4393
4394    
4395  public void cleanUpDifferential(StructureDefinition sd) {
4396    if (sd.getDifferential().getElement().size() > 1)
4397      cleanUpDifferential(sd, 1);
4398  }
4399  
4400  private void cleanUpDifferential(StructureDefinition sd, int start) {
4401    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
4402    int c = start;
4403    int len = sd.getDifferential().getElement().size();
4404    HashSet<String> paths = new HashSet<String>();
4405    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
4406      ElementDefinition ed = sd.getDifferential().getElement().get(c);
4407      if (!paths.contains(ed.getPath())) {
4408        paths.add(ed.getPath());
4409        int ic = c+1; 
4410        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4411          ic++;
4412        ElementDefinition slicer = null;
4413        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
4414        slices.add(ed);
4415        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
4416          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
4417          if (ed.getPath().equals(edi.getPath())) {
4418            if (slicer == null) {
4419              slicer = new ElementDefinition();
4420              slicer.setPath(edi.getPath());
4421              slicer.getSlicing().setRules(SlicingRules.OPEN);
4422              sd.getDifferential().getElement().add(c, slicer);
4423              c++;
4424              ic++;
4425            }
4426            slices.add(edi);
4427          }
4428          ic++;
4429          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4430            ic++;
4431        }
4432        // now we're at the end, we're going to figure out the slicing discriminator
4433        if (slicer != null)
4434          determineSlicing(slicer, slices);
4435      }
4436      c++;
4437      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
4438        cleanUpDifferential(sd, c);
4439        c++;
4440        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
4441          c++;
4442      }
4443  }
4444  }
4445
4446
4447  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
4448    // first, name them
4449    int i = 0;
4450    for (ElementDefinition ed : slices) {
4451      if (ed.hasUserData(UserDataNames.SNAPSHOT_slice_name)) {
4452        ed.setSliceName(ed.getUserString(UserDataNames.SNAPSHOT_slice_name));
4453      } else {
4454        i++;
4455        ed.setSliceName("slice-"+Integer.toString(i));
4456      }
4457    }
4458    // now, the hard bit, how are they differentiated? 
4459    // right now, we hard code this...
4460    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
4461      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
4462    else if (slicer.getPath().equals("DiagnosticReport.result"))
4463      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
4464    else if (slicer.getPath().equals("Observation.related"))
4465      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
4466    else if (slicer.getPath().equals("Bundle.entry"))
4467      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
4468    else  
4469      throw new Error("No slicing for "+slicer.getPath());
4470  }
4471
4472
4473  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) {
4474    if (discriminator.endsWith("@pattern"))
4475      return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4476    if (discriminator.endsWith("@profile"))
4477      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4478    if (discriminator.endsWith("@type")) 
4479      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6));
4480    if (discriminator.endsWith("@exists"))
4481      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 
4482    if (isExists)
4483      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 
4484    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
4485  }
4486
4487
4488  private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) {
4489    return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str);
4490  }
4491
4492
4493  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
4494    switch (t.getType()) {
4495    case PROFILE: return t.getPath()+"/@profile";
4496    case PATTERN: return t.getPath()+"/@pattern";
4497    case TYPE: return t.getPath()+"/@type";
4498    case VALUE: return t.getPath();
4499    case EXISTS: return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - one with minOccurs=1 and other with maxOccur=0
4500    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
4501    }
4502  }
4503
4504
4505  public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) {
4506    String epath = url.substring(54);
4507    if (!epath.contains("."))
4508      return null;
4509    String type = epath.substring(0, epath.indexOf("."));
4510    StructureDefinition sd = context.fetchTypeDefinition(type);
4511    if (sd == null)
4512      return null;
4513    ElementDefinition ed = null;
4514    for (ElementDefinition t : sd.getSnapshot().getElement()) {
4515      if (t.getPath().equals(epath)) {
4516        ed = t;
4517        break;
4518      }
4519    }
4520    if (ed == null)
4521      return null;
4522    if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) {
4523      return null;
4524    } else {
4525      StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities");
4526      StructureDefinition ext = template.copy();
4527      ext.setUrl(url);
4528      ext.setId("extension-"+epath);
4529      ext.setName("Extension-"+epath);
4530      ext.setTitle("Extension for r4 "+epath);
4531      ext.setStatus(sd.getStatus());
4532      ext.setDate(sd.getDate());
4533      ext.getContact().clear();
4534      ext.getContact().addAll(sd.getContact());
4535      ext.setFhirVersion(sd.getFhirVersion());
4536      ext.setDescription(ed.getDefinition());
4537      ext.getContext().clear();
4538      ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf(".")));
4539      ext.getDifferential().getElement().clear();
4540      ext.getSnapshot().getElement().get(3).setFixed(new UriType(url));
4541      ext.getSnapshot().getElement().set(4, ed.copy());
4542      ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary()));
4543      return ext;      
4544    }
4545
4546  }
4547
4548
4549  public boolean isThrowException() {
4550    return wantThrowExceptions;
4551  }
4552
4553
4554  public void setThrowException(boolean exception) {
4555    this.wantThrowExceptions = exception;
4556  }
4557
4558
4559  public ValidationOptions getTerminologyServiceOptions() {
4560    return terminologyServiceOptions;
4561  }
4562
4563
4564  public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
4565    this.terminologyServiceOptions = terminologyServiceOptions;
4566  }
4567
4568
4569  public boolean isNewSlicingProcessing() {
4570    return newSlicingProcessing;
4571  }
4572
4573
4574  public ProfileUtilities setNewSlicingProcessing(boolean newSlicingProcessing) {
4575    this.newSlicingProcessing = newSlicingProcessing;
4576    return this;
4577  }
4578
4579
4580  public boolean isDebug() {
4581    return debug;
4582  }
4583
4584
4585  public void setDebug(boolean debug) {
4586    this.debug = debug;
4587  }
4588
4589
4590  public String getDefWebRoot() {
4591    return defWebRoot;
4592  }
4593
4594
4595  public void setDefWebRoot(String defWebRoot) {
4596    this.defWebRoot = defWebRoot;
4597    if (!this.defWebRoot.endsWith("/"))
4598      this.defWebRoot = this.defWebRoot + '/';
4599  }
4600
4601
4602  public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) {
4603    return makeBaseDefinition(fhirVersion.toCode());
4604  }
4605  public static StructureDefinition makeBaseDefinition(String fhirVersion) {
4606    StructureDefinition base = new StructureDefinition();
4607    base.setId("Base");
4608    base.setUrl("http://hl7.org/fhir/StructureDefinition/Base");
4609    base.setVersion(fhirVersion);
4610    base.setName("Base"); 
4611    base.setStatus(PublicationStatus.ACTIVE);
4612    base.setDate(new Date());
4613    base.setFhirVersion(FHIRVersion.fromCode(fhirVersion));
4614    base.setKind(StructureDefinitionKind.COMPLEXTYPE); 
4615    base.setAbstract(true); 
4616    base.setType("Base");
4617    base.setWebPath("http://build.fhir.org/types.html#Base");
4618    ElementDefinition e = base.getSnapshot().getElementFirstRep();
4619    e.setId("Base");
4620    e.setPath("Base"); 
4621    e.setMin(0); 
4622    e.setMax("*"); 
4623    e.getBase().setPath("Base");
4624    e.getBase().setMin(0); 
4625    e.getBase().setMax("*"); 
4626    e.setIsModifier(false); 
4627    e = base.getDifferential().getElementFirstRep();
4628    e.setId("Base");
4629    e.setPath("Base"); 
4630    e.setMin(0); 
4631    e.setMax("*"); 
4632    return base;
4633  }
4634
4635  public XVerExtensionManager getXver() {
4636    return xver;
4637  }
4638
4639  public ProfileUtilities setXver(XVerExtensionManager xver) {
4640    this.xver = xver;
4641    return this;
4642  }
4643
4644
4645  private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) {
4646    List<ElementChoiceGroup> result = new ArrayList<>();
4647    for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
4648      ElementChoiceGroup grp = processConstraint(children, c);
4649      if (grp != null) {
4650        result.add(grp);
4651      }
4652    }
4653    return result;
4654  }
4655
4656  public ElementChoiceGroup processConstraint(List<ElementDefinition> children, ElementDefinitionConstraintComponent c) {
4657    if (!c.hasExpression()) {
4658      return null;
4659    }
4660    ExpressionNode expr = null;
4661    try {
4662      expr = fpe.parse(c.getExpression());
4663    } catch (Exception e) {
4664      return null;
4665    }
4666    if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) {
4667      return null;      
4668    }
4669    ExpressionNode n1 = expr.getGroup();
4670    ExpressionNode n2 = expr.getOpNext();
4671    if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) {
4672      return null;
4673    }
4674    ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals);
4675    while (n1 != null) {
4676      if (n1.getKind() != Kind.Name || n1.getInner() != null) {
4677        return null;
4678      }
4679      grp.elements.add(n1.getName());
4680      if (n1.getOperation() == null || n1.getOperation() == Operation.Union) {
4681        n1 = n1.getOpNext();
4682      } else {
4683        return null;
4684      }
4685    }
4686    int total = 0;
4687    for (String n : grp.elements) {
4688      boolean found = false;
4689      for (ElementDefinition child : children) {
4690        String name = tail(child.getPath());
4691        if (n.equals(name)) {
4692          found = true;
4693          if (!"0".equals(child.getMax())) {
4694            total++;
4695          }
4696        }
4697      }
4698      if (!found) {
4699        return null;
4700      }
4701    }
4702    if (total <= 1) {
4703      return null;
4704    }
4705    return grp;
4706  }
4707
4708  public Set<String> getMasterSourceFileNames() {
4709    return masterSourceFileNames;
4710  }
4711
4712  public void setMasterSourceFileNames(Set<String> masterSourceFileNames) {
4713    this.masterSourceFileNames = masterSourceFileNames;
4714  }
4715
4716  
4717  public Set<String> getLocalFileNames() {
4718    return localFileNames;
4719  }
4720
4721  public void setLocalFileNames(Set<String> localFileNames) {
4722    this.localFileNames = localFileNames;
4723  }
4724
4725  public ProfileKnowledgeProvider getPkp() {
4726    return pkp;
4727  }
4728
4729
4730  public static final String UD_ERROR_STATUS = "error-status";
4731  public static final int STATUS_OK = 0;
4732  public static final int STATUS_HINT = 1;
4733  public static final int STATUS_WARNING = 2;
4734  public static final int STATUS_ERROR = 3;
4735  public static final int STATUS_FATAL = 4;
4736  private static final String ROW_COLOR_ERROR = "#ffcccc";
4737  private static final String ROW_COLOR_FATAL = "#ff9999";
4738  private static final String ROW_COLOR_WARNING = "#ffebcc";
4739  private static final String ROW_COLOR_HINT = "#ebf5ff";
4740  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
4741
4742  public String getRowColor(ElementDefinition element, boolean isConstraintMode) {
4743    switch (element.getUserInt(UD_ERROR_STATUS)) {
4744    case STATUS_HINT: return ROW_COLOR_HINT;
4745    case STATUS_WARNING: return ROW_COLOR_WARNING;
4746    case STATUS_ERROR: return ROW_COLOR_ERROR;
4747    case STATUS_FATAL: return ROW_COLOR_FATAL;
4748    }
4749    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
4750      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
4751    else
4752      return null;
4753  }
4754
4755  public static boolean isExtensionDefinition(StructureDefinition sd) {
4756    return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension");
4757  }
4758
4759  public AllowUnknownProfile getAllowUnknownProfile() {
4760    return allowUnknownProfile;
4761  }
4762
4763  public void setAllowUnknownProfile(AllowUnknownProfile allowUnknownProfile) {
4764    this.allowUnknownProfile = allowUnknownProfile;
4765  }
4766
4767  public static boolean isSimpleExtension(StructureDefinition sd) {
4768    if (!isExtensionDefinition(sd)) {
4769      return false;
4770    }
4771    ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
4772    return value != null && !value.isProhibited();
4773  }
4774
4775  public static boolean isComplexExtension(StructureDefinition sd) {
4776    if (!isExtensionDefinition(sd)) {
4777      return false;
4778    }
4779    ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
4780    return value == null || value.isProhibited();
4781  }
4782
4783  public static boolean isModifierExtension(StructureDefinition sd) {
4784    ElementDefinition defn = sd.getSnapshot().getElementByPath("Extension");
4785    return defn != null && defn.getIsModifier();
4786  }
4787
4788  public boolean isForPublication() {
4789    return forPublication;
4790  }
4791
4792  public void setForPublication(boolean forPublication) {
4793    this.forPublication = forPublication;
4794  }
4795
4796  public List<ValidationMessage> getMessages() {
4797    return messages;
4798  }
4799
4800  public static boolean isResourceBoundary(ElementDefinition ed) {
4801    return ed.getType().size() == 1 && "Resource".equals(ed.getTypeFirstRep().getCode());
4802  }
4803
4804  public static boolean isSuppressIgnorableExceptions() {
4805    return suppressIgnorableExceptions;
4806  }
4807
4808  public static void setSuppressIgnorableExceptions(boolean suppressIgnorableExceptions) {
4809    ProfileUtilities.suppressIgnorableExceptions = suppressIgnorableExceptions;
4810  }
4811
4812  public void setMessages(List<ValidationMessage> messages) {
4813    if (messages != null) {
4814      this.messages = messages;
4815      wantThrowExceptions = false;
4816    }
4817  }
4818
4819  private Map<String, List<Property>> propertyCache = new HashMap<>();
4820  
4821  public Map<String, List<Property>> getCachedPropertyList() {
4822    return propertyCache;
4823  }
4824
4825  public void checkExtensions(ElementDefinition outcome) {
4826    outcome.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));
4827    if (outcome.hasBinding()) {
4828      outcome.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));      
4829    }
4830
4831  }
4832  
4833  public static void markExtensions(ElementDefinition ed, boolean overrideSource, StructureDefinition src) {
4834    for (Extension ex : ed.getExtension()) {
4835      markExtensionSource(ex, overrideSource, src);
4836    }
4837    for (Extension ex : ed.getBinding().getExtension()) {
4838      markExtensionSource(ex, overrideSource, src);
4839    }
4840    for (TypeRefComponent t : ed.getType()) {
4841      for (Extension ex : t.getExtension()) {
4842        markExtensionSource(ex, overrideSource, src);
4843      }
4844    }
4845  }
4846
4847  public static boolean hasObligations(StructureDefinition sd) {
4848    if (sd.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE)) {
4849      return true;
4850    }
4851    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
4852      if (ed.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE)) {
4853        return true;
4854      }
4855      for (TypeRefComponent tr : ed.getType()) {
4856        if (tr.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE)) {
4857          return true;
4858        }
4859      }
4860    }
4861    return false;
4862  }
4863
4864  public List<String> getSuppressedMappings() {
4865    return suppressedMappings;
4866  }
4867
4868  public void setSuppressedMappings(List<String> suppressedMappings) {
4869    this.suppressedMappings = suppressedMappings;
4870  }
4871  
4872  public static String getCSUrl(StructureDefinition profile) {
4873    if (profile.hasExtension(ToolingExtensions.EXT_SD_CS_URL)) {
4874      return ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_SD_CS_URL);
4875    } else {
4876      return profile.getUrl()+"?codesystem";
4877    }    
4878  }
4879
4880  public static String getUrlFromCSUrl(String url) {
4881    if (url == null) {
4882      return null;
4883    }
4884    if (url.endsWith("?codesystem")) {
4885      return url.replace("?codesystem", "");
4886    } else {
4887      return null;
4888    }
4889  }
4890
4891  public FHIRPathEngine getFpe() {
4892    return fpe;
4893  }
4894
4895}