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