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