001package org.hl7.fhir.r5.profilemodel;
002
003import java.util.ArrayList;
004import java.util.Date;
005import java.util.List;
006
007import org.hl7.fhir.exceptions.FHIRException;
008import org.hl7.fhir.r5.model.Base;
009import org.hl7.fhir.r5.model.BaseDateTimeType;
010import org.hl7.fhir.r5.model.CodeableConcept;
011import org.hl7.fhir.r5.model.ContactPoint;
012import org.hl7.fhir.r5.model.Identifier;
013import org.hl7.fhir.r5.model.DataType;
014import org.hl7.fhir.r5.model.DateTimeType;
015import org.hl7.fhir.r5.model.HumanName;
016import org.hl7.fhir.r5.context.IWorkerContext;
017import org.hl7.fhir.r5.model.Address;
018import org.hl7.fhir.r5.model.PrimitiveType;
019import org.hl7.fhir.r5.model.Quantity;
020import org.hl7.fhir.r5.model.Reference;
021import org.hl7.fhir.r5.model.Resource;
022
023/**
024 * This class provides a profile centric view of a resource, as driven by a profile
025 * 
026 * This class is also suitable to be used as the base of a POJO 
027 * @author grahamegrieve
028 *
029 */
030public class PEInstance {
031
032  private PEBuilder builder;
033  private PEDefinition definition;
034  private Resource resource; // for FHIRPath
035  private Base data;
036  private String path;
037  
038  protected PEInstance(PEBuilder builder, PEDefinition definition, Resource resource, Base data, String path) {
039    super();
040    this.builder = builder;
041    this.definition = definition;
042    this.resource = resource; 
043    this.data = data;
044    this.path = path;
045  }
046  
047  /**
048   * @return definition information about this instance data 
049   */
050  public PEDefinition definition() {
051    return definition;
052  }
053  
054  /**
055   * @return the type of this element
056   */
057  public PEType type() {
058    return definition.types().get(0);
059  }
060  
061  /**
062   * @return all the children of this instance data
063   */
064  public List<PEInstance> children() {
065    List<PEInstance> res = new ArrayList<>();
066    for (PEDefinition child : definition.children()) {
067      List<Base> instances = builder.exec(resource, data, child.fhirpath());
068      int i = 0;
069      for (Base b : instances) {
070        res.add(new PEInstance(builder, child, resource, b, path+"."+child.name()+(child.repeats() ? "["+i+"]": "")));
071        i++;
072      }
073    }
074    return res;
075  }
076
077  /**
078   * @return all the single children of this instance data for the named property. An exception if there's more than one, null if there's none
079   */
080  public PEInstance child(String name) {
081    PEDefinition child = byName(definition.children(), name);
082    List<Base> instances = builder.exec(resource, data, child.fhirpath());
083    if (instances.isEmpty()) {
084      return null;
085    } else if (instances.size() == 1) {
086      return new PEInstance(builder, child, resource, instances.get(0), path+"."+child.name()+(child.repeats() ? "[0]": ""));      
087    } else {
088      throw new FHIRException("Found multiple instances for "+name+"@ "+path);
089    }
090  }
091
092  /**
093   * @return all the children of this instance data for the named property
094   */
095  public List<PEInstance> children(String name) {
096    PEDefinition child = byName(definition.children(), name);
097    List<PEInstance> res = new ArrayList<>();
098    List<Base> instances = builder.exec(resource, data, child.fhirpath());
099    int i = 0;
100    for (Base b : instances) {
101      res.add(new PEInstance(builder, child, resource, b, path+"."+child.name()+(child.repeats() ? "["+i+"]": "")));
102      i++;
103    }
104    return res;
105  }
106
107  private PEDefinition byName(List<PEDefinition> children, String name) {
108    for (PEDefinition defn : children) {
109      if (defn.name().equals(name)) {
110        return defn;
111      }
112    }
113    throw new FHIRException("No children with the name '"+name+"'");
114  }
115
116  /**
117   * @return make a child, and append it to existing children (if they exist)
118   */
119  public PEInstance makeChild(String name) {
120    PEDefinition child = byName(definition.children(), name);
121    Base b = data.addChild(child.schemaName());
122    builder.populateByProfile(b, child);
123    return new PEInstance(builder, child, resource, b, path+"."+child.name());
124  }
125  
126  /**
127   * @return get a child. if it doesn't exist, make one
128   */
129  public PEInstance forceChild(String name) {
130    PEDefinition child = byName(definition.children(), name);
131    List<Base> instances = builder.exec(resource, data, child.fhirpath());
132    if (instances.isEmpty()) {
133      Base b = data.addChild(child.schemaName());
134      builder.populateByProfile(b, child);
135      return new PEInstance(builder, child, resource, b, path+"."+child.name()+(child.isList() ? "[0]": ""));
136    } else {
137      return new PEInstance(builder, child, resource, instances.get(0), path+"."+child.name()+(child.repeats() ? "[0]": ""));
138    }
139  }
140  
141  /**
142   * remove the nominated child from the resource
143   */
144  public boolean removeChild(PEInstance child) {
145    return data.removeChild(child.definition().schemaName(), child.data);
146  }
147
148
149  public enum PEInstanceDataKind {
150    Resource, Complex, DataType, Primitive
151  }
152
153  /**
154   * @return the kind of data behind this profiled node
155   */
156  public PEInstanceDataKind getDataKind() {
157    if (data instanceof Resource) {
158      return PEInstanceDataKind.Resource;
159    }
160    if (data instanceof PrimitiveType) {
161      return PEInstanceDataKind.Primitive;
162    }
163    if (data instanceof DataType) {
164      return PEInstanceDataKind.DataType;
165    }
166    return PEInstanceDataKind.Complex;
167  }
168  
169  public Base data() {
170    return data;
171  }
172  
173  /**
174   * @return if dataKind = Resource, get the underlying resource, otherwise an exception
175   */
176  public Resource asResource() {
177    return (Resource) data;
178  }
179  
180  /**
181   * @return if dataKind = Datatype, get the underlying resource, otherwise an exception
182   */
183  public DataType asDataType() {
184    return (DataType) data;
185  }
186  
187  public CodeableConcept asCodeableConcept() {
188    return (CodeableConcept) asDataType();
189  }
190  
191  public Identifier Identifier() {
192    return (Identifier) asDataType();
193  }
194  
195  public Quantity asQuantity() {
196    return (Quantity) asDataType();
197  }
198  
199  public HumanName asHumanName() {
200    return (HumanName) asDataType();
201  }
202  
203  public Address Address() {
204    return (Address) asDataType();
205  }
206  
207  public ContactPoint asContactPoint() {
208    return (ContactPoint) asDataType();
209  }
210  
211  public Reference asReference() {
212    return (Reference) asDataType();
213  }
214  
215  
216  /**
217   * @return if dataKind = PrimitiveValue, get the underlying resource, otherwise an exception
218   * 
219   * Note that this is for e.g. String.value, not String itself
220   */
221  public String getPrimitiveAsString() {
222    return data.primitiveValue();
223  }
224  
225  public Date getPrimitiveAsDate() {
226    if (data instanceof BaseDateTimeType) {
227      return ((DateTimeType) data).getValue();
228    }
229    return null;
230  }
231  
232  public void setPrimitiveValue(String value) {
233    PrimitiveType<?> pt = (PrimitiveType<?>) data;
234    pt.setValueAsString(value);
235  }
236
237  public String getPath() {
238    return path;
239  }
240
241  public Base getBase() {
242    return data;
243  }
244
245  public boolean hasChild(String name) {
246    PEDefinition child = byName(definition.children(), name);
247    List<Base> instances = builder.exec(resource, data, child.fhirpath());
248    return !instances.isEmpty();
249  }
250  
251  public IWorkerContext getContext() {
252    return builder.getContext();
253  }
254}