001package org.hl7.fhir.r5.profilemodel;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.List;
006import java.util.Map;
007
008import org.apache.xmlbeans.impl.xb.xsdschema.All;
009import org.hl7.fhir.exceptions.DefinitionException;
010import org.hl7.fhir.r5.context.ContextUtilities;
011import org.hl7.fhir.r5.model.Base;
012import org.hl7.fhir.r5.model.ElementDefinition;
013import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
014import org.hl7.fhir.r5.model.StructureDefinition;
015import org.hl7.fhir.r5.profilemodel.PEDefinition.PEDefinitionElementMode;
016import org.hl7.fhir.utilities.Utilities;
017
018public abstract class PEDefinition {
019
020  public enum PEDefinitionElementMode {
021    Resource, Element, DataType, Extension
022  }
023
024  protected PEBuilder builder;
025  protected String name;
026  protected String path;
027  protected StructureDefinition profile;
028  protected ElementDefinition definition;
029  protected List<PEType> types;
030  protected Map<String, List<PEDefinition>> children = new HashMap<>();
031  private boolean recursing;
032  private boolean mustHaveValue;
033  private boolean inFixedValue;
034  
035//  /**
036//   * Don't create one of these directly - always use the public methods on ProfiledElementBuilder
037//   *  
038//   * @param builder
039//   * @param baseElement
040//   * @param profiledElement
041//   * @param data
042//   */
043//  protected PEDefinition(PEBuilder builder, String name, 
044//      ElementDefinition definition, Base data) {
045//    super();
046//    this.builder = builder;
047//    this.name = name;
048//    this.definition = definition;
049////    this.data = data;
050//  }
051
052  protected PEDefinition(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, String ppath) {
053    this.builder = builder;
054    this.name = name;
055    this.profile = profile;
056    this.definition = definition;
057    this.path = path == null ? name : ppath+"."+name;
058  }
059
060
061  /** 
062   * @return The name of the element or slice in the profile (always unique amongst children)
063   */
064  public String name() {
065    return name;
066  }
067
068  /** 
069   * @return The path of the element or slice in the profile (name.name.name...)
070   */
071  public String path() {
072    return path;
073  }
074
075  /**
076   * @return The name of the element in the resource (may be different to the slice name)
077   */
078  public String schemaName() {
079    return definition.getName();
080  }
081  
082  /**
083   * @return a list of types. There is usually at least one type; it might be Element, Type, BackboneElement or BackboneType
084   * 
085   * The following elements don't have types (true primitives): Element.id. Extension.url, PrimitiveType.value
086   */
087  public List<PEType> types() {
088    if (types == null) {
089      List<PEType> ltypes = new ArrayList<>();
090      listTypes(ltypes);
091      types = ltypes;
092    }
093    return types;
094  }
095  
096  protected abstract void listTypes(List<PEType> types);
097  
098  /**
099   * @return The minimum number of repeats allowed
100   */
101  public int min() {
102    return mustHaveValue ? 1 : definition.getMin();
103  }
104  
105  /**
106   * @return the maximum number of repeats allowed
107   */
108  public int max() {
109    return definition.getMax() == null || "*".equals(definition.getMax()) ? Integer.MAX_VALUE : Integer.parseInt(definition.getMax());
110  }
111  
112  /**
113   * @return the definition of the element in the profile (fully populated)
114   * 
115   * Note that the profile definition might be the same as a base definition, when the tree runs off the end of what's profiled
116   */
117  public ElementDefinition definition() {
118    return definition;
119  }
120  
121  /**
122   * @return the definition of the element in the base specification
123   * 
124   * Note that the profile definition might be the same as a base definition, when the tree runs off the end of what's profiled
125   */
126  public ElementDefinition baseDefinition() {
127    String type = definition.getBase().getPath();
128    if (type.contains(".")) {
129      type= type.substring(0, type.indexOf("."));
130    }
131    StructureDefinition sd = builder.getContext().fetchTypeDefinition(type);
132    return sd.getSnapshot().getElementByPath(definition.getBase().getPath());
133  }
134  
135  /**
136   * @return the short documentation of the definition (shown in the profile table view)
137   */
138  public String shortDocumentation() {
139    return definition.getShort();
140  }
141  
142  /**
143   * @return the full definition of the element (markdown syntax)
144   */
145  public String documentation() {
146    return definition.getDefinition();
147  }
148  
149//  /**
150//   * @return if the profiled definition has a value set, get the expansion 
151//   */
152//  public ValueSet expansion() {
153//    throw new NotImplementedException("Not done yet");
154//  }
155//  
156  /**
157   * @param typeUrl - the url of one of the types listed in types()
158   * @return - the list of children for the nominated type
159   * 
160   * Warning: profiles and resources can be recursive; you can't iterate this tree until you get 
161   * to the leaves because you will never get to a child that doesn't have children (extensions have extensions etc)
162   * 
163   */
164  public List<PEDefinition> children(String typeUrl) {
165    return children(typeUrl, false);
166  }
167  
168  public List<PEDefinition> children(String typeUrl, boolean allFixed) {
169    if (children.containsKey(typeUrl+"$"+allFixed)) {
170      return children.get(typeUrl+"$"+allFixed);      
171    } 
172    List<PEDefinition> res = new ArrayList<>();
173    makeChildren(typeUrl, res, allFixed);
174    children.put(typeUrl+"$"+allFixed, res);
175    return res;    
176  }
177  
178  public List<PEDefinition> children() {
179    if (types().size() == 1) {
180      return children(types.get(0).getUrl(), false);
181    } else {
182      throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
183    }
184  }
185  
186  public List<PEDefinition> children(boolean allFixed) {
187    if (types().size() == 1) {
188      return children(types.get(0).getUrl(), allFixed);
189    } else {
190      throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
191    }
192  }
193  
194  /**
195   * @return True if the element has a fixed value. This will always be false if fixedProps = false when the builder is created
196   */
197  public boolean fixedValue() {
198    return definition.hasFixed() || definition.hasPattern();
199  }
200  
201  protected abstract void makeChildren(String typeUrl, List<PEDefinition> children, boolean allFixed);
202
203  @Override
204  public String toString() {
205    return name+"("+schemaName()+"):"+types().toString()+" ["+min()+":"+(max() == Integer.MAX_VALUE ? "*" : max() )+"] \""+shortDocumentation()+"\"";
206  }
207
208  /**
209   * @return true if the builder observes that this element is recursing (extensions have extensions)
210   * 
211   * Note that this is unreliable and may be withdrawn if it can't be fixed
212   */
213  public boolean isRecursing() {
214    return recursing;
215  }
216
217  protected void setRecursing(boolean recursing) {
218    this.recursing = recursing;
219  }
220  
221  protected boolean isMustHaveValue() {
222    return mustHaveValue;
223  }
224
225  protected void setMustHaveValue(boolean mustHaveValue) {
226    this.mustHaveValue = mustHaveValue;
227  }
228
229  /**
230   * @return true if this property is inside an element that has an assigned fixed value
231   */
232  public boolean isInFixedValue() {
233    return inFixedValue;
234  }
235
236
237  protected void setInFixedValue(boolean inFixedValue) {
238    this.inFixedValue = inFixedValue;
239  }
240
241
242  /** 
243   * This is public to support unit testing - there's no reason to use it otherwise
244   * 
245   * @return used in the instance processor to differentiate slices
246   */
247  public abstract String fhirpath();
248
249
250  public boolean isList() {
251    return "*".equals(definition.getBase().getMax());
252  }
253
254
255  public boolean repeats() {
256    return max() > 1;
257  }
258  
259  public PEDefinitionElementMode mode() {
260    if (builder.isResource(definition.getBase().getPath())) {
261      return PEDefinitionElementMode.Resource;
262    }
263    for (TypeRefComponent tr : definition.getType()) {
264      if ("Extension".equals(tr.getWorkingCode())) {
265        return PEDefinitionElementMode.Extension;
266      }
267      if (!Utilities.existsInList(tr.getWorkingCode(), "Element", "BackboneElement")) {
268        return PEDefinitionElementMode.DataType;
269      }
270    }
271    return PEDefinitionElementMode.Element;
272  }
273
274  /**
275   * @return true if this element is profiled one way or another
276   */
277  public boolean isProfiled() {
278    return !profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition");
279  }
280
281}
282
283