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