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}