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.BackboneElement; 050import org.hl7.fhir.r4.model.PrimitiveType; 051import org.hl7.fhir.r4.model.Quantity; 052import org.hl7.fhir.r4.model.Reference; 053import org.hl7.fhir.r4.model.Resource; 054import org.hl7.fhir.r4.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 Type) { 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 Type asDataType() { 226 return (Type) 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, Type 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}