001package org.hl7.fhir.r5.elementmodel; 002 003import java.io.PrintStream; 004 005/* 006 Copyright (c) 2011+, HL7, Inc. 007 All rights reserved. 008 009 Redistribution and use in source and binary forms, with or without modification, 010 are permitted provided that the following conditions are met: 011 012 * Redistributions of source code must retain the above copyright notice, this 013 list of conditions and the following disclaimer. 014 * Redistributions in binary form must reproduce the above copyright notice, 015 this list of conditions and the following disclaimer in the documentation 016 and/or other materials provided with the distribution. 017 * Neither the name of HL7 nor the names of its contributors may be used to 018 endorse or promote products derived from this software without specific 019 prior written permission. 020 021 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 022 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 023 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 024 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 025 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 026 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 028 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 029 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 030 POSSIBILITY OF SUCH DAMAGE. 031 032 */ 033 034 035import java.util.*; 036 037import org.apache.commons.lang3.Validate; 038import org.hl7.fhir.exceptions.FHIRException; 039import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 040import org.hl7.fhir.r5.context.ContextUtilities; 041import org.hl7.fhir.r5.elementmodel.Element.SliceDefinition; 042import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; 043import org.hl7.fhir.r5.extensions.ExtensionsUtils; 044import org.hl7.fhir.r5.model.Base; 045import org.hl7.fhir.r5.model.DataType; 046import org.hl7.fhir.r5.model.ElementDefinition; 047import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 048import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 049import org.hl7.fhir.r5.model.ICoding; 050import org.hl7.fhir.r5.model.StringType; 051import org.hl7.fhir.r5.model.StructureDefinition; 052import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 053import org.hl7.fhir.r5.model.TypeConvertor; 054import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 055import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 056import org.hl7.fhir.r5.utils.ToolingExtensions; 057import org.hl7.fhir.utilities.ElementDecoration; 058import org.hl7.fhir.utilities.ElementDecoration.DecorationType; 059import org.hl7.fhir.utilities.FhirPublication; 060import org.hl7.fhir.utilities.NamedItemList; 061import org.hl7.fhir.utilities.NamedItemList.NamedItem; 062import org.hl7.fhir.utilities.SourceLocation; 063import org.hl7.fhir.utilities.Utilities; 064import org.hl7.fhir.utilities.validation.ValidationMessage; 065import org.hl7.fhir.utilities.xhtml.XhtmlNode; 066 067/** 068 * This class represents the underlying reference model of FHIR 069 * 070 * A resource is nothing but a set of elements, where every element has a 071 * name, maybe a stated type, maybe an id, and either a value or child elements 072 * (one or the other, but not both or neither) 073 * 074 * @author Grahame Grieve 075 * 076 */ 077public class Element extends Base implements NamedItem { 078 public class SliceDefinition { 079 080 private StructureDefinition profile; 081 private ElementDefinition definition; 082 private ElementDefinition slice; 083 084 public SliceDefinition(StructureDefinition profile, ElementDefinition definition, ElementDefinition slice) { 085 this.profile = profile; 086 this.definition = definition; 087 this.slice = slice; 088 } 089 090 public StructureDefinition getProfile() { 091 return profile; 092 } 093 094 public ElementDefinition getDefinition() { 095 return definition; 096 } 097 098 public ElementDefinition getSlice() { 099 return slice; 100 } 101 102 } 103 104 private static final HashSet<String> extensionList = new HashSet<>(Arrays.asList("extension", "modifierExtension")); 105 106 public enum SpecialElement { 107 CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, BUNDLE_ISSUES, PARAMETER, LOGICAL; 108 109 public static SpecialElement fromProperty(Property property) { 110 if (property.getStructure().getType().equals("Parameters")) 111 return PARAMETER; 112 if (property.getStructure().getType().equals("Bundle") && property.getName().equals("resource")) 113 return BUNDLE_ENTRY; 114 if (property.getStructure().getType().equals("Bundle") && property.getName().equals("outcome")) 115 return BUNDLE_OUTCOME; 116 if (property.getStructure().getType().equals("Bundle") && property.getName().equals("issues")) 117 return BUNDLE_ISSUES; 118 if (property.getName().equals("contained")) 119 return CONTAINED; 120 if (property.getStructure().getKind() == StructureDefinitionKind.LOGICAL) 121 return LOGICAL; 122 throw new FHIRException("Unknown resource containing a native resource: "+property.getDefinition().getId()); 123 } 124 125 public String toHuman() { 126 switch (this) { 127 case BUNDLE_ENTRY: return "entry"; 128 case BUNDLE_OUTCOME: return "outcome"; 129 case BUNDLE_ISSUES: return "issues"; 130 case CONTAINED: return "contained"; 131 case PARAMETER: return "parameter"; 132 case LOGICAL: return "logical"; 133 default: return "??"; 134 } 135 } 136 } 137 138 private List<String> comments;// not relevant for production, but useful in documentation 139 private String name; 140 private String type; 141 private String value; 142 private int index = -1; 143 private NamedItemList<Element> children; 144 private Property property; 145 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 146 private int line; 147 private int col; 148 private SpecialElement special; 149 private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation 150 private String explicitType; // for xsi:type attribute 151 private Element parentForValidator; 152 private boolean hasParentForValidator; 153 private String path; 154 private List<ValidationMessage> messages; 155 private boolean prohibited; 156 private boolean required; 157 private int descendentCount; 158 private int instanceId; 159 private boolean isNull; 160 private Base source; 161 private boolean ignorePropertyOrder; 162 private FhirFormat format; 163 private Object nativeObject; 164 private List<SliceDefinition> sliceDefinitions; 165 166 public Element(String name) { 167 super(); 168 this.name = name; 169 } 170 171 public Element(Element other) { 172 super(); 173 name = other.name; 174 type = other.type; 175 property = other.property; 176 elementProperty = other.elementProperty; 177 special = other.special; 178 } 179 180 public Element(String name, Property property) { 181 super(); 182 this.name = name; 183 this.property = property; 184 if (property.isResource()) { 185 children = new NamedItemList<>(); 186 } 187 } 188 189 public Element(String name, Property property, String type, String value) { 190 super(); 191 this.name = name; 192 this.property = property; 193 this.type = type; 194 this.value = value; 195 } 196 197 public void updateProperty(Property property, SpecialElement special, Property elementProperty) { 198 this.property = property; 199 this.elementProperty = elementProperty; 200 this.special = special; 201 } 202 203 public SpecialElement getSpecial() { 204 return special; 205 } 206 207 public String getName() { 208 return name; 209 } 210 211 public String getType() { 212 if (type == null) 213 return property.getType(name); 214 else 215 return type; 216 } 217 218 public String getValue() { 219 return value; 220 } 221 222 public boolean hasChildren() { 223 return !(children == null || children.isEmpty()); 224 } 225 226 public NamedItemList<Element> getChildren() { 227 if (children == null) 228 children = new NamedItemList<Element>(); 229 return children; 230 } 231 232 public boolean hasComments() { 233 return !(comments == null || comments.isEmpty()); 234 } 235 236 public List<String> getComments() { 237 if (comments == null) 238 comments = new ArrayList<String>(); 239 return comments; 240 } 241 242 public Property getProperty() { 243 return property; 244 } 245 246 public void setValue(String value) { 247 this.value = value; 248 } 249 250 public Element setType(String type) { 251 this.type = type; 252 return this; 253 254 } 255 256 public boolean isNull() { 257 return isNull; 258 } 259 260 public void setNull(boolean isNull) { 261 this.isNull = isNull; 262 } 263 264 public boolean hasValue() { 265 return value != null; 266 } 267 268 public List<Element> getChildrenByName(String name) { 269 return children.getByName(name); 270 } 271 272 public void numberChildren() { 273 if (children == null) 274 return; 275 276 String last = ""; 277 int index = 0; 278 for (Element child : children) { 279 if (child.getProperty().isList()) { 280 if (last.equals(child.getName())) { 281 index++; 282 } else { 283 last = child.getName(); 284 index = 0; 285 } 286 child.index = index; 287 } else { 288 child.index = -1; 289 } 290 child.numberChildren(); 291 } 292 } 293 294 public int getIndex() { 295 return index; 296 } 297 298 public boolean hasIndex() { 299 return index > -1; 300 } 301 302 public void setIndex(int index) { 303 this.index = index; 304 } 305 306 public String getChildValue(String name) { 307 if (children == null) 308 return null; 309 for (Element child : children) { 310 if (name.equals(child.getName())) 311 return child.getValue(); 312 } 313 for (Element child : children) { 314 if (name.equals(child.getNameBase())) 315 return child.getValue(); 316 } 317 return null; 318 } 319 320 private String getNameBase() { 321 if (property.isChoice()) { 322 return property.getName().replace("[x]", ""); 323 } else { 324 return getName(); 325 } 326 } 327 328 public void setChildValue(String name, String value) { 329 if (children == null) 330 children = new NamedItemList<Element>(); 331 for (Element child : children) { 332 if (name.equals(child.getName())) { 333 if (!child.isPrimitive()) 334 throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")"); 335 child.setValue(value); 336 } 337 } 338 339 try { 340 setProperty(name.hashCode(), name, new StringType(value)); 341 } catch (FHIRException e) { 342 throw new Error(e); 343 } 344 } 345 346 public List<Element> getChildren(String name) { 347 List<Element> res = new ArrayList<Element>(); 348 if (children.size() > 20) { 349 List<Element> l = children.getByName(name); 350 if (l != null) { 351 res.addAll(l); 352 } 353 } else { 354 if (children != null) 355 for (Element child : children) { 356 if (name.equals(child.getName())) 357 res.add(child); 358 } 359 } 360 return res; 361 } 362 363 public boolean hasType() { 364 if (type == null) 365 return property.hasType(name); 366 else 367 return true; 368 } 369 370 @Override 371 public String fhirType() { 372 return getType(); 373 } 374 375 @Override 376 public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { 377 if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) { 378// String tn = getType(); 379// throw new Error(tn+" not done yet"); 380 Base[] b = new Base[1]; 381 b[0] = new StringType(value); 382 return b; 383 } 384 385 List<Base> result = new ArrayList<Base>(); 386 if (children != null) { 387 if (children.size() > 20) { 388 List<Element> l = children.getByName(name); 389 if (l != null) { 390 result.addAll(l); 391 } 392 } else { 393 for (Element child : children) { 394 if (child.getName().equals(name)) { 395 result.add(child); 396 } 397 if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]")) { 398 result.add(child); 399 } 400 } 401 } 402 } 403 if (result.isEmpty() && checkValid) { 404// throw new FHIRException("not determined yet"); 405 } 406 return result.toArray(new Base[result.size()]); 407 } 408 409 @Override 410 protected void listChildren(List<org.hl7.fhir.r5.model.Property> childProps) { 411 if (children != null) { 412 Map<String, org.hl7.fhir.r5.model.Property> map = new HashMap<String, org.hl7.fhir.r5.model.Property>(); 413 for (Element c : children) { 414 org.hl7.fhir.r5.model.Property p = map.get(c.getName()); 415 if (p == null) { 416 p = new org.hl7.fhir.r5.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c); 417 childProps.add(p); 418 map.put(c.getName(), p); 419 420 } else 421 p.getValues().add(c); 422 } 423 } 424 } 425 426 @Override 427 public Base setProperty(int hash, String name, Base value) throws FHIRException { 428 if ("xhtml".equals(getType()) && (hash == "value".hashCode())) { 429 this.xhtml = TypeConvertor.castToXhtml(value); 430 this.value = TypeConvertor.castToXhtmlString(value); 431 return this; 432 } 433 if (isPrimitive() && (hash == "value".hashCode())) { 434 this.value = TypeConvertor.castToString(value).asStringValue(); 435 return this; 436 } 437 438 if (!value.isPrimitive() && !(value instanceof Element)) { 439 if (isDataType(value)) 440 value = convertToElement(property.getChild(name), value); 441 else 442 throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type"); 443 } 444 445 if (children == null) 446 children = new NamedItemList<Element>(); 447 Element childForValue = null; 448 449 // look through existing children 450 for (Element child : children) { 451 if (child.getName().equals(name)) { 452 if (!child.isList()) { 453 childForValue = child; 454 break; 455 } else { 456 Element ne = new Element(child).setFormat(format); 457 children.add(ne); 458 numberChildren(); 459 childForValue = ne; 460 break; 461 } 462 } 463 } 464 465 int i = 0; 466 if (childForValue == null) 467 for (Property p : property.getChildProperties(this.name, type)) { 468 int t = -1; 469 for (int c =0; c < children.size(); c++) { 470 Element e = children.get(c); 471 if (p.getName().equals(e.getName())) 472 t = c; 473 } 474 if (t >= i) 475 i = t+1; 476 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 477 Element ne = new Element(name, p).setFormat(format); 478 children.add(i, ne); 479 childForValue = ne; 480 break; 481 } 482 } 483 484 if (childForValue == null) 485 throw new Error("Cannot set property "+name+" on "+this.name); 486 else if (value.isPrimitive()) { 487 if (childForValue.property.getName().endsWith("[x]")) 488 childForValue.name = name+Utilities.capitalize(value.fhirType()); 489 childForValue.setValue(value.primitiveValue()); 490 } else { 491 Element ve = (Element) value; 492 childForValue.type = ve.getType(); 493 if (childForValue.property.getName().endsWith("[x]")) 494 childForValue.name = name+Utilities.capitalize(childForValue.type); 495 else if (value.isResource()) { 496 if (childForValue.elementProperty == null) 497 childForValue.elementProperty = childForValue.property; 498 childForValue.property = ve.property; 499 childForValue.special = SpecialElement.BUNDLE_ENTRY; 500 } 501 if (ve.children != null) { 502 if (childForValue.children == null) 503 childForValue.children = new NamedItemList<Element>(); 504 else 505 childForValue.children.clear(); 506 childForValue.children.addAll(ve.children); 507 } 508 } 509 return childForValue; 510 } 511 512 private Base convertToElement(Property prop, Base v) throws FHIRException { 513 return new ObjectConverter(property.getContext()).convert(prop, (DataType) v); 514 } 515 516 private boolean isDataType(Base v) { 517 return v instanceof DataType && property.getContextUtils().getTypeNames().contains(v.fhirType()); 518 } 519 520 @Override 521 public Base makeProperty(int hash, String name) throws FHIRException { 522 if (isPrimitive() && (hash == "value".hashCode())) { 523 return new StringType(value); 524 } else { 525 return makeElement(name); 526 } 527 } 528 529 public Element makeElement(String name) throws FHIRException { 530 if (children == null) 531 children = new NamedItemList<Element>(); 532 533 // look through existing children 534 for (Element child : children) { 535 if (child.getName().equals(name)) { 536 if (!child.isList()) { 537 return child; 538 } else { 539 Element ne = new Element(child).setFormat(format); 540 children.add(ne); 541 numberChildren(); 542 return ne; 543 } 544 } 545 } 546 547 for (Property p : property.getChildProperties(this.name, type)) { 548 if (p.getName().equals(name)) { 549 Element ne = new Element(name, p).setFormat(format); 550 children.add(ne); 551 return ne; 552 } else if (p.getDefinition().isChoice() && name.startsWith(p.getName().replace("[x]", ""))) { 553 String type = name.substring(p.getName().length()-3); 554 if (property.getContext().isPrimitiveType(Utilities.uncapitalize(type))) { 555 type = Utilities.uncapitalize(type); 556 } 557 Element ne = new Element(name, p).setFormat(format); 558 ne.setType(type); 559 children.add(ne); 560 return ne; 561 562 } 563 } 564 565 throw new Error("Unrecognised name "+name+" on "+this.name); 566 } 567 568 public Element forceElement(String name) throws FHIRException { 569 if (children == null) 570 children = new NamedItemList<Element>(); 571 572 // look through existing children 573 for (Element child : children) { 574 if (child.getName().equals(name)) { 575 return child; 576 } 577 } 578 579 for (Property p : property.getChildProperties(this.name, type)) { 580 if (p.getName().equals(name)) { 581 Element ne = new Element(name, p).setFormat(format); 582 children.add(ne); 583 return ne; 584 } 585 } 586 587 throw new Error("Unrecognised name "+name+" on "+this.name); 588 } 589 590 591 private int maxToInt(String max) { 592 if (max.equals("*")) 593 return Integer.MAX_VALUE; 594 else 595 return Integer.parseInt(max); 596 } 597 598 @Override 599 public boolean isPrimitive() { 600 return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name)); 601 } 602 603 @Override 604 public boolean isBooleanPrimitive() { 605 return isPrimitive() && ("boolean".equals(type) || "boolean".equals(property.getType(name))); 606 } 607 608 @Override 609 public boolean isResource() { 610 return property.isResource(); 611 } 612 613 614 @Override 615 public boolean hasPrimitiveValue() { 616 //return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name); 617 return super.hasPrimitiveValue(); 618 } 619 620 @Override 621 public boolean canHavePrimitiveValue() { 622 return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name); 623 } 624 625 626 @Override 627 public String primitiveValue() { 628 if (isPrimitive() || value != null) 629 return value; 630 else { 631 if (canHavePrimitiveValue() && children != null) { 632 for (Element c : children) { 633 if (c.getName().equals("value")) 634 return c.primitiveValue(); 635 } 636 } 637 return null; 638 } 639 } 640 641 // for the validator 642 public int line() { 643 return line; 644 } 645 646 public int col() { 647 return col; 648 } 649 650 public Element markLocation(int line, int col) { 651 this.line = line; 652 this.col = col; 653 return this; 654 } 655 656 public Element markLocation(SourceLocation loc) { 657 this.line = loc.getLine(); 658 this.col = loc.getColumn(); 659 return this; 660 } 661 662 public Element markLocation(Element src) { 663 this.line = src.line(); 664 this.col = src.col(); 665 return this; 666 } 667 668 public void clearDecorations() { 669 clearUserData("fhir.decorations"); 670 for (Element e : children) { 671 e.clearDecorations(); 672 } 673 } 674 675 public void markValidation(StructureDefinition profile, ElementDefinition definition) { 676 @SuppressWarnings("unchecked") 677 List<ElementDecoration> decorations = (List<ElementDecoration>) getUserData("fhir.decorations"); 678 if (decorations == null) { 679 decorations = new ArrayList<>(); 680 setUserData("fhir.decorations", decorations); 681 } 682 decorations.add(new ElementDecoration(DecorationType.TYPE, profile.getWebPath(), definition.getPath())); 683 if (definition.getId() != null && tail(definition.getId()).contains(":")) { 684 String[] details = tail(definition.getId()).split(":"); 685 decorations.add(new ElementDecoration(DecorationType.SLICE, null, details[1])); 686 } 687 } 688 689 private String tail(String id) { 690 return id.contains(".") ? id.substring(id.lastIndexOf(".")+1) : id; 691 } 692 693 public Element getNamedChild(String name) { 694 return getNamedChild(name, true); 695 } 696 public Element getNamedChild(String name, boolean exception) { 697 if (children == null) 698 return null; 699 if (children.size() > 20) { 700 List<Element> l = children.getByName(name); 701 if (l == null || l.size() == 0) { 702 // try the other way (in case of complicated naming rules) 703 } else if (l.size() > 1 && exception) { 704 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 705 } else { 706 return l.get(0); 707 } 708 } else { 709 710 } 711 Element result = null; 712 713 for (Element child : children) { 714 if (child.getName() != null && name != null && child.getProperty() != null && child.getProperty().getDefinition() != null && child.fhirType() != null) { 715 if (child.getName().equals(name) || (child.getName().length() > child.fhirType().length() && child.getName().substring(0, child.getName().length() - child.fhirType().length()).equals(name) && child.getProperty().getDefinition().isChoice())) { 716 if (result == null) 717 result = child; 718 else 719 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 720 } 721 } 722 } 723 return result; 724 } 725 726 public void getNamedChildren(String name, List<Element> list) { 727 if (children != null) 728 if (children.size() > 20) { 729 List<Element> l = children.getByName(name); 730 if (l != null) { 731 list.addAll(l); 732 } 733 } else { 734 for (Element child : children) 735 if (child.getName().equals(name)) 736 list.add(child); 737 } 738 } 739 740 public String getNamedChildValue(String name) { 741 return getNamedChildValue(name, true); 742 } 743 744 public String getNamedChildValue(String name, boolean exception) { 745 Element child = getNamedChild(name, exception); 746 return child == null ? null : child.value; 747 } 748 749 public void getNamedChildrenWithWildcard(String string, List<Element> values) { 750 Validate.isTrue(string.endsWith("[x]")); 751 752 String start = string.substring(0, string.length() - 3); 753 if (children != null) { 754 for (Element child : children) { 755 if (child.getName().startsWith(start)) { 756 values.add(child); 757 } 758 } 759 } 760 } 761 762 763 public XhtmlNode getXhtml() { 764 return xhtml; 765 } 766 767 public Element setXhtml(XhtmlNode xhtml) { 768 this.xhtml = xhtml; 769 return this; 770 } 771 772 @Override 773 public boolean isEmpty() { 774 // GG: this used to also test !"".equals(value). 775 // the condition where "" is empty and there are no children is an error, and so this really only manifested as an issue in corner cases technical testing of the validator / FHIRPath. 776 // it should not cause any problems in real life. 777 if (value != null) { 778 return false; 779 } 780 for (Element next : getChildren()) { 781 if (!next.isEmpty()) { 782 return false; 783 } 784 } 785 return true; 786 } 787 788 public Property getElementProperty() { 789 return elementProperty; 790 } 791 792 public boolean hasElementProperty() { 793 return elementProperty != null; 794 } 795 796 public boolean hasChild(String name) { 797 return getNamedChild(name, true) != null; 798 } 799 800 public boolean hasChild(String name, boolean exception) { 801 return getNamedChild(name, exception) != null; 802 } 803 804 public boolean hasChildren(String name) { 805 if (children != null) 806 for (Element child : children) 807 if (child.getName().equals(name)) 808 return true; 809 return false; 810 } 811 812 @Override 813 public String toString() { 814 if (name.equals(fhirType()) && isResource()) { 815 return fhirType()+"/"+getIdBase() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 816 817 } else if (isResource()) { 818 return name+"="+fhirType()+"/"+getIdBase()+ "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 819 } else { 820 return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 821 } 822 } 823 824 @Override 825 public String getIdBase() { 826 return getChildValue("id"); 827 } 828 829 @Override 830 public void setIdBase(String value) { 831 setChildValue("id", value); 832 } 833 834 835 @Override 836 public boolean equalsDeep(Base other) { 837 if (!super.equalsDeep(other)) 838 return false; 839 if (isPrimitive() && primitiveValue() != null && other.isPrimitive()) 840 return primitiveValue().equals(other.primitiveValue()); 841 if (isPrimitive() || other.isPrimitive()) 842 return false; 843 Set<String> processed = new HashSet<String>(); 844 for (org.hl7.fhir.r5.model.Property p : children()) { 845 String name = p.getName(); 846 processed.add(name); 847 org.hl7.fhir.r5.model.Property o = other.getChildByName(name); 848 if (!equalsDeep(p, o)) 849 return false; 850 } 851 for (org.hl7.fhir.r5.model.Property p : children()) { 852 String name = p.getName(); 853 if (!processed.contains(name)) { 854 org.hl7.fhir.r5.model.Property o = other.getChildByName(name); 855 if (!equalsDeep(p, o)) 856 return false; 857 } 858 } 859 return true; 860 } 861 862 private boolean equalsDeep(org.hl7.fhir.r5.model.Property p, org.hl7.fhir.r5.model.Property o) { 863 if (o == null || p == null) 864 return false; 865 if (p.getValues().size() != o.getValues().size()) 866 return false; 867 for (int i = 0; i < p.getValues().size(); i++) 868 if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true)) 869 return false; 870 return true; 871 } 872 873 @Override 874 public boolean equalsShallow(Base other) { 875 if (!super.equalsShallow(other)) 876 return false; 877 if (isPrimitive() && other.isPrimitive()) 878 return primitiveValue().equals(other.primitiveValue()); 879 if (isPrimitive() || other.isPrimitive()) 880 return false; 881 return true; //? 882 } 883 884 public DataType asType() throws FHIRException { 885 return new ObjectConverter(property.getContext()).convertToType(this); 886 } 887 888 @Override 889 public boolean isMetadataBased() { 890 return true; 891 } 892 893 public boolean isList() { 894 if (elementProperty != null) 895 return elementProperty.isList(); 896 else 897 return property.isList(); 898 } 899 900 public boolean isBaseList() { 901 if (elementProperty != null) 902 return elementProperty.isBaseList(); 903 else 904 return property.isBaseList(); 905 } 906 907 @Override 908 public String[] getTypesForProperty(int hash, String name) throws FHIRException { 909 Property p = property.getChildSimpleName(this.name, name); 910 if (p != null) { 911 Set<String> types = new HashSet<String>(); 912 for (TypeRefComponent tr : p.getDefinition().getType()) { 913 types.add(tr.getCode()); 914 } 915 return types.toArray(new String[]{}); 916 } 917 return super.getTypesForProperty(hash, name); 918 919 } 920 921 public void sort() { 922 if (children != null) { 923 List<Element> remove = new ArrayList<Element>(); 924 for (Element child : children) { 925 child.sort(); 926 if (child.isEmpty()) 927 remove.add(child); 928 } 929 children.removeAll(remove); 930 children.sort(new ElementSortComparator(this, this.property)); 931 } 932 } 933 934 public class ElementSortComparator implements Comparator<Element> { 935 private List<ElementDefinition> children; 936 public ElementSortComparator(Element e, Property property) { 937 String tn = e.getType(); 938 StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, null)); 939 if (sd != null && !sd.getAbstract()) 940 children = sd.getSnapshot().getElement(); 941 else 942 children = property.getStructure().getSnapshot().getElement(); 943 } 944 945 @Override 946 public int compare(Element e0, Element e1) { 947 int i0 = find(e0); 948 int i1 = find(e1); 949 return Integer.compare(i0, i1); 950 } 951 private int find(Element e0) { 952 int i = e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) : children.indexOf(e0.property.getDefinition()); 953 return i; 954 } 955 956 } 957 958 public class ICodingImpl implements ICoding { 959 private String system; 960 private String version; 961 private String code; 962 private String display; 963 private boolean doesSystem; 964 private boolean doesVersion; 965 private boolean doesCode; 966 private boolean doesDisplay; 967 public ICodingImpl(boolean doesCode, boolean doesSystem, boolean doesVersion, boolean doesDisplay) { 968 super(); 969 this.doesCode = doesCode; 970 this.doesSystem = doesSystem; 971 this.doesVersion = doesVersion; 972 this.doesDisplay = doesDisplay; 973 } 974 public String getSystem() { 975 return system; 976 } 977 public String getVersion() { 978 return version; 979 } 980 public String getCode() { 981 return code; 982 } 983 public String getDisplay() { 984 return display; 985 } 986 public boolean hasSystem() { 987 return !Utilities.noString(system); 988 } 989 public boolean hasVersion() { 990 return !Utilities.noString(version); 991 } 992 public boolean hasCode() { 993 return !Utilities.noString(code); 994 } 995 public boolean hasDisplay() { 996 return !Utilities.noString(display); 997 } 998 public boolean supportsSystem() { 999 return doesSystem; 1000 } 1001 public boolean supportsVersion() { 1002 return doesVersion; 1003 } 1004 public boolean supportsCode() { 1005 return doesCode; 1006 } 1007 public boolean supportsDisplay() { 1008 return doesDisplay; 1009 } 1010 } 1011 1012 public ICoding getAsICoding() throws FHIRException { 1013 if ("code".equals(fhirType())) { 1014 if (property.getDefinition().getBinding().getStrength() != BindingStrength.REQUIRED) 1015 return null; 1016 ICodingImpl c = new ICodingImpl(true, true, false, false); 1017 c.code = primitiveValue(); 1018 ValueSetExpansionOutcome vse = property.getContext().expandVS(property.getStructure(), property.getDefinition().getBinding(), true, false); 1019 if (vse.getValueset() == null) 1020 return null; 1021 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 1022 if (cc.getCode().equals(c.code)) { 1023 c.system = cc.getSystem(); 1024 if (cc.hasVersion()) { 1025 c.doesVersion = true; 1026 c.version = cc.getVersion(); 1027 } 1028 if (cc.hasDisplay()) { 1029 c.doesDisplay = true; 1030 c.display = cc.getDisplay(); 1031 } 1032 } 1033 } 1034 if (c.system == null) 1035 return null; 1036 return c; 1037 } else if ("Coding".equals(fhirType())) { 1038 ICodingImpl c = new ICodingImpl(true, true, true, true); 1039 c.system = getNamedChildValue("system", false); 1040 c.code = getNamedChildValue("code", false); 1041 c.display = getNamedChildValue("display", false); 1042 c.version = getNamedChildValue("version", false); 1043 return c; 1044 } else if ("Quantity".equals(fhirType())) { 1045 ICodingImpl c = new ICodingImpl(true, true, false, false); 1046 c.system = getNamedChildValue("system", false); 1047 c.code = getNamedChildValue("code", false); 1048 return c; 1049 } else 1050 return null; 1051 } 1052 1053 public String getExplicitType() { 1054 return explicitType; 1055 } 1056 1057 public void setExplicitType(String explicitType) { 1058 this.explicitType = explicitType; 1059 } 1060 1061 public boolean hasDescendant(Element element) { 1062 if (children != null) { 1063 for (Element child : children) { 1064 if (element == child || child.hasDescendant(element)) { 1065 return true; 1066 } 1067 } 1068 } 1069 return false; 1070 } 1071 1072 public Element getExtension(String url) { 1073 if (children != null) { 1074 for (Element child : children) { 1075 if (extensionList.contains(child.getName())) { 1076 String u = child.getChildValue("url"); 1077 if (url.equals(u)) { 1078 return child; 1079 } 1080 } 1081 } 1082 } 1083 return null; 1084 } 1085 1086 public Base getExtensionValue(String url) { 1087 if (children != null) { 1088 for (Element child : children) { 1089 if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) { 1090 String u = child.getChildValue("url"); 1091 if (url.equals(u)) { 1092 return child.getNamedChild("value", false); 1093 } 1094 } 1095 } 1096 } 1097 return null; 1098 } 1099 1100 public boolean hasExtension(String url) { 1101 if (children != null) { 1102 for (Element child : children) { 1103 if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) { 1104 String u = child.getChildValue("url"); 1105 if (url.equals(u)) { 1106 return true; 1107 } 1108 } 1109 } 1110 } 1111 return false; 1112 } 1113 1114 /** 1115 * this is set by the instance validator. There's no reason to maintain this when working with an element tree, and so it should be ignored outside the validator 1116 */ 1117 public Element getParentForValidator() { 1118 if (!hasParentForValidator) { 1119 throw new Error("Parent not set"); 1120 } 1121 return parentForValidator; 1122 } 1123 1124 public void setParentForValidator(Element parentForValidator) { 1125 this.parentForValidator = parentForValidator; 1126 this.hasParentForValidator = true; 1127 } 1128 1129 public boolean hasParentForValidator() { 1130 return hasParentForValidator; 1131 } 1132 1133 public void clear() { 1134 comments = null; 1135 children.clear(); 1136 property = null; 1137 elementProperty = null; 1138 xhtml = null; 1139 path = null; 1140 } 1141 1142 public String getPath() { 1143 return path; 1144 } 1145 1146 public void setPath(String path) { 1147 this.path = path; 1148 } 1149 1150 public void addMessage(ValidationMessage vm) { 1151 if (messages == null) { 1152 messages = new ArrayList<>(); 1153 } 1154 messages.add(vm); 1155 } 1156 1157 public boolean hasMessages() { 1158 return messages != null && !messages.isEmpty(); 1159 } 1160 1161 public List<ValidationMessage> getMessages() { 1162 return messages; 1163 } 1164 1165 public void removeChild(String name) { 1166 children.removeIf(n -> name.equals(n.getName())); 1167 } 1168 1169 public boolean isProhibited() { 1170 return prohibited; 1171 } 1172 1173 public void setProhibited(boolean prohibited) { 1174 this.prohibited = prohibited; 1175 } 1176 1177 public boolean isRequired() { 1178 return required; 1179 } 1180 1181 public void setRequired(boolean required) { 1182 this.required = required; 1183 } 1184 1185 public int getDescendentCount() { 1186 return descendentCount; 1187 } 1188 1189 public void setDescendentCount(int descendentCount) { 1190 this.descendentCount = descendentCount; 1191 } 1192 1193 public int countDescendents() { 1194 if (descendentCount > 0) { 1195 return descendentCount; 1196 } else if (children != null) { 1197 descendentCount = children.size(); 1198 for (Element e : children) { 1199 descendentCount = descendentCount + e.countDescendents(); 1200 } 1201 } else { 1202 descendentCount = 0; 1203 } 1204 return descendentCount; 1205 } 1206 1207 public int getInstanceId() { 1208 return instanceId; 1209 } 1210 1211 public void setInstanceId(int instanceId) { 1212 this.instanceId = instanceId; 1213 } 1214 1215 1216 @Override 1217 public boolean hasValidationInfo() { 1218 return hasSource() ? source.hasValidationInfo() : super.hasValidationInfo(); 1219 } 1220 1221 @Override 1222 public List<ValidationInfo> getValidationInfo() { 1223 return hasSource() ? source.getValidationInfo() : super.getValidationInfo(); 1224 } 1225 1226 @Override 1227 public ValidationInfo addDefinition(StructureDefinition source, ElementDefinition defn, ValidationMode mode) { 1228 if (this.source != null) { 1229 return this.source.addDefinition(source, defn, mode); 1230 } else { 1231 return super.addDefinition(source, defn, mode); 1232 } 1233 } 1234 1235 public boolean hasSource() { 1236 return source != null; 1237 } 1238 1239 1240 public Base getSource() { 1241 return source; 1242 } 1243 1244 public void setSource(Base source) { 1245 this.source = source; 1246 } 1247 1248 public void printToOutput() { 1249 printToOutput(System.out, ""); 1250 1251 } 1252 1253 public void printToOutput(PrintStream stream) { 1254 printToOutput(stream, ""); 1255 1256 } 1257 1258 private void printToOutput(PrintStream out, String indent) { 1259 String s = indent+name +(index == -1 ? "" : "["+index+"]") +(special != null ? "$"+special.toHuman(): "")+ (type!= null || explicitType != null ? " : "+type+(explicitType != null ? "/'"+explicitType+"'" : "") : ""); 1260 if (isNull) { 1261 s = s + " = (null)"; 1262 } else if (value != null) { 1263 s = s + " = '"+value+"'"; 1264 } else if (xhtml != null) { 1265 s = s + " = (xhtml)"; 1266 } 1267 if (property != null) { 1268 s = s +" {"+property.summary(); 1269 if (elementProperty != null) { 1270 s = s +" -> "+elementProperty.summary(); 1271 } 1272 s = s + "}"; 1273 } 1274 if (line > 0) { 1275 s = s + " (l"+line+":c"+col+")"; 1276 } 1277 out.println(s); 1278 if (children != null) { 1279 for (Element child : children) { 1280 child.printToOutput(out, indent+" "); 1281 } 1282 } 1283 1284 } 1285 1286 private String msgCounts() { 1287 int e = 0; 1288 int w = 0; 1289 int h = 0; 1290 for (ValidationMessage msg : messages) { 1291 switch (msg.getLevel()) { 1292 case ERROR: 1293 e++; 1294 break; 1295 case FATAL: 1296 e++; 1297 break; 1298 case INFORMATION: 1299 h++; 1300 break; 1301 case NULL: 1302 break; 1303 case WARNING: 1304 w++; 1305 break; 1306 default: 1307 break; 1308 } 1309 } 1310 return "e:"+e+",w:"+w+",h:"+h; 1311 } 1312 1313 public void populatePaths(String path) { 1314 if (path == null) { 1315 path = fhirType(); 1316 } 1317 setPath(path); 1318 if (children != null) { 1319 for (Element n : children) { 1320 n.populatePaths(path+"."+n.getName()); 1321 } 1322 } 1323 1324 } 1325 1326 public String fhirTypeRoot() { 1327 if (fhirType().contains("/")) { 1328 return fhirType().substring(fhirType().lastIndexOf("/")+1); 1329 } else { 1330 return fhirType(); 1331 } 1332 } 1333 1334 public void setElement(String string, Element map) { 1335 throw new Error("Not done yet"); 1336 } 1337 1338 public Element addElement(String name) { 1339 if (children == null) 1340 children = new NamedItemList<Element>(); 1341 1342 for (Property p : property.getChildProperties(this.name, type)) { 1343 if (p.getName().equals(name)) { 1344 if (!p.isList() && hasChild(name, false)) { 1345 throw new Error(name+" on "+this.name+" is not a list, so can't add an element"); 1346 } 1347 Element ne = new Element(name, p).setFormat(format); 1348 children.add(ne); 1349 return ne; 1350 } 1351 // polymorphic support 1352 if (p.getName().endsWith("[x]")) { 1353 String base = p.getName().substring(0, p.getName().length()-3); 1354 1355 if (name.startsWith(base)) { 1356 String type = name.substring(base.length()); 1357 if (p.getContextUtils().isPrimitiveType(Utilities.uncapitalize(type))) { 1358 type = Utilities.uncapitalize(type); 1359 } 1360 if (p.canBeType(type)) { 1361 Element ne = new Element(name, p).setFormat(format); 1362 ne.setType(type); 1363 children.add(ne); 1364 return ne; 1365 } 1366 } 1367 } 1368 } 1369 1370 throw new Error("Unrecognised property '"+name+"' on "+this.name); 1371 } 1372 1373 @Override 1374 public Base copy() { 1375 Element element = new Element(this); 1376 this.copyValues(element); 1377 return element; 1378 } 1379 1380 @Override 1381 public void copyValues(Base dst) { 1382 super.copyValues(dst); 1383 1384 Element dest = (Element) dst; 1385 if (comments != null) { 1386 dest.comments = new ArrayList<>(); 1387 dest.comments.addAll(comments); 1388 } else { 1389 dest.comments = null; 1390 } 1391 dest.value = value; 1392 if (children != null) { 1393 dest.children = new NamedItemList<>(); 1394 for (Element child : children) { 1395 dest.children.add((Element) child.copy()); 1396 } 1397 } else { 1398 dest.children = null; 1399 } 1400 dest.line = line; 1401 dest.col = col; 1402 dest.xhtml = xhtml; 1403 dest.explicitType = explicitType; 1404 dest.hasParentForValidator = false; 1405 dest.path = path; 1406 dest.messages = null; 1407 dest.prohibited = prohibited; 1408 dest.required = required; 1409 dest.descendentCount = descendentCount; 1410 dest.instanceId = instanceId; 1411 dest.isNull = isNull; 1412 dest.source = source; 1413 dest.format = format; 1414 } 1415 1416 public Base setProperty(String name, Base value) throws FHIRException { 1417 setChildValue(name, value.primitiveValue()); 1418 return this; 1419 } 1420 1421 public boolean isIgnorePropertyOrder() { 1422 return ignorePropertyOrder; 1423 } 1424 1425 public void setIgnorePropertyOrder(boolean ignorePropertyOrder) { 1426 this.ignorePropertyOrder = ignorePropertyOrder; 1427 if (children != null) { 1428 for (Element e : children) { 1429 e.setIgnorePropertyOrder(ignorePropertyOrder); 1430 } 1431 } 1432 } 1433 1434 1435 private String webPath; 1436 public boolean hasWebPath() { 1437 return webPath != null; 1438 } 1439 public String getWebPath() { 1440 return webPath; 1441 } 1442 public void setWebPath(String webPath) { 1443 this.webPath = webPath; 1444 } 1445 1446 public String getTranslation(String lang) { 1447 for (Element e : getChildren()) { 1448 if (e.fhirType().equals("Extension")) { 1449 String url = e.getNamedChildValue("url", false); 1450 if (ToolingExtensions.EXT_TRANSLATION.equals(url)) { 1451 String l = null; 1452 String v = null; 1453 for (Element g : e.getChildren()) { 1454 if (g.fhirType().equals("Extension")) { 1455 String u = g.getNamedChildValue("url", false); 1456 if ("lang".equals(u)) { 1457 l = g.getNamedChildValue("value", false); 1458 } else if ("value".equals(u)) { 1459 v = g.getNamedChildValue("value", false); 1460 } 1461 } 1462 } 1463 if (LanguageUtils.langsMatch(lang, l)) { 1464 return v; 1465 } 1466 } 1467 } 1468 } 1469 return null; 1470 } 1471 1472 public String getBasePath() { 1473 if (property.getStructure().hasExtension(ToolingExtensions.EXT_RESOURCE_IMPLEMENTS)) { 1474 StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ExtensionsUtils.getExtensionString(property.getStructure(), ToolingExtensions.EXT_RESOURCE_IMPLEMENTS)); 1475 if (sd != null) { 1476 ElementDefinition ed = sd.getSnapshot().getElementByPath(property.getDefinition().getPath().replace(property.getStructure().getType(), sd.getType())); 1477 if (ed != null) { 1478 return ed.getBase().getPath(); 1479 } 1480 } 1481 } 1482 return property.getDefinition().getBase().getPath(); 1483 } 1484 1485 public void setTranslation(String lang, String translation) { 1486 for (Element e : getChildren()) { 1487 if (e.fhirType().equals("Extension")) { 1488 String url = e.getNamedChildValue("url", false); 1489 if (ToolingExtensions.EXT_TRANSLATION.equals(url)) { 1490 String l = null; 1491 Element v = null; 1492 for (Element g : e.getChildren()) { 1493 if (g.fhirType().equals("Extension")) { 1494 String u = g.getNamedChildValue("url", false); 1495 if ("lang".equals(u)) { 1496 l = g.getNamedChildValue("value", false); 1497 } else if ("value".equals(u)) { 1498 v = g.getNamedChild("value", false); 1499 } 1500 } 1501 } 1502 if (LanguageUtils.langsMatch(lang, l)) { 1503 if (v == null) { 1504 Element ext = e.addElement("extension"); 1505 ext.addElement("url").setValue("value"); 1506 ext.addElement("valueString").setValue(translation); 1507 } else { 1508 v.setValue(translation); 1509 } 1510 } 1511 } 1512 } 1513 } 1514 Element t = addElement("extension"); 1515 t.addElement("url").setValue(ToolingExtensions.EXT_TRANSLATION); 1516 1517 Element ext = t.addElement("extension"); 1518 ext.addElement("url").setValue("lang"); 1519 ext.addElement("valueCode").setValue(lang); 1520 1521 ext = t.addElement("extension"); 1522 ext.addElement("url").setValue("content"); 1523 ext.addElement("valueString").setValue(translation); 1524 } 1525 1526 @Override 1527 public String getListName() { 1528 if (getProperty().getName().endsWith("[x]")) { 1529 String n = getProperty().getName(); 1530 return n.substring(0, n.length()-3); 1531 } else { 1532 return getName(); 1533 } 1534 } 1535 1536 public FhirFormat getFormat() { 1537 return format; 1538 } 1539 1540 public Element setFormat(FhirFormat format) { 1541 this.format = format; 1542 return this; 1543 } 1544 1545 public Object getNativeObject() { 1546 return nativeObject; 1547 } 1548 1549 public Element setNativeObject(Object nativeObject) { 1550 this.nativeObject = nativeObject; 1551 return this; 1552 } 1553 1554 public void removeExtension(String url) { 1555 List<Element> rem = new ArrayList<>(); 1556 for (Element e : children) { 1557 if ("extension".equals(e.getName()) && url.equals(e.getChildValue("url"))) { 1558 rem.add(e); 1559 } 1560 } 1561 children.removeAll(rem); 1562 } 1563 1564 public void addSliceDefinition(StructureDefinition profile, ElementDefinition definition, ElementDefinition slice) { 1565 if (sliceDefinitions == null) { 1566 sliceDefinitions = new ArrayList<>(); 1567 } 1568 sliceDefinitions.add(new SliceDefinition(profile, definition, slice)); 1569 } 1570 1571 public boolean hasSlice(StructureDefinition sd, String sliceName) { 1572 if (sliceDefinitions != null) { 1573 for (SliceDefinition def : sliceDefinitions) { 1574 if (def.profile == sd && sliceName.equals(def.definition.getSliceName())) { 1575 return true; 1576 } 1577 } 1578 } 1579 return false; 1580 } 1581 1582 public FhirPublication getFHIRPublicationVersion() { 1583 return FhirPublication.fromCode(property.getStructure().getVersion()); 1584 } 1585 1586}