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