001package org.hl7.fhir.r4.elementmodel; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import org.hl7.fhir.r4.conformance.ProfileUtilities; 007import org.hl7.fhir.r4.context.IWorkerContext; 008import org.hl7.fhir.r4.formats.FormatUtilities; 009import org.hl7.fhir.r4.model.ElementDefinition; 010import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation; 011import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 012import org.hl7.fhir.r4.model.StructureDefinition; 013import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 014import org.hl7.fhir.r4.model.TypeDetails; 015import org.hl7.fhir.r4.utils.ToolingExtensions; 016import org.hl7.fhir.r4.utils.TypesUtilities; 017import org.hl7.fhir.utilities.Utilities; 018import org.apache.commons.lang3.StringUtils; 019import org.hl7.fhir.exceptions.DefinitionException; 020import org.hl7.fhir.exceptions.FHIRException; 021 022public class Property { 023 024 private IWorkerContext context; 025 private ElementDefinition definition; 026 private StructureDefinition structure; 027 private Boolean canBePrimitive; 028 029 public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure) { 030 this.context = context; 031 this.definition = definition; 032 this.structure = structure; 033 } 034 035 public String getName() { 036 return definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 037 } 038 039 public ElementDefinition getDefinition() { 040 return definition; 041 } 042 043 public String getType() { 044 if (definition.getType().size() == 0) 045 return null; 046 else if (definition.getType().size() > 1) { 047 String tn = definition.getType().get(0).getCode(); 048 for (int i = 1; i < definition.getType().size(); i++) { 049 if (!tn.equals(definition.getType().get(i).getCode())) 050 throw new Error("logic error, gettype when types > 1"); 051 } 052 return tn; 053 } else 054 return definition.getType().get(0).getCode(); 055 } 056 057 public String getType(String elementName) { 058 if (!definition.getPath().contains(".")) 059 return definition.getPath(); 060 ElementDefinition ed = definition; 061 if (definition.hasContentReference()) { 062 if (!definition.getContentReference().startsWith("#")) 063 throw new Error("not handled yet"); 064 boolean found = false; 065 for (ElementDefinition d : structure.getSnapshot().getElement()) { 066 if (d.hasId() && d.getId().equals(definition.getContentReference().substring(1))) { 067 found = true; 068 ed = d; 069 } 070 } 071 if (!found) 072 throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+structure.getUrl()); 073 } 074 if (ed.getType().size() == 0) 075 return null; 076 else if (ed.getType().size() > 1) { 077 String t = ed.getType().get(0).getCode(); 078 boolean all = true; 079 for (TypeRefComponent tr : ed.getType()) { 080 if (!t.equals(tr.getCode())) 081 all = false; 082 } 083 if (all) 084 return t; 085 String tail = ed.getPath().substring(ed.getPath().lastIndexOf(".")+1); 086 if (tail.endsWith("[x]") && elementName != null && elementName.startsWith(tail.substring(0, tail.length()-3))) { 087 String name = elementName.substring(tail.length()-3); 088 return isPrimitive(lowFirst(name)) ? lowFirst(name) : name; 089 } else 090 throw new Error("logic error, gettype when types > 1, name mismatch for "+elementName+" on at "+ed.getPath()); 091 } else if (ed.getType().get(0).getCode() == null) { 092 if (Utilities.existsInList(ed.getId(), "Element.id", "Extension.url")) 093 return "string"; 094 else 095 return structure.getId(); 096 } else 097 return ed.getType().get(0).getCode(); 098 } 099 100 public boolean hasType(String elementName) { 101 if (definition.getType().size() == 0) 102 return false; 103 else if (definition.getType().size() > 1) { 104 String t = definition.getType().get(0).getCode(); 105 boolean all = true; 106 for (TypeRefComponent tr : definition.getType()) { 107 if (!t.equals(tr.getCode())) 108 all = false; 109 } 110 if (all) 111 return true; 112 String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 113 if (tail.endsWith("[x]") && elementName.startsWith(tail.substring(0, tail.length()-3))) { 114 String name = elementName.substring(tail.length()-3); 115 return true; 116 } else 117 return false; 118 } else 119 return true; 120 } 121 122 public StructureDefinition getStructure() { 123 return structure; 124 } 125 126 /** 127 * Is the given name a primitive 128 * 129 * @param E.g. "Observation.status" 130 */ 131 public boolean isPrimitiveName(String name) { 132 String code = getType(name); 133 return isPrimitive(code); 134 } 135 136 /** 137 * Is the given type a primitive 138 * 139 * @param E.g. "integer" 140 */ 141 public boolean isPrimitive(String code) { 142 return TypesUtilities.isPrimitive(code); 143 // was this... but this can be very inefficient compared to hard coding the list 144// StructureDefinition sd = context.fetchTypeDefinition(code); 145// return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 146 } 147 148 private String lowFirst(String t) { 149 return t.substring(0, 1).toLowerCase()+t.substring(1); 150 } 151 152 public boolean isResource() { 153 if (definition.getType().size() > 0) 154 return definition.getType().size() == 1 && ("Resource".equals(definition.getType().get(0).getCode()) || "DomainResource".equals(definition.getType().get(0).getCode())); 155 else 156 return !definition.getPath().contains(".") && structure.getKind() == StructureDefinitionKind.RESOURCE; 157 } 158 159 public boolean isList() { 160 return !"1".equals(definition.getMax()); 161 } 162 163 public String getScopedPropertyName() { 164 return definition.getBase().getPath(); 165 } 166 167 public String getNamespace() { 168 if (ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 169 return ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 170 if (ToolingExtensions.hasExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 171 return ToolingExtensions.readStringExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 172 return FormatUtilities.FHIR_NS; 173 } 174 175 private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) { 176 boolean result = false; 177 if (!ed.getType().isEmpty()) { 178 result = true; 179 for (final ElementDefinition ele : children) { 180 if (!ele.getPath().contains("extension")) { 181 result = false; 182 break; 183 } 184 } 185 } 186 return result; 187 } 188 189 public boolean IsLogicalAndHasPrimitiveValue(String name) { 190// if (canBePrimitive!= null) 191// return canBePrimitive; 192 193 canBePrimitive = false; 194 if (structure.getKind() != StructureDefinitionKind.LOGICAL) 195 return false; 196 if (!hasType(name)) 197 return false; 198 StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name)); 199 if (sd == null) 200 sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(getType(name), context.getOverrideVersionNs())); 201 if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) 202 return true; 203 if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL) 204 return false; 205 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 206 if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) { 207 canBePrimitive = true; 208 return true; 209 } 210 } 211 return false; 212 } 213 214 public boolean isChoice() { 215 if (definition.getType().size() <= 1) 216 return false; 217 String tn = definition.getType().get(0).getCode(); 218 for (int i = 1; i < definition.getType().size(); i++) 219 if (!definition.getType().get(i).getCode().equals(tn)) 220 return true; 221 return false; 222 } 223 224 225 protected List<Property> getChildProperties(String elementName, String statedType) throws FHIRException { 226 ElementDefinition ed = definition; 227 StructureDefinition sd = structure; 228 List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed); 229 String url = null; 230 if (children.isEmpty() || isElementWithOnlyExtension(ed, children)) { 231 // ok, find the right definitions 232 String t = null; 233 if (ed.getType().size() == 1) 234 t = ed.getType().get(0).getCode(); 235 else if (ed.getType().size() == 0) 236 throw new Error("types == 0, and no children found on "+getDefinition().getPath()); 237 else { 238 t = ed.getType().get(0).getCode(); 239 boolean all = true; 240 for (TypeRefComponent tr : ed.getType()) { 241 if (!tr.getCode().equals(t)) { 242 all = false; 243 break; 244 } 245 } 246 if (!all) { 247 // ok, it's polymorphic 248 if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR)) { 249 t = statedType; 250 if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) 251 t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); 252 boolean ok = false; 253 for (TypeRefComponent tr : ed.getType()) { 254 if (tr.getCode().equals(t)) 255 ok = true; 256 if (Utilities.isAbsoluteUrl(tr.getCode())) { 257 StructureDefinition sdt = context.fetchResource(StructureDefinition.class, tr.getCode()); 258 if (sdt != null && sdt.getType().equals(t)) { 259 url = tr.getCode(); 260 ok = true; 261 } 262 } 263 if (ok) 264 break; 265 } 266 if (!ok) 267 throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath()); 268 269 } else { 270 t = elementName.substring(tail(ed.getPath()).length() - 3); 271 if (isPrimitive(lowFirst(t))) 272 t = lowFirst(t); 273 } 274 } 275 } 276 if (!"xhtml".equals(t)) { 277 for (TypeRefComponent aType: ed.getType()) { 278 if (aType.getCode().equals(t)) { 279 if (aType.hasProfile()) { 280 assert aType.getProfile().size() == 1; 281 url = aType.getProfile().get(0).getValue(); 282 } else { 283 url = ProfileUtilities.sdNs(t, context.getOverrideVersionNs()); 284 } 285 break; 286 } 287 } 288 if (url==null) 289 throw new FHIRException("Unable to find type " + t + " for element " + elementName + " with path " + ed.getPath()); 290 sd = context.fetchResource(StructureDefinition.class, url); 291 if (sd == null) 292 throw new DefinitionException("Unable to find type '"+t+"' for name '"+elementName+"' on property "+definition.getPath()); 293 children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0)); 294 } 295 } 296 List<Property> properties = new ArrayList<Property>(); 297 for (ElementDefinition child : children) { 298 properties.add(new Property(context, child, sd)); 299 } 300 return properties; 301 } 302 303 protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException { 304 ElementDefinition ed = definition; 305 StructureDefinition sd = structure; 306 List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed); 307 if (children.isEmpty()) { 308 // ok, find the right definitions 309 String t = null; 310 if (ed.getType().size() == 1) 311 t = ed.getType().get(0).getCode(); 312 else if (ed.getType().size() == 0) 313 throw new Error("types == 0, and no children found"); 314 else { 315 t = ed.getType().get(0).getCode(); 316 boolean all = true; 317 for (TypeRefComponent tr : ed.getType()) { 318 if (!tr.getCode().equals(t)) { 319 all = false; 320 break; 321 } 322 } 323 if (!all) { 324 // ok, it's polymorphic 325 t = type.getType(); 326 } 327 } 328 if (!"xhtml".equals(t)) { 329 sd = context.fetchResource(StructureDefinition.class, t); 330 if (sd == null) 331 throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath()); 332 children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0)); 333 } 334 } 335 List<Property> properties = new ArrayList<Property>(); 336 for (ElementDefinition child : children) { 337 properties.add(new Property(context, child, sd)); 338 } 339 return properties; 340 } 341 342 private String tail(String path) { 343 return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path; 344 } 345 346 public Property getChild(String elementName, String childName) throws FHIRException { 347 List<Property> children = getChildProperties(elementName, null); 348 for (Property p : children) { 349 if (p.getName().equals(childName)) { 350 return p; 351 } 352 } 353 return null; 354 } 355 356 public Property getChild(String name, TypeDetails type) throws DefinitionException { 357 List<Property> children = getChildProperties(type); 358 for (Property p : children) { 359 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 360 return p; 361 } 362 } 363 return null; 364 } 365 366 public Property getChild(String name) throws FHIRException { 367 List<Property> children = getChildProperties(name, null); 368 for (Property p : children) { 369 if (p.getName().equals(name)) { 370 return p; 371 } 372 } 373 return null; 374 } 375 376 public Property getChildSimpleName(String elementName, String name) throws FHIRException { 377 List<Property> children = getChildProperties(elementName, null); 378 for (Property p : children) { 379 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 380 return p; 381 } 382 } 383 return null; 384 } 385 386 public IWorkerContext getContext() { 387 return context; 388 } 389 390 @Override 391 public String toString() { 392 return definition.getPath(); 393 } 394 395 396}