001package org.hl7.fhir.dstu3.elementmodel; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import static org.apache.commons.lang3.StringUtils.isNotBlank; 035 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.Comparator; 039import java.util.HashSet; 040import java.util.List; 041import java.util.Set; 042 043import org.apache.commons.lang3.Validate; 044import org.hl7.fhir.dstu3.model.Base; 045import org.hl7.fhir.dstu3.model.ElementDefinition; 046import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; 047import org.hl7.fhir.dstu3.model.StringType; 048import org.hl7.fhir.dstu3.model.StructureDefinition; 049import org.hl7.fhir.dstu3.model.Type; 050import org.hl7.fhir.exceptions.FHIRException; 051import org.hl7.fhir.utilities.Utilities; 052import org.hl7.fhir.utilities.xhtml.XhtmlNode; 053 054/** 055 * This class represents the underlying reference model of FHIR 056 * 057 * A resource is nothing but a set of elements, where every element has a 058 * name, maybe a stated type, maybe an id, and either a value or child elements 059 * (one or the other, but not both or neither) 060 * 061 * @author Grahame Grieve 062 * 063 */ 064public class Element extends Base { 065 066 067 public enum SpecialElement { 068 CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, PARAMETER; 069 070 public static SpecialElement fromProperty(Property property) { 071 if (property.getStructure().getIdElement().getIdPart().equals("Parameters")) 072 return PARAMETER; 073 if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("resource")) 074 return BUNDLE_ENTRY; 075 if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("outcome")) 076 return BUNDLE_OUTCOME; 077 if (property.getName().equals("contained")) 078 return CONTAINED; 079 throw new Error("Unknown resource containing a native resource: "+property.getDefinition().getId()); 080 } 081 } 082 083 private List<String> comments;// not relevant for production, but useful in documentation 084 private String name; 085 private String type; 086 private String value; 087 private int index = -1; 088 private List<Element> children; 089 private Property property; 090 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 091 private int line; 092 private int col; 093 private SpecialElement special; 094 private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation 095 096 public Element(String name) { 097 super(); 098 this.name = name; 099 } 100 101 public Element(Element other) { 102 super(); 103 name = other.name; 104 type = other.type; 105 property = other.property; 106 elementProperty = other.elementProperty; 107 special = other.special; 108 } 109 110 public Element(String name, Property property) { 111 super(); 112 this.name = name; 113 this.property = property; 114 } 115 116 public Element(String name, Property property, String type, String value) { 117 super(); 118 this.name = name; 119 this.property = property; 120 this.type = type; 121 this.value = value; 122 } 123 124 public void updateProperty(Property property, SpecialElement special, Property elementProperty) { 125 this.property = property; 126 this.elementProperty = elementProperty; 127 this.special = special; 128 } 129 130 public SpecialElement getSpecial() { 131 return special; 132 } 133 134 public String getName() { 135 return name; 136 } 137 138 public String getType() { 139 if (type == null) 140 return property.getType(name); 141 else 142 return type; 143 } 144 145 public String getValue() { 146 return value; 147 } 148 149 public boolean hasChildren() { 150 return !(children == null || children.isEmpty()); 151 } 152 153 public List<Element> getChildren() { 154 if (children == null) 155 children = new ArrayList<Element>(); 156 return children; 157 } 158 159 public boolean hasComments() { 160 return !(comments == null || comments.isEmpty()); 161 } 162 163 public List<String> getComments() { 164 if (comments == null) 165 comments = new ArrayList<String>(); 166 return comments; 167 } 168 169 public Property getProperty() { 170 return property; 171 } 172 173 public void setValue(String value) { 174 this.value = value; 175 } 176 177 public void setType(String type) { 178 this.type = type; 179 180 } 181 182 public boolean hasValue() { 183 return value != null; 184 } 185 186 public List<Element> getChildrenByName(String name) { 187 List<Element> res = new ArrayList<Element>(); 188 if (hasChildren()) { 189 for (Element child : children) 190 if (name.equals(child.getName())) 191 res.add(child); 192 } 193 return res; 194 } 195 196 public void numberChildren() { 197 if (children == null) 198 return; 199 200 String last = ""; 201 int index = 0; 202 for (Element child : children) { 203 if (child.getProperty().isList()) { 204 if (last.equals(child.getName())) { 205 index++; 206 } else { 207 last = child.getName(); 208 index = 0; 209 } 210 child.index = index; 211 } else { 212 child.index = -1; 213 } 214 child.numberChildren(); 215 } 216 } 217 218 public int getIndex() { 219 return index; 220 } 221 222 public boolean hasIndex() { 223 return index > -1; 224 } 225 226 public void setIndex(int index) { 227 this.index = index; 228 } 229 230 public String getChildValue(String name) { 231 if (children == null) 232 return null; 233 for (Element child : children) { 234 if (name.equals(child.getName())) 235 return child.getValue(); 236 } 237 return null; 238 } 239 240 public void setChildValue(String name, String value) { 241 if (children == null) 242 children = new ArrayList<Element>(); 243 for (Element child : children) { 244 if (name.equals(child.getName())) { 245 if (!child.isPrimitive()) 246 throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")"); 247 child.setValue(value); 248 } 249 } 250 try { 251 setProperty(name.hashCode(), name, new StringType(value)); 252 } catch (FHIRException e) { 253 throw new Error(e); 254 } 255 } 256 257 public List<Element> getChildren(String name) { 258 List<Element> res = new ArrayList<Element>(); 259 if (children != null) 260 for (Element child : children) { 261 if (name.equals(child.getName())) 262 res.add(child); 263 } 264 return res; 265 } 266 267 public boolean hasType() { 268 if (type == null) 269 return property.hasType(name); 270 else 271 return true; 272 } 273 274 @Override 275 public String fhirType() { 276 return getType(); 277 } 278 279 @Override 280 public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { 281 if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) { 282// String tn = getType(); 283// throw new Error(tn+" not done yet"); 284 Base[] b = new Base[1]; 285 b[0] = new StringType(value); 286 return b; 287 } 288 289 List<Base> result = new ArrayList<Base>(); 290 if (children != null) { 291 for (Element child : children) { 292 if (child.getName().equals(name)) 293 result.add(child); 294 if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]")) 295 result.add(child); 296 } 297 } 298 if (result.isEmpty() && checkValid) { 299// throw new FHIRException("not determined yet"); 300 } 301 return result.toArray(new Base[result.size()]); 302 } 303 304 @Override 305 protected void listChildren(List<org.hl7.fhir.dstu3.model.Property> childProps) { 306 if (children != null) { 307 for (Element c : children) { 308 childProps.add(new org.hl7.fhir.dstu3.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c)); 309 } 310 } 311 } 312 313 @Override 314 public Base setProperty(int hash, String name, Base value) throws FHIRException { 315 if (isPrimitive() && (hash == "value".hashCode())) { 316 this.value = castToString(value).asStringValue(); 317 return this; 318 } 319 if ("xhtml".equals(getType()) && (hash == "value".hashCode())) { 320 this.xhtml = castToXhtml(value); 321 this.value = castToXhtmlString(value); 322 return this; 323 } 324 325 if (!value.isPrimitive() && !(value instanceof Element)) { 326 if (isDataType(value)) 327 value = convertToElement(property.getChild(name), value); 328 else 329 throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type"); 330 } 331 332 if (children == null) 333 children = new ArrayList<Element>(); 334 Element childForValue = null; 335 336 // look through existing children 337 for (Element child : children) { 338 if (child.getName().equals(name)) { 339 if (!child.isList()) { 340 childForValue = child; 341 break; 342 } else { 343 Element ne = new Element(child); 344 children.add(ne); 345 numberChildren(); 346 childForValue = ne; 347 break; 348 } 349 } 350 } 351 352 if (childForValue == null) 353 for (Property p : property.getChildProperties(this.name, type)) { 354 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 355 Element ne = new Element(name, p); 356 children.add(ne); 357 childForValue = ne; 358 break; 359 } 360 } 361 362 if (childForValue == null) 363 throw new Error("Cannot set property "+name+" on "+this.name); 364 else if (value.isPrimitive()) { 365 if (childForValue.property.getName().endsWith("[x]")) 366 childForValue.name = name+Utilities.capitalize(value.fhirType()); 367 childForValue.setValue(value.primitiveValue()); 368 } else { 369 Element ve = (Element) value; 370 childForValue.type = ve.getType(); 371 if (childForValue.property.getName().endsWith("[x]")) 372 childForValue.name = name+Utilities.capitalize(childForValue.type); 373 else if (value.isResource()) { 374 if (childForValue.elementProperty == null) 375 childForValue.elementProperty = childForValue.property; 376 childForValue.property = ve.property; 377 childForValue.special = SpecialElement.BUNDLE_ENTRY; 378 } 379 if (ve.children != null) { 380 if (childForValue.children == null) 381 childForValue.children = new ArrayList<Element>(); 382 else 383 childForValue.children.clear(); 384 childForValue.children.addAll(ve.children); 385 } 386 } 387 return childForValue; 388 } 389 390 private Base convertToElement(Property prop, Base v) throws FHIRException { 391 return new ObjectConverter(property.getContext()).convert(prop, (Type) v); 392 } 393 394 private boolean isDataType(Base v) { 395 return v instanceof Type && property.getContext().getTypeNames().contains(v.fhirType()); 396 } 397 398 @Override 399 public Base makeProperty(int hash, String name) throws FHIRException { 400 if (isPrimitive() && (hash == "value".hashCode())) { 401 return new StringType(value); 402 } 403 404 if (children == null) 405 children = new ArrayList<Element>(); 406 407 // look through existing children 408 for (Element child : children) { 409 if (child.getName().equals(name)) { 410 if (!child.isList()) { 411 return child; 412 } else { 413 Element ne = new Element(child); 414 children.add(ne); 415 numberChildren(); 416 return ne; 417 } 418 } 419 } 420 421 for (Property p : property.getChildProperties(this.name, type)) { 422 if (p.getName().equals(name)) { 423 Element ne = new Element(name, p); 424 children.add(ne); 425 return ne; 426 } 427 } 428 429 throw new Error("Unrecognised name "+name+" on "+this.name); 430 } 431 432 private int maxToInt(String max) { 433 if (max.equals("*")) 434 return Integer.MAX_VALUE; 435 else 436 return Integer.parseInt(max); 437 } 438 439 @Override 440 public boolean isPrimitive() { 441 return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name)); 442 } 443 444 @Override 445 public boolean isResource() { 446 return property.isResource(); 447 } 448 449 450 @Override 451 public boolean hasPrimitiveValue() { 452 return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name); 453 } 454 455 456 @Override 457 public String primitiveValue() { 458 if (isPrimitive()) 459 return value; 460 else { 461 if (hasPrimitiveValue() && children != null) { 462 for (Element c : children) { 463 if (c.getName().equals("value")) 464 return c.primitiveValue(); 465 } 466 } 467 return null; 468 } 469 } 470 471 // for the validator 472 public int line() { 473 return line; 474 } 475 476 public int col() { 477 return col; 478 } 479 480 public Element markLocation(int line, int col) { 481 this.line = line; 482 this.col = col; 483 return this; 484 } 485 486 public void markValidation(StructureDefinition profile, ElementDefinition definition) { 487 } 488 489 public Element getNamedChild(String name) { 490 if (children == null) 491 return null; 492 Element result = null; 493 for (Element child : children) { 494 if (child.getName().equals(name)) { 495 if (result == null) 496 result = child; 497 else 498 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 499 } 500 } 501 return result; 502 } 503 504 public void getNamedChildren(String name, List<Element> list) { 505 if (children != null) 506 for (Element child : children) 507 if (child.getName().equals(name)) 508 list.add(child); 509 } 510 511 public String getNamedChildValue(String name) { 512 Element child = getNamedChild(name); 513 return child == null ? null : child.value; 514 } 515 516 public void getNamedChildrenWithWildcard(String string, List<Element> values) { 517 Validate.isTrue(string.endsWith("[x]")); 518 519 String start = string.substring(0, string.length() - 3); 520 if (children != null) { 521 for (Element child : children) { 522 if (child.getName().startsWith(start)) { 523 values.add(child); 524 } 525 } 526 } 527 } 528 529 530 public XhtmlNode getXhtml() { 531 return xhtml; 532 } 533 534 public Element setXhtml(XhtmlNode xhtml) { 535 this.xhtml = xhtml; 536 return this; 537 } 538 539 @Override 540 public boolean isEmpty() { 541 if (isNotBlank(value)) { 542 return false; 543 } 544 for (Element next : getChildren()) { 545 if (!next.isEmpty()) { 546 return false; 547 } 548 } 549 return true; 550 } 551 552 public Property getElementProperty() { 553 return elementProperty; 554 } 555 556 public boolean hasElementProperty() { 557 return elementProperty != null; 558 } 559 560 public boolean hasChild(String name) { 561 return getNamedChild(name) != null; 562 } 563 564 @Override 565 public String toString() { 566 return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 567 } 568 569 @Override 570 public String getIdBase() { 571 return getChildValue("id"); 572 } 573 574 @Override 575 public void setIdBase(String value) { 576 setChildValue("id", value); 577 } 578 579 580 @Override 581 public boolean equalsDeep(Base other) { 582 if (!super.equalsDeep(other)) 583 return false; 584 if (isPrimitive() && other.isPrimitive()) 585 return primitiveValue().equals(other.primitiveValue()); 586 if (isPrimitive() || other.isPrimitive()) 587 return false; 588 Set<String> processed = new HashSet<String>(); 589 for (org.hl7.fhir.dstu3.model.Property p : children()) { 590 String name = p.getName(); 591 processed.add(name); 592 org.hl7.fhir.dstu3.model.Property o = other.getChildByName(name); 593 if (!equalsDeep(p, o)) 594 return false; 595 } 596 for (org.hl7.fhir.dstu3.model.Property p : children()) { 597 String name = p.getName(); 598 if (!processed.contains(name)) { 599 org.hl7.fhir.dstu3.model.Property o = other.getChildByName(name); 600 if (!equalsDeep(p, o)) 601 return false; 602 } 603 } 604 return true; 605 } 606 607 private boolean equalsDeep(org.hl7.fhir.dstu3.model.Property p, org.hl7.fhir.dstu3.model.Property o) { 608 if (o == null || p == null) 609 return false; 610 if (p.getValues().size() != o.getValues().size()) 611 return false; 612 for (int i = 0; i < p.getValues().size(); i++) 613 if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true)) 614 return false; 615 return true; 616 } 617 618 @Override 619 public boolean equalsShallow(Base other) { 620 if (!super.equalsShallow(other)) 621 return false; 622 if (isPrimitive() && other.isPrimitive()) 623 return primitiveValue().equals(other.primitiveValue()); 624 if (isPrimitive() || other.isPrimitive()) 625 return false; 626 return true; //? 627 } 628 629 public Type asType() throws FHIRException { 630 return new ObjectConverter(property.getContext()).convertToType(this); 631 } 632 633 @Override 634 public boolean isMetadataBased() { 635 return true; 636 } 637 638 public boolean isList() { 639 if (elementProperty != null) 640 return elementProperty.isList(); 641 else 642 return property.isList(); 643 } 644 645 @Override 646 public String[] getTypesForProperty(int hash, String name) throws FHIRException { 647 Property p = property.getChildSimpleName(this.name, name); 648 if (p != null) { 649 Set<String> types = new HashSet<String>(); 650 for (TypeRefComponent tr : p.getDefinition().getType()) { 651 types.add(tr.getCode()); 652 } 653 return types.toArray(new String[]{}); 654 } 655 return super.getTypesForProperty(hash, name); 656 657 } 658 659 public void sort() { 660 if (children != null) { 661 List<Element> remove = new ArrayList<Element>(); 662 for (Element child : children) { 663 child.sort(); 664 if (child.isEmpty()) 665 remove.add(child); 666 } 667 children.removeAll(remove); 668 Collections.sort(children, new ElementSortComparator(this, this.property)); 669 } 670 } 671 672 public class ElementSortComparator implements Comparator<Element> { 673 private List<ElementDefinition> children; 674 public ElementSortComparator(Element e, Property property) { 675 String tn = e.getType(); 676 StructureDefinition sd = property.getContext().fetchTypeDefinition(tn); 677 if (sd != null && !sd.getAbstract()) 678 children = sd.getSnapshot().getElement(); 679 else 680 children = property.getStructure().getSnapshot().getElement(); 681 } 682 683 @Override 684 public int compare(Element e0, Element e1) { 685 int i0 = find(e0); 686 int i1 = find(e1); 687 return (i0 < i1) ? -1 : ((i0 == i1) ? 0 : 1); 688 } 689 private int find(Element e0) { 690 int i = e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) : children.indexOf(e0.property.getDefinition()); 691 return i; 692 } 693 694 } 695 696}