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