001package org.hl7.fhir.r4.profilemodel;
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
031import java.util.ArrayList;
032import java.util.HashSet;
033import java.util.List;
034import java.util.Set;
035
036import org.apache.commons.lang3.NotImplementedException;
037import org.hl7.fhir.exceptions.DefinitionException;
038import org.hl7.fhir.exceptions.FHIRException;
039import org.hl7.fhir.r4.conformance.ProfileUtilities;
040import org.hl7.fhir.r4.context.ContextUtilities;
041import org.hl7.fhir.r4.context.IWorkerContext;
042import org.hl7.fhir.r4.fhirpath.FHIRPathEngine;
043import org.hl7.fhir.r4.model.Base;
044import org.hl7.fhir.r4.model.CanonicalType;
045import org.hl7.fhir.r4.model.ElementDefinition;
046import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType;
047import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent;
048import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
049import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules;
050import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
051import org.hl7.fhir.r4.model.Enumerations.BindingStrength;
052import org.hl7.fhir.r4.model.Resource;
053import org.hl7.fhir.r4.model.ResourceFactory;
054import org.hl7.fhir.r4.model.StructureDefinition;
055import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
056import org.hl7.fhir.r4.model.ValueSet;
057import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
058import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
059import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
060import org.hl7.fhir.utilities.Utilities;
061
062/**
063 * Factory class for the ProfiledElement sub-system
064 * 
065 * *** NOTE: This sub-system is still under development ***
066 * 
067 * This subsystem takes a profile and creates a view of the profile that stitches
068 * all the parts together, and presents it as a seamless tree. There's two views:
069 * 
070 *  - definition: A logical view of the contents of the profile 
071 *  - instance: a logical view of a resource that conforms to the profile
072 *  
073 * The tree of elements in the profile model is different to the the base resource:
074 *  - some elements are removed (max = 0)
075 *  - extensions are turned into named elements 
076 *  - slices are turned into named elements 
077 *  - element properties - doco, cardinality, binding etc is updated for what the profile says
078 * 
079 * Definition
080 * ----------
081 * This presents a single view of the contents of a resource as specified by 
082 * the profile. It's suitable for use in any kind of tree view. 
083 * 
084 * Each node has a unique name amongst it's siblings, but this name may not be 
085 * the name in the instance, since slicing splits up a single named element into 
086 * different definitions.
087 * 
088 * Each node has:
089 *   - name (unique amongst siblings)
090 *   - schema name (the actual name in the instance)
091 *   - min cardinality 
092 *   - max cardinality 
093 *   - short documentation (for the tree view)
094 *   - full documentation (markdown source)
095 *   - profile definition - the full definition in the profile
096 *   - base definition - the full definition at the resource level
097 *   - types() - a list of possible types
098 *   - children(type) - a list of child nodes for the provided type 
099 *   - expansion - if there's a binding, the codes in the expansion based on the binding
100 *   
101 * Note that the tree may not have leaves; the trees recurse indefinitely because 
102 * extensions have extensions etc. So you can't do a depth-first search of the tree
103 * without some kind of decision to stop at a given point. 
104 * 
105 * Instance
106 * --------
107 * 
108 * todo
109 * 
110 * @author grahamegrieve
111 *
112 */
113public class PEBuilder {
114
115  public enum PEElementPropertiesPolicy {
116    NONE, EXTENSION, EXTENSION_ID
117  }
118
119  private IWorkerContext context;
120  private ProfileUtilities pu;
121  private ContextUtilities cu;
122  private PEElementPropertiesPolicy elementProps;
123  private boolean fixedPropsDefault;
124  private FHIRPathEngine fpe;
125
126  /**
127   * @param context - must be loaded with R5 definitions
128   * @param elementProps - whether to include Element.id and Element.extension in the tree. Recommended choice: Extension
129   */
130  public PEBuilder(IWorkerContext context, PEElementPropertiesPolicy elementProps, boolean fixedPropsDefault) {
131    super();
132    this.context = context;
133    this.elementProps = elementProps;
134    this.fixedPropsDefault = fixedPropsDefault;
135    pu = new ProfileUtilities(context, null, null);
136    cu = new ContextUtilities(context);
137    fpe = new FHIRPathEngine(context, pu);
138  }
139  
140  /**
141   * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model
142   * for the provided version of the nominated profile
143   * 
144   * The tree of elements in the profile model is different to those defined in the base resource:
145   *  - some elements are removed (max = 0)
146   *  - extensions are turned into named elements 
147   *  - slices are turned into named elements 
148   *  - element properties - doco, cardinality, binding etc is updated for what the profile says
149   * 
150   * Warning: profiles and resources are recursive; you can't iterate this tree until it you get 
151   * to the leaves because there are nodes that don't terminate (extensions have extensions)
152   * 
153   */
154  public PEDefinition buildPEDefinition(StructureDefinition profile) {
155    if (!profile.hasSnapshot()) {
156      throw new DefinitionException("Profile '"+profile.getVersionedUrl()+"' does not have a snapshot");      
157    }
158    return new PEDefinitionResource(this, profile, null);
159  }
160  
161  /**
162   * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model
163   * for the latest version of the nominated profile
164   * 
165   * The tree of elements in the profile model is different to those defined in the base resource:
166   *  - some elements are removed (max = 0)
167   *  - extensions are turned into named elements 
168   *  - slices are turned into named elements 
169   *  - element properties - doco, cardinality, binding etc is updated for what the profile says
170   * 
171   * Warning: profiles and resources are recursive; you can't iterate this tree until it you get 
172   * to the leaves because there are nodes that don't terminate (extensions have extensions)
173   * 
174   */
175  public PEDefinition buildPEDefinition(String url) {
176    StructureDefinition profile = getProfile(url);
177    if (profile == null) {
178      throw new DefinitionException("Unable to find profile for URL '"+url+"'");
179    }
180    if (!profile.hasSnapshot()) {
181      throw new DefinitionException("Profile '"+url+"' does not have a snapshot");      
182    }
183    return new PEDefinitionResource(this, profile, profile.getName());
184  }
185  
186  /**
187   * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model
188   * for the nominated version of the nominated profile
189   * 
190   * The tree of elements in the profile model is different to the the base resource:
191   *  - some elements are removed (max = 0)
192   *  - extensions are turned into named elements 
193   *  - slices are turned into named elements 
194   *  - element properties - doco, cardinality, binding etc is updated for what the profile says
195   * 
196   * Warning: profiles and resources can be recursive; you can't iterate this tree until it you get 
197   * to the leaves because you will never get to a child that doesn't have children
198   * 
199   */
200  public PEDefinition buildPEDefinition(String url, String version) {
201    StructureDefinition profile = getProfile(url, version);
202    if (profile == null) {
203      throw new DefinitionException("Unable to find profile for URL '"+url+"'");
204    }
205    if (!profile.hasSnapshot()) {
206      throw new DefinitionException("Profile '"+url+"' does not have a snapshot");      
207    }
208    return new PEDefinitionResource(this, profile, profile.getName());
209  }
210  
211  /**
212   * Given a resource and a profile, return a tree of instance data as defined by the profile model 
213   * using the latest version of the profile
214   * 
215   * The tree is a facade to the underlying resource - all actual data is stored against the resource,
216   * and retrieved on the fly from the resource, so that applications can work at either level, as 
217   * convenient. 
218   * 
219   * Note that there's a risk that deleting something through the resource while holding 
220   * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade 
221   * that will continue to function, but is making changes to resource content that is no 
222   * longer part of the resource 
223   * 
224   */
225  public PEInstance buildPEInstance(String url, Resource resource) {
226    PEDefinition defn = buildPEDefinition(url);
227    return loadInstance(defn, resource);
228  }
229  
230  /**
231   * Given a resource and a profile, return a tree of instance data as defined by the profile model 
232   * using the provided version of the profile
233   * 
234   * The tree is a facade to the underlying resource - all actual data is stored against the resource,
235   * and retrieved on the fly from the resource, so that applications can work at either level, as 
236   * convenient. 
237   * 
238   * Note that there's a risk that deleting something through the resource while holding 
239   * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade 
240   * that will continue to function, but is making changes to resource content that is no 
241   * longer part of the resource 
242   * 
243   */
244  public PEInstance buildPEInstance(StructureDefinition profile, Resource resource) {
245    PEDefinition defn = buildPEDefinition(profile);
246    return loadInstance(defn, resource);
247  }
248  
249  /**
250   * Given a resource and a profile, return a tree of instance data as defined by the profile model 
251   * using the nominated version of the profile
252   * 
253   * The tree is a facade to the underlying resource - all actual data is stored against the resource,
254   * and retrieved on the fly from the resource, so that applications can work at either level, as 
255   * convenient. 
256   * 
257   * Note that there's a risk that deleting something through the resource while holding 
258   * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade 
259   * that will continue to function, but is making changes to resource content that is no 
260   * longer part of the resource 
261   */
262  public PEInstance buildPEInstance(String url, String version, Resource resource) {
263    PEDefinition defn = buildPEDefinition(url, version);
264    return loadInstance(defn, resource);
265  }
266  
267  /**
268   * For the current version of a profile, construct a resource and fill out any fixed or required elements
269   * 
270   * Note that fixed values are filled out irrespective of the value of fixedProps when the builder is created
271   * 
272   * @param url identifies the profile
273   * @param version identifies the version of the profile
274   * @param meta whether to mark the profile in Resource.meta.profile 
275   * @return constructed resource
276   */
277  public Resource createResource(String url, String version, boolean meta) {
278    PEDefinition definition = buildPEDefinition(url, version);
279    Resource res = ResourceFactory.createResource(definition.types().get(0).getType());
280    populateByProfile(res, definition);
281    if (meta) {
282      res.getMeta().addProfile(definition.profile.getUrl());
283    }
284    return res;
285  }
286
287  /**
288   * For the provided version of a profile, construct a resource and fill out any fixed or required elements
289   * 
290   * Note that fixed values are filled out irrespective of the value of fixedProps when the builder is created
291   * 
292   * @param profile  the profile
293   * @param meta whether to mark the profile in Resource.meta.profile 
294   * @return constructed resource
295   */
296  public Resource createResource(StructureDefinition profile, boolean meta) {
297    PEDefinition definition = buildPEDefinition(profile);
298    Resource res = ResourceFactory.createResource(definition.types().get(0).getType());
299    populateByProfile(res, definition);
300    if (meta) {
301      res.getMeta().addProfile(definition.profile.getUrl());
302    }
303    return res;
304  }
305
306  /**
307   * For the current version of a profile, construct a resource and fill out any fixed or required elements
308   * 
309   * Note that fixed values are filled out irrespective of the value of fixedProps when the builder is created
310   * 
311   * @param url identifies the profile
312   * @param meta whether to mark the profile in Resource.meta.profile 
313   * @return constructed resource
314   */
315  public Resource createResource(String url, boolean meta) {
316    PEDefinition definition = buildPEDefinition(url);
317    Resource res = ResourceFactory.createResource(definition.types().get(0).getType());
318    populateByProfile(res, definition);
319    if (meta) {
320      res.getMeta().addProfile(definition.profile.getUrl());
321    }
322    return res;
323  }
324
325
326
327  // -- methods below here are only used internally to the package
328
329  private StructureDefinition getProfile(String url) {
330    return context.fetchResource(StructureDefinition.class, url);
331  }
332
333
334  private StructureDefinition getProfile(String url, String version) {
335    return context.fetchResource(StructureDefinition.class, url, version);
336  }
337//
338//  protected List<PEDefinition> listChildren(boolean allFixed, StructureDefinition profileStructure, ElementDefinition definition, TypeRefComponent t, CanonicalType u) {
339//    // TODO Auto-generated method stub
340//    return null;
341//  }
342
343  protected List<PEDefinition> listChildren(boolean allFixed, PEDefinition parent, StructureDefinition profileStructure, ElementDefinition definition, String url, String... omitList) {
344    StructureDefinition profile = profileStructure;
345    boolean inExtension = profile.getDerivation() == TypeDerivationRule.CONSTRAINT && "Extension".equals(profile.getType());
346    List<ElementDefinition> list = pu.getChildList(profile, definition);
347    if (definition.getType().size() == 1 || (!definition.getPath().contains(".")) || list.isEmpty()) {
348      assert url == null || checkType(definition, url);
349      List<PEDefinition> res = new ArrayList<>();
350      if (list.size() == 0) {
351        profile = context.fetchResource(StructureDefinition.class, url);
352        if (profile == null) {
353          throw new FHIRException("Unable to resolve profile "+url);
354        }
355        list = pu.getChildList(profile, profile.getSnapshot().getElementFirstRep());
356      }
357      if (list.size() > 0) {
358        Set<String> names = new HashSet<>();
359        int i = 0;
360        while (i < list.size()) {
361          ElementDefinition defn = list.get(i);
362          if (!defn.getMax().equals("0") && (allFixed || include(defn))) {
363            if (passElementPropsCheck(defn, inExtension) && !Utilities.existsInList(defn.getName(), omitList)) {
364              String name = uniquefy(names, defn.getName());
365              PEDefinitionElement pe = new PEDefinitionElement(this, name, profile, defn, parent.path());
366              pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
367              if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
368                pe.setMustHaveValue(definition.getMustHaveValue());
369              }
370              pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
371              if (defn.hasSlicing()) {
372                if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
373                  res.add(pe);
374                  pe.setSlicer(true);
375                }
376                i++;
377                while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
378                  StructureDefinition ext = getExtensionDefinition(list.get(i));
379                  if (ext != null) {
380                    res.add(new PEDefinitionExtension(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, ext, parent.path()));
381                  } else if (isTypeSlicing(defn)) {
382                    res.add(new PEDefinitionTypeSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
383                  } else {
384                    if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
385                      res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
386                    } else {
387                      res.add(new PEDefinitionSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
388                    }
389                  }
390                  i++;
391                }
392              } else {
393                res.add(pe);
394                i++;
395              }
396            } else {
397              i++;
398            } 
399          } else {
400            i++;
401          }
402        }
403      }
404      return res;
405    } else if (list.isEmpty()) {
406      throw new DefinitionException("not done yet!");
407    } else {
408      throw new DefinitionException("not done yet");
409    }
410  }
411
412  private String uniquefy(Set<String> names, String name) {
413    if (names.contains(name)) {
414      int i = 0;
415      while (names.contains(name+i)) {
416        i++;
417      }
418      name = name+i;
419    }
420    names.add(name);
421    return name;
422  }
423
424  protected PEDefinition makeChild(PEDefinition parent, StructureDefinition profileStructure, ElementDefinition definition) {
425    PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, definition, parent.path());
426    if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
427      pe.setMustHaveValue(definition.getMustHaveValue());
428    }
429    pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
430    return pe;
431  }
432
433  private boolean passElementPropsCheck(ElementDefinition bdefn, boolean inExtension) {
434    if (inExtension) {
435      return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id");      
436    }
437    switch (elementProps) {
438    case EXTENSION:
439      return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id");
440    case NONE:
441      return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id", "Element.extension");
442    case EXTENSION_ID:
443    default:
444      return true;
445    }
446  }
447
448  private boolean isTypeSlicing(ElementDefinition defn) {
449    ElementDefinitionSlicingComponent sl = defn.getSlicing();
450    return sl.getRules() == SlicingRules.CLOSED && sl.getDiscriminator().size() == 1 &&
451        sl.getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE && "$this".equals(sl.getDiscriminatorFirstRep().getPath());
452  }
453
454  private boolean include(ElementDefinition defn) {
455    if (fixedPropsDefault) { 
456      return true;
457    } else { 
458      return !(defn.hasFixed() || defn.hasPattern());
459    }
460  }
461
462  protected List<PEDefinition> listSlices(StructureDefinition profileStructure, ElementDefinition definition, PEDefinition parent) {
463    List<ElementDefinition> list = pu.getSliceList(profileStructure, definition);
464    List<PEDefinition> res = new ArrayList<>();
465    for (ElementDefinition ed : list) {
466      if (profileStructure.getDerivation() == TypeDerivationRule.CONSTRAINT && profileStructure.getType().equals("Extension")) {
467        res.add(new PEDefinitionSubExtension(this, profileStructure, ed, parent.path()));
468      } else {
469        PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, ed, parent.path());
470        pe.setRecursing(definition == ed || (profileStructure.getDerivation() == TypeDerivationRule.SPECIALIZATION && profileStructure.getType().equals("Extension")));
471        res.add(pe);
472      }
473    }
474    return res;
475  }
476
477
478  private boolean checkType(ElementDefinition defn, String url) {
479    for (TypeRefComponent t : defn.getType()) {
480      if (("http://hl7.org/fhir/StructureDefinition/"+t.getWorkingCode()).equals(url)) {
481        return true;
482      }
483      for (CanonicalType u : t.getProfile()) {
484        if (url.equals(u.getValue())) {
485          return true;
486        }
487      }
488    }
489    return !defn.getPath().contains(".");
490  }
491
492
493  private StructureDefinition getExtensionDefinition(ElementDefinition ed) {
494    if ("Extension".equals(ed.getTypeFirstRep().getWorkingCode()) && ed.getTypeFirstRep().getProfile().size() == 1) {
495      return context.fetchResource(StructureDefinition.class, ed.getTypeFirstRep().getProfile().get(0).asStringValue());
496    } else {
497      return null;
498    }
499  }
500
501
502  private ElementDefinition getByName(List<ElementDefinition> blist, String name) {
503    for (ElementDefinition ed : blist) {
504      if (name.equals(ed.getName())) {
505        return ed;
506      }
507    }
508    return null;
509  }
510
511
512  protected PEType makeType(TypeRefComponent t) {
513    if (t.hasProfile()) {
514      StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue());
515      if (sd == null) {
516        return new PEType(tail(t.getProfile().get(0).getValue()), t.getWorkingCode(), t.getProfile().get(0).getValue());
517      } else {
518        return new PEType(sd.getName(), t.getWorkingCode(), t.getProfile().get(0).getValue());
519      }
520    } else {
521      return makeType(t.getWorkingCode());
522    } 
523  }
524
525  protected PEType makeType(TypeRefComponent t, CanonicalType u) {
526    StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue());
527    if (sd == null) {
528      return new PEType(tail(u.getValue()), t.getWorkingCode(), u.getValue());
529    } else {
530      return new PEType(sd.getName(), t.getWorkingCode(), u.getValue());
531    }
532  }
533
534
535  protected PEType makeType(String tn, String url) {
536    return new PEType(tn, tn, url);
537  }
538  
539  protected PEType makeType(String tn) {
540    return new PEType(tn, tn, "http://hl7.org/fhir/StructureDefinition/"+ tn);
541  }
542
543  private String tail(String value) {
544    return value.contains("/") ? value.substring(value.lastIndexOf("/")+1) : value;
545  }
546
547  protected List<ElementDefinition> getChildren(StructureDefinition profileStructure, ElementDefinition definition) {
548    return pu.getChildList(profileStructure, definition);
549  }
550
551  private PEInstance loadInstance(PEDefinition defn, Resource resource) {
552    return new PEInstance(this, defn, resource, resource, defn.name());
553  }
554
555  public IWorkerContext getContext() {
556    return context;
557  }
558
559  protected void populateByProfile(Base base, PEDefinition definition) {
560    if (definition.types().size() == 1) {
561      for (PEDefinition pe : definition.directChildren(true)) {
562        if (pe.hasFixedValue()) {
563          if (pe.definition().hasPattern()) {
564            base.setProperty(pe.schemaName(), pe.definition().getPattern());
565          } else { 
566            base.setProperty(pe.schemaName(), pe.definition().getFixed());
567          }
568        } else if (!pe.isSlicer() && pe.max() == 1) {
569          for (int i = 0; i < pe.min(); i++) {
570            Base b = null;
571            if (pe.schemaName().endsWith("[x]")) {
572              if (pe.types().size() == 1) {
573                b = base.addChild(pe.schemaName().replace("[x]", Utilities.capitalize(pe.types().get(0).getType())));
574              }
575            } else if (!pe.isBaseList()) {
576              b = base.makeProperty(pe.schemaName().hashCode(), pe.schemaName());
577            } else {
578              b = base.addChild(pe.schemaName());
579            }
580            if (b != null) {
581              populateByProfile(b, pe);
582            }
583          }
584        }
585      }
586    }
587  }
588
589  public String makeSliceExpression(StructureDefinition profile, ElementDefinitionSlicingComponent slicing, ElementDefinition definition) {
590    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" and ");
591    for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) {
592      switch (d.getType()) {
593      case EXISTS:
594        throw new DefinitionException("The discriminator type 'exists' is not supported by the PEBuilder");
595      case PATTERN:
596        throw new DefinitionException("The discriminator type 'pattern' is not supported by the PEBuilder");
597      case PROFILE:
598        throw new DefinitionException("The discriminator type 'profile' is not supported by the PEBuilder");
599      case TYPE:
600        throw new DefinitionException("The discriminator type 'type' is not supported by the PEBuilder");
601      case VALUE:
602        String path = d.getPath();
603        ElementDefinition ed = getChildElement(profile, definition, path);
604        if (ed == null) {
605          throw new DefinitionException("The discriminator path '"+path+"' could not be resolved by the PEBuilder");          
606        }
607        // three things possible:
608        //  fixed, pattern, or binding
609        if (ed.hasFixed()) {
610          if (!ed.getFixed().isPrimitive()) {
611            throw new DefinitionException("The discriminator path '"+path+"' has a fixed value that is not a primitive ("+ed.getFixed().fhirType()+") - this is not supported by the PEBuilder");          
612          }
613          b.append(path+" = '"+ed.getFixed().primitiveValue()+"'");
614        } else if (ed.hasPattern()) {
615          throw new DefinitionException("The discriminator path '"+path+"' has a pattern on the element '"+ed.getId()+"' - this is not supported by the PEBuilder");
616        } else if (ed.hasBinding()) {
617          if (ed.getBinding().getStrength() != BindingStrength.REQUIRED) {
618            throw new DefinitionException("The discriminator path '"+path+"' has a binding on the element '"+ed.getId()+"' but the strength is not required - this is not supported by the PEBuilder");
619          } else {
620            ValueSet vs = context.fetchResource(ValueSet.class, ed.getBinding().getValueSet());
621            if (vs == null) {
622              throw new DefinitionException("The discriminator path '"+path+"' has a binding on the element '"+ed.getId()+"' but the valueSet '"+ed.getBinding().getValueSet()+"' is not known - this is not supported by the PEBuilder");              
623            }
624            ValueSetExpansionOutcome exp = context.expandVS(vs, true, false);
625            if (exp.isOk()) {
626              CommaSeparatedStringBuilder bs = new CommaSeparatedStringBuilder(" | ");
627              for (ValueSetExpansionContainsComponent cc : exp.getValueset().getExpansion().getContains()) {
628                bs.append("'"+cc.getCode()+"'");
629              }
630              b.append(path+" in ("+bs.toString()+")");
631            } else {
632              throw new DefinitionException("The discriminator path '"+path+"' has a binding on the element '"+ed.getId()+"' but the valueSet '"+ed.getBinding().getValueSet()+"' could not be expanded: "+exp.getError());                            
633            }            
634          }
635        } else {
636          throw new DefinitionException("The discriminator path '"+path+"' has no fixed or pattern value or a binding on the element '"+ed.getId()+"' - this is not supported by the PEBuilder");          
637          
638        }
639        break;
640      case NULL:
641        throw new DefinitionException("The discriminator type 'null' is not supported by the PEBuilder");
642      default:
643        throw new DefinitionException("The discriminator type '??' is not supported by the PEBuilder"); 
644      }
645    }
646    return b.toString();
647  }
648
649  private ElementDefinition getChildElement(StructureDefinition profile, ElementDefinition definition, String path) {
650    String head = path.contains(".") ? path.substring(0, path.indexOf(".")) : path;
651    String tail = path.contains(".") ? path.substring(path.indexOf(".")+1) : null;
652    ElementDefinition focus = definition;
653    
654    do {
655      List<ElementDefinition> elements = pu.getChildList(profile, focus);
656      if (elements.size() == 0) {
657        profile = focus.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, focus.getTypeFirstRep().getProfile().get(0).asStringValue()) :
658          context.fetchTypeDefinition(focus.getTypeFirstRep().getWorkingCode());
659        elements = pu.getChildList(profile, profile.getSnapshot().getElementFirstRep());
660      }
661      focus = getByName(elements, head);
662      if (tail != null) {
663        head = tail.contains(".") ? tail.substring(0, tail.indexOf(".")) : tail;
664        tail = tail.contains(".") ? tail.substring(tail.indexOf(".")+1) : null;
665      } else {
666        head = null;
667      }
668    } while (head != null && focus != null);
669    return focus;
670  }
671
672  public List<Base> exec(Resource resource, Base data, String fhirpath) {
673    return fpe.evaluate(this, resource, resource, data, fhirpath);
674  }
675
676  public boolean isResource(String name) {
677    return cu.isResource(name);
678  }
679}