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 (isPrimitive() && (hash == "value".hashCode())) { 300 this.value = castToString(value).asStringValue(); 301 return this; 302 } 303 if ("xhtml".equals(getType()) && (hash == "value".hashCode())) { 304 this.xhtml = castToXhtml(value); 305 this.value = castToXhtmlString(value); 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 if (childForValue == null) 337 for (Property p : property.getChildProperties(this.name, type)) { 338 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 339 Element ne = new Element(name, p); 340 children.add(ne); 341 childForValue = ne; 342 break; 343 } 344 } 345 346 if (childForValue == null) 347 throw new Error("Cannot set property "+name+" on "+this.name); 348 else if (value.isPrimitive()) { 349 if (childForValue.property.getName().endsWith("[x]")) 350 childForValue.name = name+Utilities.capitalize(value.fhirType()); 351 childForValue.setValue(value.primitiveValue()); 352 } else { 353 Element ve = (Element) value; 354 childForValue.type = ve.getType(); 355 if (childForValue.property.getName().endsWith("[x]")) 356 childForValue.name = name+Utilities.capitalize(childForValue.type); 357 else if (value.isResource()) { 358 if (childForValue.elementProperty == null) 359 childForValue.elementProperty = childForValue.property; 360 childForValue.property = ve.property; 361 childForValue.special = SpecialElement.BUNDLE_ENTRY; 362 } 363 if (ve.children != null) { 364 if (childForValue.children == null) 365 childForValue.children = new ArrayList<Element>(); 366 else 367 childForValue.children.clear(); 368 childForValue.children.addAll(ve.children); 369 } 370 } 371 return childForValue; 372 } 373 374 private Base convertToElement(Property prop, Base v) throws FHIRException { 375 return new ObjectConverter(property.getContext()).convert(prop, (Type) v); 376 } 377 378 private boolean isDataType(Base v) { 379 return v instanceof Type && property.getContext().getTypeNames().contains(v.fhirType()); 380 } 381 382 @Override 383 public Base makeProperty(int hash, String name) throws FHIRException { 384 if (isPrimitive() && (hash == "value".hashCode())) { 385 return new StringType(value); 386 } 387 388 if (children == null) 389 children = new ArrayList<Element>(); 390 391 // look through existing children 392 for (Element child : children) { 393 if (child.getName().equals(name)) { 394 if (!child.isList()) { 395 return child; 396 } else { 397 Element ne = new Element(child); 398 children.add(ne); 399 numberChildren(); 400 return ne; 401 } 402 } 403 } 404 405 for (Property p : property.getChildProperties(this.name, type)) { 406 if (p.getName().equals(name)) { 407 Element ne = new Element(name, p); 408 children.add(ne); 409 return ne; 410 } 411 } 412 413 throw new Error("Unrecognised name "+name+" on "+this.name); 414 } 415 416 private int maxToInt(String max) { 417 if (max.equals("*")) 418 return Integer.MAX_VALUE; 419 else 420 return Integer.parseInt(max); 421 } 422 423 @Override 424 public boolean isPrimitive() { 425 return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name)); 426 } 427 428 @Override 429 public boolean isBooleanPrimitive() { 430 return isPrimitive() && ("boolean".equals(type) || "boolean".equals(property.getType(name))); 431 } 432 433 @Override 434 public boolean isResource() { 435 return property.isResource(); 436 } 437 438 439 @Override 440 public boolean hasPrimitiveValue() { 441 return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name); 442 } 443 444 445 @Override 446 public String primitiveValue() { 447 if (isPrimitive()) 448 return value; 449 else { 450 if (hasPrimitiveValue() && children != null) { 451 for (Element c : children) { 452 if (c.getName().equals("value")) 453 return c.primitiveValue(); 454 } 455 } 456 return null; 457 } 458 } 459 460 // for the validator 461 public int line() { 462 return line; 463 } 464 465 public int col() { 466 return col; 467 } 468 469 public Element markLocation(int line, int col) { 470 this.line = line; 471 this.col = col; 472 return this; 473 } 474 475 public void clearDecorations() { 476 clearUserData("fhir.decorations"); 477 for (Element e : children) 478 e.clearDecorations(); 479 } 480 481 public void markValidation(StructureDefinition profile, ElementDefinition definition) { 482 @SuppressWarnings("unchecked") 483 List<ElementDecoration> decorations = (List<ElementDecoration>) getUserData("fhir.decorations"); 484 if (decorations == null) { 485 decorations = new ArrayList<ElementDecoration>(); 486 setUserData("fhir.decorations", decorations); 487 } 488 decorations.add(new ElementDecoration(DecorationType.TYPE, profile.getUserString("path"), definition.getPath())); 489 if (tail(definition.getId()).contains(":")) { 490 String[] details = tail(definition.getId()).split("\\:"); 491 decorations.add(new ElementDecoration(DecorationType.SLICE, null, details[1])); 492 } 493 } 494 495 private String tail(String id) { 496 return id.contains(".") ? id.substring(id.lastIndexOf(".")+1) : id; 497 } 498 499 public Element getNamedChild(String name) { 500 if (children == null) 501 return null; 502 Element result = null; 503 for (Element child : children) { 504 if (child.getName().equals(name)) { 505 if (result == null) 506 result = child; 507 else 508 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 509 } 510 } 511 return result; 512 } 513 514 public void getNamedChildren(String name, List<Element> list) { 515 if (children != null) 516 for (Element child : children) 517 if (child.getName().equals(name)) 518 list.add(child); 519 } 520 521 public String getNamedChildValue(String name) { 522 Element child = getNamedChild(name); 523 return child == null ? null : child.value; 524 } 525 526 public void getNamedChildrenWithWildcard(String string, List<Element> values) { 527 Validate.isTrue(string.endsWith("[x]")); 528 529 String start = string.substring(0, string.length() - 3); 530 if (children != null) { 531 for (Element child : children) { 532 if (child.getName().startsWith(start)) { 533 values.add(child); 534 } 535 } 536 } 537 } 538 539 540 public XhtmlNode getXhtml() { 541 return xhtml; 542 } 543 544 public Element setXhtml(XhtmlNode xhtml) { 545 this.xhtml = xhtml; 546 return this; 547 } 548 549 @Override 550 public boolean isEmpty() { 551 if (isNotBlank(value)) { 552 return false; 553 } 554 for (Element next : getChildren()) { 555 if (!next.isEmpty()) { 556 return false; 557 } 558 } 559 return true; 560 } 561 562 public Property getElementProperty() { 563 return elementProperty; 564 } 565 566 public boolean hasElementProperty() { 567 return elementProperty != null; 568 } 569 570 public boolean hasChild(String name) { 571 return getNamedChild(name) != null; 572 } 573 574 @Override 575 public String toString() { 576 return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 577 } 578 579 @Override 580 public String getIdBase() { 581 return getChildValue("id"); 582 } 583 584 @Override 585 public void setIdBase(String value) { 586 setChildValue("id", value); 587 } 588 589 590 @Override 591 public boolean equalsDeep(Base other) { 592 if (!super.equalsDeep(other)) 593 return false; 594 if (isPrimitive() && other.isPrimitive()) 595 return primitiveValue().equals(other.primitiveValue()); 596 if (isPrimitive() || other.isPrimitive()) 597 return false; 598 Set<String> processed = new HashSet<String>(); 599 for (org.hl7.fhir.r4.model.Property p : children()) { 600 String name = p.getName(); 601 processed.add(name); 602 org.hl7.fhir.r4.model.Property o = other.getChildByName(name); 603 if (!equalsDeep(p, o)) 604 return false; 605 } 606 for (org.hl7.fhir.r4.model.Property p : children()) { 607 String name = p.getName(); 608 if (!processed.contains(name)) { 609 org.hl7.fhir.r4.model.Property o = other.getChildByName(name); 610 if (!equalsDeep(p, o)) 611 return false; 612 } 613 } 614 return true; 615 } 616 617 private boolean equalsDeep(org.hl7.fhir.r4.model.Property p, org.hl7.fhir.r4.model.Property o) { 618 if (o == null || p == null) 619 return false; 620 if (p.getValues().size() != o.getValues().size()) 621 return false; 622 for (int i = 0; i < p.getValues().size(); i++) 623 if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true)) 624 return false; 625 return true; 626 } 627 628 @Override 629 public boolean equalsShallow(Base other) { 630 if (!super.equalsShallow(other)) 631 return false; 632 if (isPrimitive() && other.isPrimitive()) 633 return primitiveValue().equals(other.primitiveValue()); 634 if (isPrimitive() || other.isPrimitive()) 635 return false; 636 return true; //? 637 } 638 639 public Type asType() throws FHIRException { 640 return new ObjectConverter(property.getContext()).convertToType(this); 641 } 642 643 @Override 644 public boolean isMetadataBased() { 645 return true; 646 } 647 648 public boolean isList() { 649 if (elementProperty != null) 650 return elementProperty.isList(); 651 else 652 return property.isList(); 653 } 654 655 @Override 656 public String[] getTypesForProperty(int hash, String name) throws FHIRException { 657 Property p = property.getChildSimpleName(this.name, name); 658 if (p != null) { 659 Set<String> types = new HashSet<String>(); 660 for (TypeRefComponent tr : p.getDefinition().getType()) { 661 types.add(tr.getCode()); 662 } 663 return types.toArray(new String[]{}); 664 } 665 return super.getTypesForProperty(hash, name); 666 667 } 668 669 public void sort() { 670 if (children != null) { 671 List<Element> remove = new ArrayList<Element>(); 672 for (Element child : children) { 673 child.sort(); 674 if (child.isEmpty()) 675 remove.add(child); 676 } 677 children.removeAll(remove); 678 Collections.sort(children, new ElementSortComparator(this, this.property)); 679 } 680 } 681 682 public class ElementSortComparator implements Comparator<Element> { 683 private List<ElementDefinition> children; 684 public ElementSortComparator(Element e, Property property) { 685 String tn = e.getType(); 686 StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, property.getContext().getOverrideVersionNs())); 687 if (sd != null && !sd.getAbstract()) 688 children = sd.getSnapshot().getElement(); 689 else 690 children = property.getStructure().getSnapshot().getElement(); 691 } 692 693 @Override 694 public int compare(Element e0, Element e1) { 695 int i0 = find(e0); 696 int i1 = find(e1); 697 return Integer.compare(i0, i1); 698 } 699 private int find(Element e0) { 700 int i = e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) : children.indexOf(e0.property.getDefinition()); 701 return i; 702 } 703 704 } 705 706 public class ICodingImpl implements ICoding { 707 private String system; 708 private String version; 709 private String code; 710 private String display; 711 private boolean doesSystem; 712 private boolean doesVersion; 713 private boolean doesCode; 714 private boolean doesDisplay; 715 public ICodingImpl(boolean doesCode, boolean doesSystem, boolean doesVersion, boolean doesDisplay) { 716 super(); 717 this.doesCode = doesCode; 718 this.doesSystem = doesSystem; 719 this.doesVersion = doesVersion; 720 this.doesDisplay = doesDisplay; 721 } 722 public String getSystem() { 723 return system; 724 } 725 public String getVersion() { 726 return version; 727 } 728 public String getCode() { 729 return code; 730 } 731 public String getDisplay() { 732 return display; 733 } 734 public boolean hasSystem() { 735 return !Utilities.noString(system); 736 } 737 public boolean hasVersion() { 738 return !Utilities.noString(version); 739 } 740 public boolean hasCode() { 741 return !Utilities.noString(code); 742 } 743 public boolean hasDisplay() { 744 return !Utilities.noString(display); 745 } 746 public boolean supportsSystem() { 747 return doesSystem; 748 } 749 public boolean supportsVersion() { 750 return doesVersion; 751 } 752 public boolean supportsCode() { 753 return doesCode; 754 } 755 public boolean supportsDisplay() { 756 return doesDisplay; 757 } 758 } 759 760 public ICoding getAsICoding() throws FHIRException { 761 if ("code".equals(fhirType())) { 762 if (property.getDefinition().getBinding().getStrength() != BindingStrength.REQUIRED) 763 return null; 764 ICodingImpl c = new ICodingImpl(true, true, false, false); 765 c.code = primitiveValue(); 766 ValueSetExpansionOutcome vse = property.getContext().expandVS(property.getDefinition().getBinding(), true, false); 767 if (vse.getValueset() == null) 768 return null; 769 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 770 if (cc.getCode().equals(c.code)) { 771 c.system = cc.getSystem(); 772 if (cc.hasVersion()) { 773 c.doesVersion = true; 774 c.version = cc.getVersion(); 775 } 776 if (cc.hasDisplay()) { 777 c.doesDisplay = true; 778 c.display = cc.getDisplay(); 779 } 780 } 781 } 782 if (c.system == null) 783 return null; 784 return c; 785 } else if ("Coding".equals(fhirType())) { 786 ICodingImpl c = new ICodingImpl(true, true, true, true); 787 c.system = getNamedChildValue("system"); 788 c.code = getNamedChildValue("code"); 789 c.display = getNamedChildValue("display"); 790 c.version = getNamedChildValue("version"); 791 return c; 792 } else if ("Quantity".equals(fhirType())) { 793 ICodingImpl c = new ICodingImpl(true, true, false, false); 794 c.system = getNamedChildValue("system"); 795 c.code = getNamedChildValue("code"); 796 return c; 797 } else 798 return null; 799 } 800 801 802}