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