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