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