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.Date;
033import java.util.List;
034
035import org.hl7.fhir.exceptions.FHIRException;
036import org.hl7.fhir.r4.model.Base;
037import org.hl7.fhir.r4.model.BaseDateTimeType;
038import org.hl7.fhir.r4.model.CodeableConcept;
039import org.hl7.fhir.r4.model.Coding;
040import org.hl7.fhir.r4.model.ContactPoint;
041import org.hl7.fhir.r4.model.Identifier;
042import org.hl7.fhir.r4.model.Type;
043import org.hl7.fhir.r4.model.DateTimeType;
044import org.hl7.fhir.r4.model.DateType;
045import org.hl7.fhir.r4.model.Extension;
046import org.hl7.fhir.r4.model.HumanName;
047import org.hl7.fhir.r4.context.IWorkerContext;
048import org.hl7.fhir.r4.model.Address;
049import org.hl7.fhir.r4.model.PrimitiveType;
050import org.hl7.fhir.r4.model.Quantity;
051import org.hl7.fhir.r4.model.Reference;
052import org.hl7.fhir.r4.model.Resource;
053import org.hl7.fhir.r4.model.StringType;
054
055/**
056 * This class provides a profile centric view of a resource, as driven by a profile
057 * 
058 * This class is also suitable to be used as the base of a POJO 
059 * @author grahamegrieve
060 *
061 */
062public class PEInstance {
063
064  private PEBuilder builder;
065  private PEDefinition definition;
066  private Resource resource; // for FHIRPath
067  private Base data;
068  private String path;
069  
070  protected PEInstance(PEBuilder builder, PEDefinition definition, Resource resource, Base data, String path) {
071    super();
072    this.builder = builder;
073    this.definition = definition;
074    this.resource = resource; 
075    this.data = data;
076    this.path = path;
077  }
078  
079  /**
080   * @return definition information about this instance data 
081   */
082  public PEDefinition definition() {
083    return definition;
084  }
085  
086  /**
087   * @return the type of this element
088   */
089  public PEType type() {
090    return definition.types().get(0);
091  }
092  
093  /**
094   * @return all the children of this instance data
095   */
096  public List<PEInstance> children() {
097    List<PEInstance> res = new ArrayList<>();
098    for (PEDefinition child : definition.children()) {
099      List<Base> instances = builder.exec(resource, data, child.fhirpath());
100      int i = 0;
101      for (Base b : instances) {
102        res.add(new PEInstance(builder, child, resource, b, path+"."+child.name()+(child.repeats() ? "["+i+"]": "")));
103        i++;
104      }
105    }
106    return res;
107  }
108
109  /**
110   * @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
111   */
112  public PEInstance child(String name) {
113    PEDefinition child = byName(definition.children(), name);
114    List<Base> instances = builder.exec(resource, data, child.fhirpath());
115    if (instances.isEmpty()) {
116      return null;
117    } else if (instances.size() == 1) {
118      return new PEInstance(builder, child, resource, instances.get(0), path+"."+child.name()+(child.repeats() ? "[0]": ""));      
119    } else {
120      throw new FHIRException("Found multiple instances for "+name+"@ "+path);
121    }
122  }
123
124  /**
125   * @return all the children of this instance data for the named property
126   */
127  public List<PEInstance> children(String name) {
128    PEDefinition child = byName(definition.children(), name);
129    List<PEInstance> res = new ArrayList<>();
130    List<Base> instances = builder.exec(resource, data, child.fhirpath());
131    int i = 0;
132    for (Base b : instances) {
133      res.add(new PEInstance(builder, child, resource, b, path+"."+child.name()+(child.repeats() ? "["+i+"]": "")));
134      i++;
135    }
136    return res;
137  }
138
139  private PEDefinition byName(List<PEDefinition> children, String name) {
140    for (PEDefinition defn : children) {
141      if (defn.name().equals(name)) {
142        return defn;
143      }
144      if (defn.name().equals(name+"[x]")) {
145        return defn;
146      }
147    }
148    throw new FHIRException("No children with the name '"+name+"'");
149  }
150
151  /**
152   * @return make a child, and append it to existing children (if they exist)
153   */
154  public PEInstance makeChild(String name) {
155    PEDefinition child = byName(definition.children(), name);
156    Base b = child.isBaseList() || !child.isBasePrimitive() ? data.addChild(child.schemaNameWithType()) : data.makeProperty(child.schemaNameWithType().hashCode(), child.schemaNameWithType());
157    builder.populateByProfile(b, child);
158    return new PEInstance(builder, child, resource, b, path+"."+child.name());
159  }
160  
161  /**
162   * @return get a child. if it doesn't exist, make one
163   */
164  public PEInstance forceChild(String name) {
165    PEDefinition child = byName(definition.children(), name);
166    List<Base> instances = builder.exec(resource, data, child.fhirpath());
167    if (instances.isEmpty()) {
168      Base b = data.addChild(child.schemaName());
169      builder.populateByProfile(b, child);
170      return new PEInstance(builder, child, resource, b, path+"."+child.name()+(child.isList() ? "[0]": ""));
171    } else {
172      return new PEInstance(builder, child, resource, instances.get(0), path+"."+child.name()+(child.repeats() ? "[0]": ""));
173    }
174  }
175  
176  /**
177   * remove the nominated child from the resource
178   */
179  public void removeChild(PEInstance child) {
180    data.removeChild(child.definition().schemaName(), child.data);
181  }
182
183  public void clear(String name) {
184    List<PEInstance> children = children(name);
185    for (PEInstance child : children) {
186      removeChild(child);
187    }
188  }
189
190  public enum PEInstanceDataKind {
191    Resource, Complex, DataType, Primitive
192  }
193
194  /**
195   * @return the kind of data behind this profiled node
196   */
197  public PEInstanceDataKind getDataKind() {
198    if (data instanceof Resource) {
199      return PEInstanceDataKind.Resource;
200    }
201    if (data instanceof PrimitiveType) {
202      return PEInstanceDataKind.Primitive;
203    }
204    if (data instanceof Type) {
205      return PEInstanceDataKind.DataType;
206    }
207    return PEInstanceDataKind.Complex;
208  }
209  
210  public Base data() {
211    return data;
212  }
213  
214  /**
215   * @return if dataKind = Resource, get the underlying resource, otherwise an exception
216   */
217  public Resource asResource() {
218    return (Resource) data;
219  }
220  
221  /**
222   * @return if dataKind = Datatype, get the underlying resource, otherwise an exception
223   */
224  public Type asDataType() {
225    return (Type) data;
226  }
227  
228  public CodeableConcept asCodeableConcept() {
229    return (CodeableConcept) asDataType();
230  }
231  
232  public Identifier Identifier() {
233    return (Identifier) asDataType();
234  }
235  
236  public Quantity asQuantity() {
237    return (Quantity) asDataType();
238  }
239  
240  public HumanName asHumanName() {
241    return (HumanName) asDataType();
242  }
243  
244  public Address Address() {
245    return (Address) asDataType();
246  }
247  
248  public ContactPoint asContactPoint() {
249    return (ContactPoint) asDataType();
250  }
251  
252  public Reference asReference() {
253    return (Reference) asDataType();
254  }
255  
256  
257  /**
258   * @return if dataKind = PrimitiveValue, get the underlying resource, otherwise an exception
259   * 
260   * Note that this is for e.g. String.value, not String itself
261   */
262  public String getPrimitiveAsString() {
263    return data.primitiveValue();
264  }
265  
266  public Date getPrimitiveAsDate() {
267    if (data instanceof BaseDateTimeType) {
268      return ((DateTimeType) data).getValue();
269    }
270    return null;
271  }
272  
273  public void setPrimitiveValue(String value) {
274    PrimitiveType<?> pt = (PrimitiveType<?>) data;
275    pt.setValueAsString(value);
276  }
277
278  public String getPath() {
279    return path;
280  }
281
282  public Base getBase() {
283    return data;
284  }
285
286  public boolean hasChild(String name) {
287    PEDefinition child = byName(definition.children(), name);
288    List<Base> instances = builder.exec(resource, data, child.fhirpath());
289    return !instances.isEmpty();
290  }
291  
292  public IWorkerContext getContext() {
293    return builder.getContext();
294  }
295
296  public void addChild(String name, Type value) {
297      PEDefinition child = byName(definition.children(), name);
298      Base b = data.setProperty(child.schemaName(), value);
299  }
300  
301  public void addChild(String name, String value) {
302    PEDefinition child = byName(definition.children(), name);
303    Base b = data.setProperty(child.schemaName(), new StringType(value));
304  }
305
306  public void addChild(String name, Date value) {
307    PEDefinition child = byName(definition.children(), name);
308    Base b = data.setProperty(child.schemaName(), new DateType(value));
309  }
310}