001package org.hl7.fhir.dstu3.elementmodel; 002 003import static org.apache.commons.lang3.StringUtils.isNotBlank; 004 005import java.io.IOException; 006import java.util.ArrayList; 007import java.util.Collections; 008import java.util.Comparator; 009import java.util.HashSet; 010import java.util.List; 011import java.util.Set; 012 013import org.apache.commons.lang3.Validate; 014import org.hl7.fhir.dstu3.elementmodel.Element.ElementSortComparator; 015import org.hl7.fhir.dstu3.model.Base; 016import org.hl7.fhir.dstu3.model.ElementDefinition; 017import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; 018import org.hl7.fhir.dstu3.model.StringType; 019import org.hl7.fhir.dstu3.model.StructureDefinition; 020import org.hl7.fhir.dstu3.model.Type; 021import org.hl7.fhir.exceptions.FHIRException; 022import org.hl7.fhir.utilities.Utilities; 023import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 024import org.hl7.fhir.utilities.xhtml.XhtmlNode; 025 026/** 027 * This class represents the underlying reference model of FHIR 028 * 029 * A resource is nothing but a set of elements, where every element has a 030 * name, maybe a stated type, maybe an id, and either a value or child elements 031 * (one or the other, but not both or neither) 032 * 033 * @author Grahame Grieve 034 * 035 */ 036public class Element extends Base { 037 038 039 public enum SpecialElement { 040 CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, PARAMETER; 041 042 public static SpecialElement fromProperty(Property property) { 043 if (property.getStructure().getIdElement().getIdPart().equals("Parameters")) 044 return PARAMETER; 045 if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("resource")) 046 return BUNDLE_ENTRY; 047 if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("outcome")) 048 return BUNDLE_OUTCOME; 049 if (property.getName().equals("contained")) 050 return CONTAINED; 051 throw new Error("Unknown resource containing a native resource: "+property.getDefinition().getId()); 052 } 053 } 054 055 private List<String> comments;// not relevant for production, but useful in documentation 056 private String name; 057 private String type; 058 private String value; 059 private int index = -1; 060 private List<Element> children; 061 private Property property; 062 private Property elementProperty; // this is used when special is set to true - it tracks the underlying element property which is used in a few places 063 private int line; 064 private int col; 065 private SpecialElement special; 066 private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation 067 068 public Element(String name) { 069 super(); 070 this.name = name; 071 } 072 073 public Element(Element other) { 074 super(); 075 name = other.name; 076 type = other.type; 077 property = other.property; 078 elementProperty = other.elementProperty; 079 special = other.special; 080 } 081 082 public Element(String name, Property property) { 083 super(); 084 this.name = name; 085 this.property = property; 086 } 087 088 public Element(String name, Property property, String type, String value) { 089 super(); 090 this.name = name; 091 this.property = property; 092 this.type = type; 093 this.value = value; 094 } 095 096 public void updateProperty(Property property, SpecialElement special, Property elementProperty) { 097 this.property = property; 098 this.elementProperty = elementProperty; 099 this.special = special; 100 } 101 102 public SpecialElement getSpecial() { 103 return special; 104 } 105 106 public String getName() { 107 return name; 108 } 109 110 public String getType() { 111 if (type == null) 112 return property.getType(name); 113 else 114 return type; 115 } 116 117 public String getValue() { 118 return value; 119 } 120 121 public boolean hasChildren() { 122 return !(children == null || children.isEmpty()); 123 } 124 125 public List<Element> getChildren() { 126 if (children == null) 127 children = new ArrayList<Element>(); 128 return children; 129 } 130 131 public boolean hasComments() { 132 return !(comments == null || comments.isEmpty()); 133 } 134 135 public List<String> getComments() { 136 if (comments == null) 137 comments = new ArrayList<String>(); 138 return comments; 139 } 140 141 public Property getProperty() { 142 return property; 143 } 144 145 public void setValue(String value) { 146 this.value = value; 147 } 148 149 public void setType(String type) { 150 this.type = type; 151 152 } 153 154 public boolean hasValue() { 155 return value != null; 156 } 157 158 public List<Element> getChildrenByName(String name) { 159 List<Element> res = new ArrayList<Element>(); 160 if (hasChildren()) { 161 for (Element child : children) 162 if (name.equals(child.getName())) 163 res.add(child); 164 } 165 return res; 166 } 167 168 public void numberChildren() { 169 if (children == null) 170 return; 171 172 String last = ""; 173 int index = 0; 174 for (Element child : children) { 175 if (child.getProperty().isList()) { 176 if (last.equals(child.getName())) { 177 index++; 178 } else { 179 last = child.getName(); 180 index = 0; 181 } 182 child.index = index; 183 } else { 184 child.index = -1; 185 } 186 child.numberChildren(); 187 } 188 } 189 190 public int getIndex() { 191 return index; 192 } 193 194 public boolean hasIndex() { 195 return index > -1; 196 } 197 198 public void setIndex(int index) { 199 this.index = index; 200 } 201 202 public String getChildValue(String name) { 203 if (children == null) 204 return null; 205 for (Element child : children) { 206 if (name.equals(child.getName())) 207 return child.getValue(); 208 } 209 return null; 210 } 211 212 public void setChildValue(String name, String value) { 213 if (children == null) 214 children = new ArrayList<Element>(); 215 for (Element child : children) { 216 if (name.equals(child.getName())) { 217 if (!child.isPrimitive()) 218 throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")"); 219 child.setValue(value); 220 } 221 } 222 try { 223 setProperty(name.hashCode(), name, new StringType(value)); 224 } catch (FHIRException e) { 225 throw new Error(e); 226 } 227 } 228 229 public List<Element> getChildren(String name) { 230 List<Element> res = new ArrayList<Element>(); 231 if (children != null) 232 for (Element child : children) { 233 if (name.equals(child.getName())) 234 res.add(child); 235 } 236 return res; 237 } 238 239 public boolean hasType() { 240 if (type == null) 241 return property.hasType(name); 242 else 243 return true; 244 } 245 246 @Override 247 public String fhirType() { 248 return getType(); 249 } 250 251 @Override 252 public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { 253 if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) { 254// String tn = getType(); 255// throw new Error(tn+" not done yet"); 256 Base[] b = new Base[1]; 257 b[0] = new StringType(value); 258 return b; 259 } 260 261 List<Base> result = new ArrayList<Base>(); 262 if (children != null) { 263 for (Element child : children) { 264 if (child.getName().equals(name)) 265 result.add(child); 266 if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]")) 267 result.add(child); 268 } 269 } 270 if (result.isEmpty() && checkValid) { 271// throw new FHIRException("not determined yet"); 272 } 273 return result.toArray(new Base[result.size()]); 274 } 275 276 @Override 277 protected void listChildren(List<org.hl7.fhir.dstu3.model.Property> childProps) { 278 if (children != null) { 279 for (Element c : children) { 280 childProps.add(new org.hl7.fhir.dstu3.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c)); 281 } 282 } 283 } 284 285 @Override 286 public Base setProperty(int hash, String name, Base value) throws FHIRException { 287 if (isPrimitive() && (hash == "value".hashCode())) { 288 this.value = castToString(value).asStringValue(); 289 return this; 290 } 291 if ("xhtml".equals(getType()) && (hash == "value".hashCode())) { 292 this.xhtml = castToXhtml(value); 293 this.value = castToXhtmlString(value); 294 return this; 295 } 296 297 if (!value.isPrimitive() && !(value instanceof Element)) { 298 if (isDataType(value)) 299 value = convertToElement(property.getChild(name), value); 300 else 301 throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type"); 302 } 303 304 if (children == null) 305 children = new ArrayList<Element>(); 306 Element childForValue = null; 307 308 // look through existing children 309 for (Element child : children) { 310 if (child.getName().equals(name)) { 311 if (!child.isList()) { 312 childForValue = child; 313 break; 314 } else { 315 Element ne = new Element(child); 316 children.add(ne); 317 numberChildren(); 318 childForValue = ne; 319 break; 320 } 321 } 322 } 323 324 if (childForValue == null) 325 for (Property p : property.getChildProperties(this.name, type)) { 326 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 327 Element ne = new Element(name, p); 328 children.add(ne); 329 childForValue = ne; 330 break; 331 } 332 } 333 334 if (childForValue == null) 335 throw new Error("Cannot set property "+name+" on "+this.name); 336 else if (value.isPrimitive()) { 337 if (childForValue.property.getName().endsWith("[x]")) 338 childForValue.name = name+Utilities.capitalize(value.fhirType()); 339 childForValue.setValue(value.primitiveValue()); 340 } else { 341 Element ve = (Element) value; 342 childForValue.type = ve.getType(); 343 if (childForValue.property.getName().endsWith("[x]")) 344 childForValue.name = name+Utilities.capitalize(childForValue.type); 345 else if (value.isResource()) { 346 if (childForValue.elementProperty == null) 347 childForValue.elementProperty = childForValue.property; 348 childForValue.property = ve.property; 349 childForValue.special = SpecialElement.BUNDLE_ENTRY; 350 } 351 if (ve.children != null) { 352 if (childForValue.children == null) 353 childForValue.children = new ArrayList<Element>(); 354 else 355 childForValue.children.clear(); 356 childForValue.children.addAll(ve.children); 357 } 358 } 359 return childForValue; 360 } 361 362 private Base convertToElement(Property prop, Base v) throws FHIRException { 363 return new ObjectConverter(property.getContext()).convert(prop, (Type) v); 364 } 365 366 private boolean isDataType(Base v) { 367 return v instanceof Type && property.getContext().getTypeNames().contains(v.fhirType()); 368 } 369 370 @Override 371 public Base makeProperty(int hash, String name) throws FHIRException { 372 if (isPrimitive() && (hash == "value".hashCode())) { 373 return new StringType(value); 374 } 375 376 if (children == null) 377 children = new ArrayList<Element>(); 378 379 // look through existing children 380 for (Element child : children) { 381 if (child.getName().equals(name)) { 382 if (!child.isList()) { 383 return child; 384 } else { 385 Element ne = new Element(child); 386 children.add(ne); 387 numberChildren(); 388 return ne; 389 } 390 } 391 } 392 393 for (Property p : property.getChildProperties(this.name, type)) { 394 if (p.getName().equals(name)) { 395 Element ne = new Element(name, p); 396 children.add(ne); 397 return ne; 398 } 399 } 400 401 throw new Error("Unrecognised name "+name+" on "+this.name); 402 } 403 404 private int maxToInt(String max) { 405 if (max.equals("*")) 406 return Integer.MAX_VALUE; 407 else 408 return Integer.parseInt(max); 409 } 410 411 @Override 412 public boolean isPrimitive() { 413 return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name)); 414 } 415 416 @Override 417 public boolean isResource() { 418 return property.isResource(); 419 } 420 421 422 @Override 423 public boolean hasPrimitiveValue() { 424 return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name); 425 } 426 427 428 @Override 429 public String primitiveValue() { 430 if (isPrimitive()) 431 return value; 432 else { 433 if (hasPrimitiveValue() && children != null) { 434 for (Element c : children) { 435 if (c.getName().equals("value")) 436 return c.primitiveValue(); 437 } 438 } 439 return null; 440 } 441 } 442 443 // for the validator 444 public int line() { 445 return line; 446 } 447 448 public int col() { 449 return col; 450 } 451 452 public Element markLocation(int line, int col) { 453 this.line = line; 454 this.col = col; 455 return this; 456 } 457 458 public void markValidation(StructureDefinition profile, ElementDefinition definition) { 459 } 460 461 public Element getNamedChild(String name) { 462 if (children == null) 463 return null; 464 Element result = null; 465 for (Element child : children) { 466 if (child.getName().equals(name)) { 467 if (result == null) 468 result = child; 469 else 470 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 471 } 472 } 473 return result; 474 } 475 476 public void getNamedChildren(String name, List<Element> list) { 477 if (children != null) 478 for (Element child : children) 479 if (child.getName().equals(name)) 480 list.add(child); 481 } 482 483 public String getNamedChildValue(String name) { 484 Element child = getNamedChild(name); 485 return child == null ? null : child.value; 486 } 487 488 public void getNamedChildrenWithWildcard(String string, List<Element> values) { 489 Validate.isTrue(string.endsWith("[x]")); 490 491 String start = string.substring(0, string.length() - 3); 492 if (children != null) { 493 for (Element child : children) { 494 if (child.getName().startsWith(start)) { 495 values.add(child); 496 } 497 } 498 } 499 } 500 501 502 public XhtmlNode getXhtml() { 503 return xhtml; 504 } 505 506 public Element setXhtml(XhtmlNode xhtml) { 507 this.xhtml = xhtml; 508 return this; 509 } 510 511 @Override 512 public boolean isEmpty() { 513 if (isNotBlank(value)) { 514 return false; 515 } 516 for (Element next : getChildren()) { 517 if (!next.isEmpty()) { 518 return false; 519 } 520 } 521 return true; 522 } 523 524 public Property getElementProperty() { 525 return elementProperty; 526 } 527 528 public boolean hasElementProperty() { 529 return elementProperty != null; 530 } 531 532 public boolean hasChild(String name) { 533 return getNamedChild(name) != null; 534 } 535 536 @Override 537 public String toString() { 538 return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 539 } 540 541 @Override 542 public String getIdBase() { 543 return getChildValue("id"); 544 } 545 546 @Override 547 public void setIdBase(String value) { 548 setChildValue("id", value); 549 } 550 551 552// @Override 553// public boolean equalsDeep(Base other) { 554// if (!super.equalsDeep(other)) 555// return false; 556// 557// } 558// 559// @Override 560// public boolean equalsShallow(Base other) { 561// if (!super.equalsShallow(other)) 562// return false; 563// } 564 565 public Type asType() throws FHIRException { 566 return new ObjectConverter(property.getContext()).convertToType(this); 567 } 568 569 @Override 570 public boolean isMetadataBased() { 571 return true; 572 } 573 574 public boolean isList() { 575 if (elementProperty != null) 576 return elementProperty.isList(); 577 else 578 return property.isList(); 579 } 580 581 @Override 582 public String[] getTypesForProperty(int hash, String name) throws FHIRException { 583 Property p = property.getChildSimpleName(this.name, name); 584 if (p != null) { 585 Set<String> types = new HashSet<String>(); 586 for (TypeRefComponent tr : p.getDefinition().getType()) { 587 types.add(tr.getCode()); 588 } 589 return types.toArray(new String[]{}); 590 } 591 return super.getTypesForProperty(hash, name); 592 593 } 594 595 public void sort() { 596 if (children != null) { 597 List<Element> remove = new ArrayList<Element>(); 598 for (Element child : children) { 599 child.sort(); 600 if (child.isEmpty()) 601 remove.add(child); 602 } 603 children.removeAll(remove); 604 Collections.sort(children, new ElementSortComparator(this, this.property)); 605 } 606 } 607 608 public class ElementSortComparator implements Comparator<Element> { 609 private List<ElementDefinition> children; 610 public ElementSortComparator(Element e, Property property) { 611 String tn = e.getType(); 612 StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+tn); 613 if (sd != null && !sd.getAbstract()) 614 children = sd.getSnapshot().getElement(); 615 else 616 children = property.getStructure().getSnapshot().getElement(); 617 } 618 619 @Override 620 public int compare(Element e0, Element e1) { 621 int i0 = find(e0); 622 int i1 = find(e1); 623 return (i0 < i1) ? -1 : ((i0 == i1) ? 0 : 1); 624 } 625 626 private int find(Element e0) { 627 int i = e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) : children.indexOf(e0.property.getDefinition()); 628 return i; 629 } 630 631 } 632 633}