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