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