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