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