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