001package org.hl7.fhir.r5.model; 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 031import java.io.Serializable; 032import java.util.ArrayList; 033import java.util.HashMap; 034import java.util.List; 035import java.util.Map; 036 037import org.hl7.fhir.exceptions.FHIRException; 038import org.hl7.fhir.instance.model.api.IBase; 039import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 040import org.hl7.fhir.utilities.FhirPublication; 041import org.hl7.fhir.utilities.Utilities; 042import org.hl7.fhir.utilities.validation.ValidationMessage; 043import org.hl7.fhir.utilities.xhtml.XhtmlNode; 044 045import ca.uhn.fhir.model.api.IElement; 046 047public abstract class Base implements Serializable, IBase, IElement { 048 049 public enum ValidationReason { 050 Validation, MatchingSlice, Expression 051 } 052 053 public enum ProfileSource { 054 BaseDefinition, ConfigProfile, MetaProfile, ProfileDependency, FromExpression, GlobalProfile 055 } 056 057 public static class ValidationMode { 058 private ValidationReason reason; 059 private ProfileSource source; 060 public ValidationMode(ValidationReason reason, ProfileSource source) { 061 super(); 062 this.reason = reason; 063 this.source = source; 064 } 065 public ValidationReason getReason() { 066 return reason; 067 } 068 public ProfileSource getSource() { 069 return source; 070 } 071 public ValidationMode withSource(ProfileSource source) { 072 ValidationMode res = new ValidationMode(reason, source); 073 return res; 074 } 075 public ValidationMode withReason(ValidationReason reason) { 076 ValidationMode res = new ValidationMode(reason, source); 077 return res; 078 } 079 } 080 081 public class ValidationInfo { 082 private StructureDefinition structure; 083 private ElementDefinition definition; 084 private ValidationReason reason; 085 private ProfileSource source; 086 private boolean valid; 087 088 public ValidationInfo(StructureDefinition structure, ElementDefinition definition, ValidationMode mode) { 089 super(); 090 this.structure = structure; 091 this.definition = definition; 092 this.reason = mode.reason; 093 this.source = mode.source; 094 } 095 096 public StructureDefinition getStructure() { 097 return structure; 098 } 099 100 public ElementDefinition getDefinition() { 101 return definition; 102 } 103 104 public ValidationReason getReason() { 105 return reason; 106 } 107 108 public ProfileSource getSource() { 109 return source; 110 } 111 112 public boolean isValid() { 113 return valid; 114 } 115 public void setValid(boolean valid) { 116 this.valid = valid; 117 } 118 119 } 120 121 private static ThreadLocal<Boolean> copyUserData = new ThreadLocal<>(); 122 123 public static boolean isCopyUserData() { 124 Boolean res = copyUserData.get(); 125 return res != null && res; 126 } 127 128 public static void setCopyUserData(boolean value) { 129 copyUserData.set(value); 130 } 131 132 /** 133 * User appended data items - allow users to add extra information to the class 134 */ 135 private transient Map<String, Object> userData; 136 137 /** 138 * Post Validation Definition information 139 */ 140 private transient List<ValidationInfo> validationInfo; 141 142 /** 143 * Round tracking xml comments for testing convenience 144 */ 145 private List<String> formatCommentsPre; 146 147 /** 148 * Round tracking xml comments for testing convenience 149 */ 150 private List<String> formatCommentsPost; 151 152 private List<ValidationMessage> validationMessages; 153 154 155 public Object getUserData(String name) { 156 if (userData == null) 157 return null; 158 return userData.get(name); 159 } 160 161 public void setUserData(String name, Object value) { 162 if (userData == null) 163 userData = new HashMap<String, Object>(); 164 userData.put(name, value); 165 } 166 167 public void clearUserData(String name) { 168 if (userData != null) 169 userData.remove(name); 170 } 171 172 173 public void setUserDataINN(String name, Object value) { 174 if (value == null) 175 return; 176 177 if (userData == null) 178 userData = new HashMap<String, Object>(); 179 userData.put(name, value); 180 } 181 182 public boolean hasUserData(String name) { 183 if (userData == null) 184 return false; 185 else 186 return userData.containsKey(name) && (userData.get(name) != null); 187 } 188 189 public String getUserString(String name) { 190 Object ud = getUserData(name); 191 if (ud == null) 192 return null; 193 if (ud instanceof String) 194 return (String) ud; 195 return ud.toString(); 196 } 197 198 public int getUserInt(String name) { 199 if (!hasUserData(name)) 200 return 0; 201 return (Integer) getUserData(name); 202 } 203 204 public void copyUserData(Base other) { 205 if (other.userData != null) { 206 if (userData == null) { 207 userData = new HashMap<>(); 208 } 209 userData.putAll(other.userData); 210 } 211 } 212 213 public boolean hasFormatComment() { 214 return hasFormatCommentPre() || hasFormatCommentPost(); 215 } 216 217 public boolean hasFormatCommentPre() { 218 return formatCommentsPre != null && !formatCommentsPre.isEmpty(); 219 } 220 221 public boolean hasFormatCommentPost() { 222 return formatCommentsPost != null && !formatCommentsPost.isEmpty(); 223 } 224 225 public List<String> getFormatCommentsPre() { 226 if (formatCommentsPre == null) 227 formatCommentsPre = new ArrayList<String>(); 228 return formatCommentsPre; 229 } 230 231 public List<String> getFormatCommentsPost() { 232 if (formatCommentsPost == null) 233 formatCommentsPost = new ArrayList<String>(); 234 return formatCommentsPost; 235 } 236 237 238 public void copyFormatComments(Base other) { 239 if (other.hasFormatComment()) { 240 formatCommentsPre = new ArrayList<>(); 241 formatCommentsPre.addAll(other.formatCommentsPre); 242 } else { 243 formatCommentsPre = null; 244 } 245 } 246 247 248 public void addFormatCommentsPre(List<String> comments) { 249 if (comments != null && !comments.isEmpty()) { 250 getFormatCommentsPre().addAll(comments); 251 } 252 } 253 254 public void addFormatCommentsPost(List<String> comments) { 255 if (comments != null && !comments.isEmpty()) { 256 getFormatCommentsPost().addAll(comments); 257 } 258 } 259 260 // these 3 allow evaluation engines to get access to primitive values 261 262 /** 263 * @return true if the data type is a primitive type and might have a primitive value 264 * (which will be accessed as a string, irrespective of the stated value) 265 */ 266 public boolean isPrimitive() { 267 return false; 268 } 269 270 /** 271 * @return true if the type is boolean, and the primitive value can only be 'true' or 'false' 272 */ 273 public boolean isBooleanPrimitive() { 274 return false; 275 } 276 277 /** 278 * @return true if the type is primitive, and there's value (e.g. no Data-Absent-Reason extension etc) 279 */ 280 public boolean hasPrimitiveValue() { 281 return primitiveValue() != null; 282 } 283 284 /** 285 * @return true if the type is primitive, and there could be a value (irrespective of whether it's present e.g. no Data-Absent-Reason extension etc) 286 */ 287 public boolean canHavePrimitiveValue() { 288 return false; 289 } 290 291 /** 292 * @return the primitive value if there is one, as a string irrespective of the actual type (e.g. dates converted to their FHIR string representation) 293 * return null if the value is not a primitive or there is no value (might be extensions instead) 294 */ 295 public String primitiveValue() { 296 return null; 297 } 298 299 /** 300 * @return true if the type is date|dateTime|instant, and the primitive value is a date/time of some precision 301 */ 302 public boolean isDateTime() { 303 return false; 304 } 305 306 /** 307 * @return the date/time value if there is one, or null 308 */ 309 public BaseDateTimeType dateTimeValue() { 310 return null; 311 } 312 313 /** 314 * @return the FHIR type name of the instance (not the java class name) 315 */ 316 public abstract String fhirType() ; 317 318 /** 319 * Note that this is potentially misleading on ElementDefinition that has a 'type' 320 * property - don't mistakenly use this thinking it's going to look at ElementDefinition.type 321 * 322 * @param name - fhir type name 323 * @return- true if it 'has' this type (including by specialization) 324 */ 325 public boolean hasType(String... name) { 326 String t = fhirType(); 327 for (String n : name) { 328 if (n.equalsIgnoreCase(t)) 329 return true; 330 if (n.contains(".")) { 331 String[] p = n.split("\\."); 332 if (p.length == 2 && Utilities.existsInList(p[0], "FHIR", "CDA") && p[1].equalsIgnoreCase(t)) 333 return true; 334 } 335 } 336 return false; 337 } 338 339 protected void listChildren(List<Property> result) { 340 // nothing 341 } 342 343 public Base setProperty(String name, Base value) throws FHIRException { 344 throw new FHIRException("Attempt to set unknown property "+name); 345 } 346 347 public Base addChild(String name) throws FHIRException { 348 throw new FHIRException("Attempt to add child with unknown name "+name); 349 } 350 351 public void removeChild(String name, Base value) throws FHIRException { 352 throw new FHIRException("Attempt to remove child with unknown name "+name); 353 } 354 /** 355 * Supports iterating the children elements in some generic processor or browser 356 * All defined children will be listed, even if they have no value on this instance 357 * 358 * Note that the actual content of primitive or xhtml elements is not iterated explicitly. 359 * To find these, the processing code must recognise the element as a primitive, typecast 360 * the value to a {@link DataType}, and examine the value 361 * 362 * @return a list of all the children defined for this element 363 */ 364 public List<Property> children() { 365 List<Property> result = new ArrayList<Property>(); 366 listChildren(result); 367 return result; 368 } 369 370 public Property getChildByName(String name) { 371 List<Property> children = new ArrayList<Property>(); 372 listChildren(children); 373 for (Property c : children) 374 if (c.getName().equals(name) || c.getName().equals(name+"[x]")) { 375 return c; 376 } 377 return null; 378 } 379 380 public List<Base> listChildrenByName(String name) throws FHIRException { 381 List<Base> result = new ArrayList<Base>(); 382 for (Base b : listChildrenByName(name, true)) 383 if (b != null) 384 result.add(b); 385 return result; 386 } 387 388 public Base getChildValueByName(String name) { 389 Property p = getChildByName(name); 390 if (p != null && p.hasValues()) { 391 if (p.getValues().size() > 1) { 392 throw new Error("Too manye values for "+name+" found"); 393 } else { 394 return p.getValues().get(0); 395 } 396 } 397 return null; 398 } 399 400 public Base[] listChildrenByName(String name, boolean checkValid) throws FHIRException { 401 if (name.equals("*")) { 402 List<Property> children = new ArrayList<Property>(); 403 listChildren(children); 404 List<Base> result = new ArrayList<Base>(); 405 for (Property c : children) 406 result.addAll(c.getValues()); 407 return result.toArray(new Base[result.size()]); 408 } 409 else 410 return getProperty(name.hashCode(), name, checkValid); 411 } 412 413 public boolean isEmpty() { 414 return true; // userData does not count 415 } 416 417 public boolean equalsDeep(Base other) { 418 return other != null; 419 } 420 421 public boolean equalsShallow(Base other) { 422 return other != null; 423 } 424 425 public static boolean compareDeep(String s1, String s2, boolean allowNull) { 426 if (allowNull) { 427 boolean noLeft = s1 == null || Utilities.noString(s1); 428 boolean noRight = s2 == null || Utilities.noString(s2); 429 if (noLeft && noRight) { 430 return true; 431 } 432 } 433 if (s1 == null || s2 == null) 434 return false; 435 return s1.equals(s2); 436 } 437 438 public static boolean compareDeep(List<? extends Base> e1, List<? extends Base> e2, boolean allowNull) { 439 if (noList(e1) && noList(e2) && allowNull) 440 return true; 441 if (noList(e1) || noList(e2)) 442 return false; 443 if (e1.size() != e2.size()) 444 return false; 445 for (int i = 0; i < e1.size(); i++) { 446 if (!compareDeep(e1.get(i), e2.get(i), allowNull)) 447 return false; 448 } 449 return true; 450 } 451 452 private static boolean noList(List<? extends Base> list) { 453 return list == null || list.isEmpty() || (list.size() == 1 && list.get(0).isEmpty()); 454 } 455 456 public static boolean compareDeep(Base e1, Base e2, boolean allowNull) { 457 if (allowNull) { 458 boolean noLeft = e1 == null || e1.isEmpty(); 459 boolean noRight = e2 == null || e2.isEmpty(); 460 if (noLeft && noRight) { 461 return true; 462 } 463 } 464 if (e1 == null || e2 == null) 465 return false; 466 if (e2.isMetadataBased() && !e1.isMetadataBased()) // respect existing order for debugging consistency; outcome must be the same either way 467 return e2.equalsDeep(e1); 468 else 469 return e1.equalsDeep(e2); 470 } 471 472 public static boolean compareDeep(XhtmlNode div1, XhtmlNode div2, boolean allowNull) { 473 if (div1 == null && div2 == null && allowNull) 474 return true; 475 if (div1 == null || div2 == null) 476 return false; 477 return div1.equalsDeep(div2); 478 } 479 480 481 public static boolean compareValues(List<? extends PrimitiveType> e1, List<? extends PrimitiveType> e2, boolean allowNull) { 482 if (e1 == null && e2 == null && allowNull) 483 return true; 484 if (e1 == null || e2 == null) 485 return false; 486 if (e1.size() != e2.size()) 487 return false; 488 for (int i = 0; i < e1.size(); i++) { 489 if (!compareValues(e1.get(i), e2.get(i), allowNull)) 490 return false; 491 } 492 return true; 493 } 494 495 public static boolean compareValues(PrimitiveType e1, PrimitiveType e2, boolean allowNull) { 496 boolean noLeft = e1 == null || e1.isEmpty(); 497 boolean noRight = e2 == null || e2.isEmpty(); 498 if (noLeft && noRight && allowNull) { 499 return true; 500 } 501 if (noLeft != noRight) 502 return false; 503 return e1.equalsShallow(e2); 504 } 505 506 protected boolean isMetadataBased() { 507 return false; 508 } 509 510 public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { 511 if (checkValid) 512 throw new FHIRException("Attempt to read invalid property '"+name+"' on type "+fhirType()); 513 return null; 514 } 515 516 public Base setProperty(int hash, String name, Base value) throws FHIRException { 517 throw new FHIRException("Attempt to write to invalid property '"+name+"' on type "+fhirType()); 518 } 519 520 public Base makeProperty(int hash, String name) throws FHIRException { 521 throw new FHIRException("Attempt to make an invalid property '"+name+"' on type "+fhirType()); 522 } 523 524 public String[] getTypesForProperty(int hash, String name) throws FHIRException { 525 throw new FHIRException("Attempt to get types for an invalid property '"+name+"' on type "+fhirType()); 526 } 527 528 public static boolean equals(String v1, String v2) { 529 if (v1 == null && v2 == null) 530 return true; 531 else if (v1 == null || v2 == null) 532 return false; 533 else 534 return v1.equals(v2); 535 } 536 537 public boolean isResource() { 538 return false; 539 } 540 541 542 public abstract String getIdBase(); 543 public abstract void setIdBase(String value); 544 545 public Property getNamedProperty(String _name) throws FHIRException { 546 return getNamedProperty(_name.hashCode(), _name, false); 547 } 548 public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException { 549 if (_checkValid) 550 throw new FHIRException("Attempt to read invalid property '"+_name+"' on type "+fhirType()); 551 return null; 552 } 553 554 public abstract Base copy(); 555 556 public void copyValues(Base dst) { 557 if (isCopyUserData() && userData != null) { 558 dst.userData = new HashMap<>(); 559 dst.userData.putAll(userData); 560 } 561 } 562 563 /** 564 * return XHTML if this is an XHTML node, else null 565 * 566 * @return 567 */ 568 public XhtmlNode getXhtml() { 569 return null; 570 } 571 572 573 public boolean hasValidationInfo() { 574 return validationInfo != null; 575 } 576 577 /** 578 * A list of definitions that the validator matched this element to. 579 * Note that the element doesn't have to conform to these definitions - check whether they're valid 580 * Some of the definitions will be noted because of slice matching 581 * 582 * @return 583 */ 584 public List<ValidationInfo> getValidationInfo() { 585 return validationInfo; 586 } 587 588 public ValidationInfo addDefinition(StructureDefinition structure, ElementDefinition defn, ValidationMode mode) { 589 if (validationInfo == null) { 590 validationInfo = new ArrayList<>(); 591 } 592 for (ValidationInfo t : validationInfo) { 593 if (t.structure == structure && t.definition == defn && t.reason == mode.reason && t.source == mode.source) { 594 return t; 595 } 596 } 597 ValidationInfo vi = new ValidationInfo(structure, defn, mode); 598 this.validationInfo.add(vi); 599 return vi; 600 } 601 602 603 public boolean hasValidated(StructureDefinition sd, ElementDefinition ed) { 604 if (validationInfo != null) { 605 for (ValidationInfo vi : validationInfo) { 606 if (vi.definition == ed && vi.structure == sd) { 607 return true; 608 } 609 } 610 } 611 return false; 612 } 613 614 // validation messages: the validator does not populate these (yet) 615 public Base addValidationMessage(ValidationMessage msg) { 616 if (validationMessages == null) { 617 validationMessages = new ArrayList<>(); 618 } 619 validationMessages.add(msg); 620 return this; 621 } 622 623 public boolean hasValidationMessages() { 624 return validationMessages != null && !validationMessages.isEmpty(); 625 } 626 627 public List<ValidationMessage> getValidationMessages() { 628 return validationMessages != null ? validationMessages : new ArrayList<>(); 629 } 630 631 public abstract FhirPublication getFHIRPublicationVersion(); 632}