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