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 void setChildValue(String name, Base value) { 347 if (children == null) 348 children = new NamedItemList<Element>(); 349 for (Element child : children) { 350 if (name.equals(child.getName())) { 351 if (!child.isPrimitive()) 352 throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")"); 353 child.setValue(value.primitiveValue()); 354 } 355 } 356 357 try { 358 setProperty(name.hashCode(), name, value); 359 } catch (FHIRException e) { 360 throw new Error(e); 361 } 362 } 363 364 public List<Element> getChildren(String name) { 365 List<Element> res = new ArrayList<Element>(); 366 if (children.size() > 20) { 367 List<Element> l = children.getByName(name); 368 if (l != null) { 369 res.addAll(l); 370 } 371 } else { 372 if (children != null) 373 for (Element child : children) { 374 if (name.equals(child.getName())) 375 res.add(child); 376 } 377 } 378 return res; 379 } 380 381 public boolean hasType() { 382 if (type == null) 383 return property.hasType(name); 384 else 385 return true; 386 } 387 388 @Override 389 public String fhirType() { 390 return getType(); 391 } 392 393 @Override 394 public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { 395 if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) { 396// String tn = getType(); 397// throw new Error(tn+" not done yet"); 398 Base[] b = new Base[1]; 399 b[0] = new StringType(value); 400 return b; 401 } 402 403 List<Base> result = new ArrayList<Base>(); 404 if (children != null) { 405 if (children.size() > 20) { 406 List<Element> l = children.getByName(name); 407 if (l != null) { 408 result.addAll(l); 409 } 410 } else { 411 for (Element child : children) { 412 if (child.getName().equals(name)) { 413 result.add(child); 414 } 415 if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]")) { 416 result.add(child); 417 } 418 } 419 } 420 } 421 if (result.isEmpty() && checkValid) { 422// throw new FHIRException("not determined yet"); 423 } 424 return result.toArray(new Base[result.size()]); 425 } 426 427 @Override 428 protected void listChildren(List<org.hl7.fhir.r5.model.Property> childProps) { 429 if (children != null) { 430 Map<String, org.hl7.fhir.r5.model.Property> map = new HashMap<String, org.hl7.fhir.r5.model.Property>(); 431 for (Element c : children) { 432 org.hl7.fhir.r5.model.Property p = map.get(c.getName()); 433 if (p == null) { 434 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); 435 childProps.add(p); 436 map.put(c.getName(), p); 437 438 } else 439 p.getValues().add(c); 440 } 441 } 442 } 443 444 @Override 445 public Base setProperty(int hash, String name, Base value) throws FHIRException { 446 if ("xhtml".equals(getType()) && (hash == "value".hashCode())) { 447 this.xhtml = TypeConvertor.castToXhtml(value); 448 this.value = TypeConvertor.castToXhtmlString(value); 449 return this; 450 } 451 if (isPrimitive() && (hash == "value".hashCode())) { 452 this.value = TypeConvertor.castToString(value).asStringValue(); 453 return this; 454 } 455 456 if (!value.isPrimitive() && !(value instanceof Element)) { 457 if (isDataType(value)) 458 value = convertToElement(property.getChild(name), value); 459 else 460 throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type"); 461 } 462 463 if (children == null) 464 children = new NamedItemList<Element>(); 465 Element childForValue = null; 466 467 // look through existing children 468 for (Element child : children) { 469 if (child.getName().equals(name)) { 470 if (!child.isList()) { 471 childForValue = child; 472 break; 473 } else { 474 Element ne = new Element(child).setFormat(format); 475 children.add(ne); 476 numberChildren(); 477 childForValue = ne; 478 break; 479 } 480 } 481 } 482 483 int i = 0; 484 if (childForValue == null) 485 for (Property p : property.getChildProperties(this.name, type)) { 486 int t = -1; 487 for (int c =0; c < children.size(); c++) { 488 Element e = children.get(c); 489 if (p.getName().equals(e.getName())) 490 t = c; 491 } 492 if (t >= i) 493 i = t+1; 494 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 495 Element ne = new Element(name, p).setFormat(format); 496 children.add(i, ne); 497 childForValue = ne; 498 break; 499 } else if (p.getName().endsWith("[x]") && name.startsWith(p.getName().replace("[x]", ""))) { 500 Element ne = new Element(p.getName(), p).setFormat(format); 501 children.add(i, ne); 502 childForValue = ne; 503 break; 504 } 505 } 506 507 if (childForValue == null) 508 throw new Error("Cannot set property "+name+" on "+this.name); 509 else if (value.isPrimitive()) { 510 if (childForValue.property.getName().endsWith("[x]")) 511 childForValue.name = childForValue.name.replace("[x]", "")+Utilities.capitalize(value.fhirType()); 512 childForValue.setValue(value.primitiveValue()); 513 } else { 514 Element ve = (Element) value; 515 childForValue.type = ve.getType(); 516 if (childForValue.property.getName().endsWith("[x]")) 517 childForValue.name = name+Utilities.capitalize(childForValue.type); 518 else if (value.isResource()) { 519 if (childForValue.elementProperty == null) 520 childForValue.elementProperty = childForValue.property; 521 childForValue.property = ve.property; 522 childForValue.special = SpecialElement.BUNDLE_ENTRY; 523 } 524 if (ve.children != null) { 525 if (childForValue.children == null) 526 childForValue.children = new NamedItemList<Element>(); 527 else 528 childForValue.children.clear(); 529 childForValue.children.addAll(ve.children); 530 } 531 } 532 return childForValue; 533 } 534 535 private Base convertToElement(Property prop, Base v) throws FHIRException { 536 return new ObjectConverter(property.getContext()).convert(prop, (DataType) v); 537 } 538 539 private boolean isDataType(Base v) { 540 return v instanceof DataType && property.getContextUtils().getTypeNames().contains(v.fhirType()); 541 } 542 543 @Override 544 public Base makeProperty(int hash, String name) throws FHIRException { 545 if (isPrimitive() && (hash == "value".hashCode())) { 546 return new StringType(value); 547 } else { 548 return makeElement(name); 549 } 550 } 551 552 public Element makeElement(String name) throws FHIRException { 553 if (children == null) 554 children = new NamedItemList<Element>(); 555 556 // look through existing children 557 for (Element child : children) { 558 if (child.getName().equals(name)) { 559 if (!child.isList()) { 560 return child; 561 } else { 562 Element ne = new Element(child).setFormat(format); 563 children.add(ne); 564 numberChildren(); 565 return ne; 566 } 567 } 568 } 569 570 for (Property p : property.getChildProperties(this.name, type)) { 571 if (p.getName().equals(name)) { 572 Element ne = new Element(name, p).setFormat(format); 573 children.add(ne); 574 return ne; 575 } else if (p.getDefinition().isChoice() && name.startsWith(p.getName().replace("[x]", ""))) { 576 String type = name.substring(p.getName().length()-3); 577 if (property.getContext().isPrimitiveType(Utilities.uncapitalize(type))) { 578 type = Utilities.uncapitalize(type); 579 } 580 Element ne = new Element(name, p).setFormat(format); 581 ne.setType(type); 582 children.add(ne); 583 return ne; 584 585 } 586 } 587 588 throw new Error("Unrecognised name "+name+" on "+this.name); 589 } 590 591 public Element forceElement(String name) throws FHIRException { 592 if (children == null) 593 children = new NamedItemList<Element>(); 594 595 // look through existing children 596 for (Element child : children) { 597 if (child.getName().equals(name)) { 598 return child; 599 } 600 } 601 602 for (Property p : property.getChildProperties(this.name, type)) { 603 if (p.getName().equals(name)) { 604 Element ne = new Element(name, p).setFormat(format); 605 children.add(ne); 606 return ne; 607 } 608 } 609 610 throw new Error("Unrecognised name "+name+" on "+this.name); 611 } 612 613 614 private int maxToInt(String max) { 615 if (max.equals("*")) 616 return Integer.MAX_VALUE; 617 else 618 return Integer.parseInt(max); 619 } 620 621 @Override 622 public boolean isPrimitive() { 623 return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name)); 624 } 625 626 @Override 627 public boolean isBooleanPrimitive() { 628 return isPrimitive() && ("boolean".equals(type) || "boolean".equals(property.getType(name))); 629 } 630 631 @Override 632 public boolean isResource() { 633 return property.isResource(); 634 } 635 636 637 @Override 638 public boolean hasPrimitiveValue() { 639 //return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name); 640 return super.hasPrimitiveValue(); 641 } 642 643 @Override 644 public boolean canHavePrimitiveValue() { 645 return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name); 646 } 647 648 649 @Override 650 public String primitiveValue() { 651 if (isPrimitive() || value != null) 652 return value; 653 else { 654 if (canHavePrimitiveValue() && children != null) { 655 for (Element c : children) { 656 if (c.getName().equals("value")) 657 return c.primitiveValue(); 658 } 659 } 660 return null; 661 } 662 } 663 664 // for the validator 665 public int line() { 666 return line; 667 } 668 669 public int col() { 670 return col; 671 } 672 673 public Element markLocation(int line, int col) { 674 this.line = line; 675 this.col = col; 676 return this; 677 } 678 679 public Element markLocation(SourceLocation loc) { 680 this.line = loc.getLine(); 681 this.col = loc.getColumn(); 682 return this; 683 } 684 685 public Element markLocation(Element src) { 686 this.line = src.line(); 687 this.col = src.col(); 688 return this; 689 } 690 691 public void clearDecorations() { 692 clearUserData("fhir.decorations"); 693 for (Element e : children) { 694 e.clearDecorations(); 695 } 696 } 697 698 public void markValidation(StructureDefinition profile, ElementDefinition definition) { 699 @SuppressWarnings("unchecked") 700 List<ElementDecoration> decorations = (List<ElementDecoration>) getUserData("fhir.decorations"); 701 if (decorations == null) { 702 decorations = new ArrayList<>(); 703 setUserData("fhir.decorations", decorations); 704 } 705 decorations.add(new ElementDecoration(DecorationType.TYPE, profile.getWebPath(), definition.getPath())); 706 if (definition.getId() != null && tail(definition.getId()).contains(":")) { 707 String[] details = tail(definition.getId()).split(":"); 708 decorations.add(new ElementDecoration(DecorationType.SLICE, null, details[1])); 709 } 710 } 711 712 private String tail(String id) { 713 return id.contains(".") ? id.substring(id.lastIndexOf(".")+1) : id; 714 } 715 716 public Element getNamedChild(String name) { 717 return getNamedChild(name, true); 718 } 719 720 public Element getNamedChild(String name, boolean exception) { 721 if (children == null) 722 return null; 723 if (children.size() > 20) { 724 List<Element> l = children.getByName(name); 725 if (l == null || l.size() == 0) { 726 // try the other way (in case of complicated naming rules) 727 } else if (l.size() > 1 && exception) { 728 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 729 } else { 730 return l.get(0); 731 } 732 } else { 733 734 } 735 Element result = null; 736 737 for (Element child : children) { 738 if (child.getName() != null && name != null && child.getProperty() != null && child.getProperty().getDefinition() != null && child.fhirType() != null) { 739 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())) { 740 if (result == null) 741 result = child; 742 else 743 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 744 } 745 } 746 } 747 return result; 748 } 749 750 public void getNamedChildren(String name, List<Element> list) { 751 if (children != null) 752 if (children.size() > 20) { 753 List<Element> l = children.getByName(name); 754 if (l != null) { 755 list.addAll(l); 756 } 757 } else { 758 for (Element child : children) 759 if (child.getName().equals(name)) 760 list.add(child); 761 } 762 } 763 764 public String getNamedChildValue(String name) { 765 return getNamedChildValue(name, true); 766 } 767 768 public String getNamedChildValue(String name, boolean exception) { 769 Element child = getNamedChild(name, exception); 770 return child == null ? null : child.value; 771 } 772 773 public void getNamedChildrenWithWildcard(String string, List<Element> values) { 774 Validate.isTrue(string.endsWith("[x]")); 775 776 String start = string.substring(0, string.length() - 3); 777 if (children != null) { 778 for (Element child : children) { 779 if (child.getName().startsWith(start)) { 780 values.add(child); 781 } 782 } 783 } 784 } 785 786 787 public XhtmlNode getXhtml() { 788 return xhtml; 789 } 790 791 public Element setXhtml(XhtmlNode xhtml) { 792 this.xhtml = xhtml; 793 return this; 794 } 795 796 @Override 797 public boolean isEmpty() { 798 // GG: this used to also test !"".equals(value). 799 // 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. 800 // it should not cause any problems in real life. 801 if (value != null) { 802 return false; 803 } 804 for (Element next : getChildren()) { 805 if (!next.isEmpty()) { 806 return false; 807 } 808 } 809 return true; 810 } 811 812 public Property getElementProperty() { 813 return elementProperty; 814 } 815 816 public boolean hasElementProperty() { 817 return elementProperty != null; 818 } 819 820 public boolean hasChild(String name) { 821 return getNamedChild(name, true) != null; 822 } 823 824 public boolean hasChild(String name, boolean exception) { 825 return getNamedChild(name, exception) != null; 826 } 827 828 public boolean hasChildren(String name) { 829 if (children != null) 830 for (Element child : children) 831 if (child.getName().equals(name)) 832 return true; 833 return false; 834 } 835 836 @Override 837 public String toString() { 838 if (name.equals(fhirType()) && isResource()) { 839 return fhirType()+"/"+getIdBase() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 840 841 } else if (isResource()) { 842 return name+"="+fhirType()+"/"+getIdBase()+ "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 843 } else { 844 return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 845 } 846 } 847 848 @Override 849 public String getIdBase() { 850 return getChildValue("id"); 851 } 852 853 @Override 854 public void setIdBase(String value) { 855 setChildValue("id", value); 856 } 857 858 859 @Override 860 public boolean equalsDeep(Base other) { 861 if (!super.equalsDeep(other)) 862 return false; 863 if (isPrimitive() && primitiveValue() != null && other.isPrimitive()) 864 return primitiveValue().equals(other.primitiveValue()); 865 if (isPrimitive() || other.isPrimitive()) 866 return false; 867 Set<String> processed = new HashSet<String>(); 868 for (org.hl7.fhir.r5.model.Property p : children()) { 869 String name = p.getName(); 870 processed.add(name); 871 org.hl7.fhir.r5.model.Property o = other.getChildByName(name); 872 if (!equalsDeep(p, o)) 873 return false; 874 } 875 for (org.hl7.fhir.r5.model.Property p : children()) { 876 String name = p.getName(); 877 if (!processed.contains(name)) { 878 org.hl7.fhir.r5.model.Property o = other.getChildByName(name); 879 if (!equalsDeep(p, o)) 880 return false; 881 } 882 } 883 return true; 884 } 885 886 private boolean equalsDeep(org.hl7.fhir.r5.model.Property p, org.hl7.fhir.r5.model.Property o) { 887 if (o == null || p == null) 888 return false; 889 if (p.getValues().size() != o.getValues().size()) 890 return false; 891 for (int i = 0; i < p.getValues().size(); i++) 892 if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true)) 893 return false; 894 return true; 895 } 896 897 @Override 898 public boolean equalsShallow(Base other) { 899 if (!super.equalsShallow(other)) 900 return false; 901 if (isPrimitive() && other.isPrimitive()) 902 return primitiveValue().equals(other.primitiveValue()); 903 if (isPrimitive() || other.isPrimitive()) 904 return false; 905 return true; //? 906 } 907 908 public DataType asType() throws FHIRException { 909 return new ObjectConverter(property.getContext()).convertToType(this); 910 } 911 912 @Override 913 public boolean isMetadataBased() { 914 return true; 915 } 916 917 public boolean isList() { 918 if (elementProperty != null) 919 return elementProperty.isList(); 920 else 921 return property.isList(); 922 } 923 924 public boolean isBaseList() { 925 if (elementProperty != null) 926 return elementProperty.isBaseList(); 927 else 928 return property.isBaseList(); 929 } 930 931 @Override 932 public String[] getTypesForProperty(int hash, String name) throws FHIRException { 933 Property p = property.getChildSimpleName(this.name, name); 934 if (p != null) { 935 Set<String> types = new HashSet<String>(); 936 for (TypeRefComponent tr : p.getDefinition().getType()) { 937 types.add(tr.getCode()); 938 } 939 return types.toArray(new String[]{}); 940 } 941 return super.getTypesForProperty(hash, name); 942 943 } 944 945 public void sort() { 946 if (children != null) { 947 List<Element> remove = new ArrayList<Element>(); 948 for (Element child : children) { 949 child.sort(); 950 if (child.isEmpty()) 951 remove.add(child); 952 } 953 children.removeAll(remove); 954 children.sort(new ElementSortComparator(this, this.property)); 955 } 956 } 957 958 public class ElementSortComparator implements Comparator<Element> { 959 private List<ElementDefinition> children; 960 public ElementSortComparator(Element e, Property property) { 961 String tn = e.getType(); 962 StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, null)); 963 if (sd != null && !sd.getAbstract()) 964 children = sd.getSnapshot().getElement(); 965 else 966 children = property.getStructure().getSnapshot().getElement(); 967 } 968 969 @Override 970 public int compare(Element e0, Element e1) { 971 int i0 = find(e0); 972 int i1 = find(e1); 973 return Integer.compare(i0, i1); 974 } 975 private int find(Element e0) { 976 int i = e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) : children.indexOf(e0.property.getDefinition()); 977 return i; 978 } 979 980 } 981 982 public class ICodingImpl implements ICoding { 983 private String system; 984 private String version; 985 private String code; 986 private String display; 987 private boolean doesSystem; 988 private boolean doesVersion; 989 private boolean doesCode; 990 private boolean doesDisplay; 991 public ICodingImpl(boolean doesCode, boolean doesSystem, boolean doesVersion, boolean doesDisplay) { 992 super(); 993 this.doesCode = doesCode; 994 this.doesSystem = doesSystem; 995 this.doesVersion = doesVersion; 996 this.doesDisplay = doesDisplay; 997 } 998 public String getSystem() { 999 return system; 1000 } 1001 public String getVersion() { 1002 return version; 1003 } 1004 public String getCode() { 1005 return code; 1006 } 1007 public String getDisplay() { 1008 return display; 1009 } 1010 public boolean hasSystem() { 1011 return !Utilities.noString(system); 1012 } 1013 public boolean hasVersion() { 1014 return !Utilities.noString(version); 1015 } 1016 public boolean hasCode() { 1017 return !Utilities.noString(code); 1018 } 1019 public boolean hasDisplay() { 1020 return !Utilities.noString(display); 1021 } 1022 public boolean supportsSystem() { 1023 return doesSystem; 1024 } 1025 public boolean supportsVersion() { 1026 return doesVersion; 1027 } 1028 public boolean supportsCode() { 1029 return doesCode; 1030 } 1031 public boolean supportsDisplay() { 1032 return doesDisplay; 1033 } 1034 } 1035 1036 public ICoding getAsICoding() throws FHIRException { 1037 if ("code".equals(fhirType())) { 1038 if (property.getDefinition().getBinding().getStrength() != BindingStrength.REQUIRED) 1039 return null; 1040 ICodingImpl c = new ICodingImpl(true, true, false, false); 1041 c.code = primitiveValue(); 1042 ValueSetExpansionOutcome vse = property.getContext().expandVS(property.getStructure(), property.getDefinition().getBinding(), true, false); 1043 if (vse.getValueset() == null) 1044 return null; 1045 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 1046 if (cc.getCode().equals(c.code)) { 1047 c.system = cc.getSystem(); 1048 if (cc.hasVersion()) { 1049 c.doesVersion = true; 1050 c.version = cc.getVersion(); 1051 } 1052 if (cc.hasDisplay()) { 1053 c.doesDisplay = true; 1054 c.display = cc.getDisplay(); 1055 } 1056 } 1057 } 1058 if (c.system == null) 1059 return null; 1060 return c; 1061 } else if ("Coding".equals(fhirType())) { 1062 ICodingImpl c = new ICodingImpl(true, true, true, true); 1063 c.system = getNamedChildValue("system", false); 1064 c.code = getNamedChildValue("code", false); 1065 c.display = getNamedChildValue("display", false); 1066 c.version = getNamedChildValue("version", false); 1067 return c; 1068 } else if ("Quantity".equals(fhirType())) { 1069 ICodingImpl c = new ICodingImpl(true, true, false, false); 1070 c.system = getNamedChildValue("system", false); 1071 c.code = getNamedChildValue("code", false); 1072 return c; 1073 } else 1074 return null; 1075 } 1076 1077 public String getExplicitType() { 1078 return explicitType; 1079 } 1080 1081 public void setExplicitType(String explicitType) { 1082 this.explicitType = explicitType; 1083 } 1084 1085 public boolean hasDescendant(Element element) { 1086 if (children != null) { 1087 for (Element child : children) { 1088 if (element == child || child.hasDescendant(element)) { 1089 return true; 1090 } 1091 } 1092 } 1093 return false; 1094 } 1095 1096 public Element getExtension(String url) { 1097 if (children != null) { 1098 for (Element child : children) { 1099 if (extensionList.contains(child.getName())) { 1100 String u = child.getChildValue("url"); 1101 if (url.equals(u)) { 1102 return child; 1103 } 1104 } 1105 } 1106 } 1107 return null; 1108 } 1109 1110 public Base getExtensionValue(String url) { 1111 if (children != null) { 1112 for (Element child : children) { 1113 if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) { 1114 String u = child.getChildValue("url"); 1115 if (url.equals(u)) { 1116 return child.getNamedChild("value", false); 1117 } 1118 } 1119 } 1120 } 1121 return null; 1122 } 1123 1124 public boolean hasExtension(String url) { 1125 if (children != null) { 1126 for (Element child : children) { 1127 if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) { 1128 String u = child.getChildValue("url"); 1129 if (url.equals(u)) { 1130 return true; 1131 } 1132 } 1133 } 1134 } 1135 return false; 1136 } 1137 1138 /** 1139 * 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 1140 */ 1141 public Element getParentForValidator() { 1142 if (!hasParentForValidator) { 1143 throw new Error("Parent not set"); 1144 } 1145 return parentForValidator; 1146 } 1147 1148 public void setParentForValidator(Element parentForValidator) { 1149 this.parentForValidator = parentForValidator; 1150 this.hasParentForValidator = true; 1151 } 1152 1153 public boolean hasParentForValidator() { 1154 return hasParentForValidator; 1155 } 1156 1157 public void clear() { 1158 comments = null; 1159 children.clear(); 1160 property = null; 1161 elementProperty = null; 1162 xhtml = null; 1163 path = null; 1164 } 1165 1166 public String getPath() { 1167 return path; 1168 } 1169 1170 public void setPath(String path) { 1171 this.path = path; 1172 } 1173 1174 public void addMessage(ValidationMessage vm) { 1175 if (messages == null) { 1176 messages = new ArrayList<>(); 1177 } 1178 messages.add(vm); 1179 } 1180 1181 public boolean hasMessages() { 1182 return messages != null && !messages.isEmpty(); 1183 } 1184 1185 public List<ValidationMessage> getMessages() { 1186 return messages; 1187 } 1188 1189 public void removeChild(String name) { 1190 if (children.removeIf(n -> name.equals(n.getName()))) { 1191 children.clearMap(); 1192 } 1193 } 1194 1195 public boolean isProhibited() { 1196 return prohibited; 1197 } 1198 1199 public void setProhibited(boolean prohibited) { 1200 this.prohibited = prohibited; 1201 } 1202 1203 public boolean isRequired() { 1204 return required; 1205 } 1206 1207 public void setRequired(boolean required) { 1208 this.required = required; 1209 } 1210 1211 public int getDescendentCount() { 1212 return descendentCount; 1213 } 1214 1215 public void setDescendentCount(int descendentCount) { 1216 this.descendentCount = descendentCount; 1217 } 1218 1219 public int countDescendents() { 1220 if (descendentCount > 0) { 1221 return descendentCount; 1222 } else if (children != null) { 1223 descendentCount = children.size(); 1224 for (Element e : children) { 1225 descendentCount = descendentCount + e.countDescendents(); 1226 } 1227 } else { 1228 descendentCount = 0; 1229 } 1230 return descendentCount; 1231 } 1232 1233 public int getInstanceId() { 1234 return instanceId; 1235 } 1236 1237 public void setInstanceId(int instanceId) { 1238 this.instanceId = instanceId; 1239 } 1240 1241 1242 @Override 1243 public boolean hasValidationInfo() { 1244 return hasSource() ? source.hasValidationInfo() : super.hasValidationInfo(); 1245 } 1246 1247 @Override 1248 public List<ValidationInfo> getValidationInfo() { 1249 return hasSource() ? source.getValidationInfo() : super.getValidationInfo(); 1250 } 1251 1252 @Override 1253 public ValidationInfo addDefinition(StructureDefinition source, ElementDefinition defn, ValidationMode mode) { 1254 if (this.source != null) { 1255 return this.source.addDefinition(source, defn, mode); 1256 } else { 1257 return super.addDefinition(source, defn, mode); 1258 } 1259 } 1260 1261 public boolean hasSource() { 1262 return source != null; 1263 } 1264 1265 1266 public Base getSource() { 1267 return source; 1268 } 1269 1270 public void setSource(Base source) { 1271 this.source = source; 1272 } 1273 1274 public void printToOutput() { 1275 printToOutput(System.out, ""); 1276 1277 } 1278 1279 public void printToOutput(PrintStream stream) { 1280 printToOutput(stream, ""); 1281 1282 } 1283 1284 private void printToOutput(PrintStream out, String indent) { 1285 String s = indent+name +(index == -1 ? "" : "["+index+"]") +(special != null ? "$"+special.toHuman(): "")+ (type!= null || explicitType != null ? " : "+type+(explicitType != null ? "/'"+explicitType+"'" : "") : ""); 1286 if (isNull) { 1287 s = s + " = (null)"; 1288 } else if (value != null) { 1289 s = s + " = '"+value+"'"; 1290 } else if (xhtml != null) { 1291 s = s + " = (xhtml)"; 1292 } 1293 if (property != null) { 1294 s = s +" {"+property.summary(); 1295 if (elementProperty != null) { 1296 s = s +" -> "+elementProperty.summary(); 1297 } 1298 s = s + "}"; 1299 } 1300 if (line > 0) { 1301 s = s + " (l"+line+":c"+col+")"; 1302 } 1303 out.println(s); 1304 if (children != null) { 1305 for (Element child : children) { 1306 child.printToOutput(out, indent+" "); 1307 } 1308 } 1309 1310 } 1311 1312 private String msgCounts() { 1313 int e = 0; 1314 int w = 0; 1315 int h = 0; 1316 for (ValidationMessage msg : messages) { 1317 switch (msg.getLevel()) { 1318 case ERROR: 1319 e++; 1320 break; 1321 case FATAL: 1322 e++; 1323 break; 1324 case INFORMATION: 1325 h++; 1326 break; 1327 case NULL: 1328 break; 1329 case WARNING: 1330 w++; 1331 break; 1332 default: 1333 break; 1334 } 1335 } 1336 return "e:"+e+",w:"+w+",h:"+h; 1337 } 1338 1339 public void populatePaths(String path) { 1340 if (path == null) { 1341 path = fhirType(); 1342 } 1343 setPath(path); 1344 if (children != null) { 1345 for (Element n : children) { 1346 n.populatePaths(path+"."+n.getName()); 1347 } 1348 } 1349 1350 } 1351 1352 public String fhirTypeRoot() { 1353 if (fhirType().contains("/")) { 1354 return fhirType().substring(fhirType().lastIndexOf("/")+1); 1355 } else { 1356 return fhirType(); 1357 } 1358 } 1359 1360 public void setElement(String string, Element map) { 1361 throw new Error("Not done yet"); 1362 } 1363 1364 public Element addElement(String name) { 1365 if (children == null) 1366 children = new NamedItemList<Element>(); 1367 1368 for (Property p : property.getChildProperties(this.name, type)) { 1369 if (p.getName().equals(name)) { 1370 if (!p.isList() && hasChild(name, false)) { 1371 throw new Error(name+" on "+this.name+" is not a list, so can't add an element"); 1372 } 1373 Element ne = new Element(name, p).setFormat(format); 1374 children.add(ne); 1375 return ne; 1376 } 1377 // polymorphic support 1378 if (p.getName().endsWith("[x]")) { 1379 String base = p.getName().substring(0, p.getName().length()-3); 1380 1381 if (name.startsWith(base)) { 1382 String type = name.substring(base.length()); 1383 if (p.getContextUtils().isPrimitiveType(Utilities.uncapitalize(type))) { 1384 type = Utilities.uncapitalize(type); 1385 } 1386 if (p.canBeType(type)) { 1387 Element ne = new Element(name, p).setFormat(format); 1388 ne.setType(type); 1389 children.add(ne); 1390 return ne; 1391 } 1392 } 1393 } 1394 } 1395 1396 throw new Error("Unrecognised property '"+name+"' on "+this.name); 1397 } 1398 1399 @Override 1400 public Base copy() { 1401 Element element = new Element(this); 1402 this.copyValues(element); 1403 return element; 1404 } 1405 1406 @Override 1407 public void copyValues(Base dst) { 1408 super.copyValues(dst); 1409 1410 Element dest = (Element) dst; 1411 if (comments != null) { 1412 dest.comments = new ArrayList<>(); 1413 dest.comments.addAll(comments); 1414 } else { 1415 dest.comments = null; 1416 } 1417 dest.value = value; 1418 if (children != null) { 1419 dest.children = new NamedItemList<>(); 1420 for (Element child : children) { 1421 dest.children.add((Element) child.copy()); 1422 } 1423 } else { 1424 dest.children = null; 1425 } 1426 dest.line = line; 1427 dest.col = col; 1428 dest.xhtml = xhtml; 1429 dest.explicitType = explicitType; 1430 dest.hasParentForValidator = false; 1431 dest.path = path; 1432 dest.messages = null; 1433 dest.prohibited = prohibited; 1434 dest.required = required; 1435 dest.descendentCount = descendentCount; 1436 dest.instanceId = instanceId; 1437 dest.isNull = isNull; 1438 dest.source = source; 1439 dest.format = format; 1440 } 1441 1442 public Base setProperty(String name, Base value) throws FHIRException { 1443 setChildValue(name, value.primitiveValue()); 1444 return this; 1445 } 1446 1447 public boolean isIgnorePropertyOrder() { 1448 return ignorePropertyOrder; 1449 } 1450 1451 public void setIgnorePropertyOrder(boolean ignorePropertyOrder) { 1452 this.ignorePropertyOrder = ignorePropertyOrder; 1453 if (children != null) { 1454 for (Element e : children) { 1455 e.setIgnorePropertyOrder(ignorePropertyOrder); 1456 } 1457 } 1458 } 1459 1460 1461 private String webPath; 1462 public boolean hasWebPath() { 1463 return webPath != null; 1464 } 1465 public String getWebPath() { 1466 return webPath; 1467 } 1468 public void setWebPath(String webPath) { 1469 this.webPath = webPath; 1470 } 1471 1472 public String getTranslation(String lang) { 1473 for (Element e : getChildren()) { 1474 if (e.fhirType().equals("Extension")) { 1475 String url = e.getNamedChildValue("url", false); 1476 if (ToolingExtensions.EXT_TRANSLATION.equals(url)) { 1477 String l = null; 1478 String v = null; 1479 for (Element g : e.getChildren()) { 1480 if (g.fhirType().equals("Extension")) { 1481 String u = g.getNamedChildValue("url", false); 1482 if ("lang".equals(u)) { 1483 l = g.getNamedChildValue("value", false); 1484 } else if ("value".equals(u)) { 1485 v = g.getNamedChildValue("value", false); 1486 } 1487 } 1488 } 1489 if (LanguageUtils.langsMatch(lang, l)) { 1490 return v; 1491 } 1492 } 1493 } 1494 } 1495 return null; 1496 } 1497 1498 public String getBasePath() { 1499 if (property.getStructure().hasExtension(ToolingExtensions.EXT_RESOURCE_IMPLEMENTS)) { 1500 StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ExtensionsUtils.getExtensionString(property.getStructure(), ToolingExtensions.EXT_RESOURCE_IMPLEMENTS)); 1501 if (sd != null) { 1502 ElementDefinition ed = sd.getSnapshot().getElementByPath(property.getDefinition().getPath().replace(property.getStructure().getType(), sd.getType())); 1503 if (ed != null) { 1504 return ed.getBase().getPath(); 1505 } 1506 } 1507 } 1508 return property.getDefinition().getBase().getPath(); 1509 } 1510 1511 public void setTranslation(String lang, String translation) { 1512 for (Element e : getChildren()) { 1513 if (e.fhirType().equals("Extension")) { 1514 String url = e.getNamedChildValue("url", false); 1515 if (ToolingExtensions.EXT_TRANSLATION.equals(url)) { 1516 String l = null; 1517 Element v = null; 1518 for (Element g : e.getChildren()) { 1519 if (g.fhirType().equals("Extension")) { 1520 String u = g.getNamedChildValue("url", false); 1521 if ("lang".equals(u)) { 1522 l = g.getNamedChildValue("value", false); 1523 } else if ("value".equals(u)) { 1524 v = g.getNamedChild("value", false); 1525 } 1526 } 1527 } 1528 if (LanguageUtils.langsMatch(lang, l)) { 1529 if (v == null) { 1530 Element ext = e.addElement("extension"); 1531 ext.addElement("url").setValue("value"); 1532 ext.addElement("valueString").setValue(translation); 1533 } else { 1534 v.setValue(translation); 1535 } 1536 } 1537 } 1538 } 1539 } 1540 Element t = addElement("extension"); 1541 t.addElement("url").setValue(ToolingExtensions.EXT_TRANSLATION); 1542 1543 Element ext = t.addElement("extension"); 1544 ext.addElement("url").setValue("lang"); 1545 ext.addElement("valueCode").setValue(lang); 1546 1547 ext = t.addElement("extension"); 1548 ext.addElement("url").setValue("content"); 1549 ext.addElement("valueString").setValue(translation); 1550 } 1551 1552 @Override 1553 public String getListName() { 1554 if (getProperty().getName().endsWith("[x]")) { 1555 String n = getProperty().getName(); 1556 return n.substring(0, n.length()-3); 1557 } else { 1558 return getName(); 1559 } 1560 } 1561 1562 public FhirFormat getFormat() { 1563 return format; 1564 } 1565 1566 public Element setFormat(FhirFormat format) { 1567 this.format = format; 1568 return this; 1569 } 1570 1571 public Object getNativeObject() { 1572 return nativeObject; 1573 } 1574 1575 public Element setNativeObject(Object nativeObject) { 1576 this.nativeObject = nativeObject; 1577 return this; 1578 } 1579 1580 public void removeExtension(String url) { 1581 List<Element> rem = new ArrayList<>(); 1582 for (Element e : children) { 1583 if ("extension".equals(e.getName()) && url.equals(e.getChildValue("url"))) { 1584 rem.add(e); 1585 } 1586 } 1587 children.removeAll(rem); 1588 } 1589 1590 public void addSliceDefinition(StructureDefinition profile, ElementDefinition definition, ElementDefinition slice) { 1591 if (sliceDefinitions == null) { 1592 sliceDefinitions = new ArrayList<>(); 1593 } 1594 sliceDefinitions.add(new SliceDefinition(profile, definition, slice)); 1595 } 1596 1597 public boolean hasSlice(StructureDefinition sd, String sliceName) { 1598 if (sliceDefinitions != null) { 1599 for (SliceDefinition def : sliceDefinitions) { 1600 if (def.profile == sd && sliceName.equals(def.definition.getSliceName())) { 1601 return true; 1602 } 1603 } 1604 } 1605 return false; 1606 } 1607 1608 public FhirPublication getFHIRPublicationVersion() { 1609 return FhirPublication.fromCode(property.getStructure().getVersion()); 1610 } 1611 1612}