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