
001package org.hl7.fhir.r5.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 java.util.ArrayList; 035import java.util.List; 036 037import org.hl7.fhir.exceptions.DefinitionException; 038import org.hl7.fhir.exceptions.FHIRException; 039import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 040import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions; 041import org.hl7.fhir.r5.context.ContextUtilities; 042import org.hl7.fhir.r5.context.IWorkerContext; 043import org.hl7.fhir.r5.fhirpath.TypeDetails; 044import org.hl7.fhir.r5.formats.FormatUtilities; 045import org.hl7.fhir.r5.model.Constants; 046import org.hl7.fhir.r5.model.ElementDefinition; 047import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; 048import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 049import org.hl7.fhir.r5.model.Extension; 050import org.hl7.fhir.r5.model.StructureDefinition; 051import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 052import org.hl7.fhir.r5.utils.ToolingExtensions; 053import org.hl7.fhir.r5.utils.TypesUtilities; 054import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 055import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 056import org.hl7.fhir.utilities.StringPair; 057import org.hl7.fhir.utilities.Utilities; 058 059@MarkedToMoveToAdjunctPackage 060public class Property { 061 062 private IWorkerContext context; 063 private ElementDefinition definition; 064 private StructureDefinition structure; 065 private ProfileUtilities profileUtilities; 066 private ContextUtilities utils; 067 private TypeRefComponent type; 068 069 public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure, ProfileUtilities profileUtilities, ContextUtilities utils) { 070 this.context = context; 071 this.definition = definition; 072 this.structure = structure; 073 this.utils = utils; 074 this.profileUtilities = profileUtilities; 075 } 076 077 078 public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure, ProfileUtilities profileUtilities, ContextUtilities utils, String type) { 079 this.context = context; 080 this.definition = definition; 081 this.structure = structure; 082 this.profileUtilities = profileUtilities; 083 this.utils = utils; 084 for (TypeRefComponent tr : definition.getType()) { 085 if (tr.getWorkingCode().equals(type)) { 086 this.type = tr; 087 } 088 } 089 } 090 091 public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure) { 092 this(context, definition, structure, new ProfileUtilities(context, null, null), new ContextUtilities(context)); 093 } 094 095 public String getName() { 096 return definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 097 } 098 099 public String getJsonName() { 100 if (definition.hasExtension(ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED)) { 101 return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED); 102 } else { 103 return getName(); 104 } 105 } 106 107 public String getXmlName() { 108 if (definition.hasExtension(ToolingExtensions.EXT_XML_NAME)) { 109 return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_XML_NAME); 110 } else if (definition.hasExtension(ToolingExtensions.EXT_XML_NAME_DEPRECATED)) { 111 return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_XML_NAME_DEPRECATED); 112 } else { 113 return getName(); 114 } 115 } 116 117 public String getXmlNamespace() { 118 if (ToolingExtensions.hasAnyOfExtensions(definition, ToolingExtensions.EXT_XML_NAMESPACE, ToolingExtensions.EXT_XML_NAMESPACE_DEPRECATED)) { 119 return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_XML_NAMESPACE, ToolingExtensions.EXT_XML_NAMESPACE_DEPRECATED); 120 } else if (ToolingExtensions.hasAnyOfExtensions(structure, ToolingExtensions.EXT_XML_NAMESPACE, ToolingExtensions.EXT_XML_NAMESPACE_DEPRECATED)) { 121 return ToolingExtensions.readStringExtension(structure, ToolingExtensions.EXT_XML_NAMESPACE, ToolingExtensions.EXT_XML_NAMESPACE_DEPRECATED); 122 } else { 123 return FormatUtilities.FHIR_NS; 124 } 125 } 126 127 public ElementDefinition getDefinition() { 128 return definition; 129 } 130 131 public String getType() { 132 if (type != null) { 133 return type.getWorkingCode(); 134 } else if (definition.getType().size() == 0) 135 return null; 136 else if (definition.getType().size() > 1) { 137 String tn = definition.getType().get(0).getWorkingCode(); 138 for (int i = 1; i < definition.getType().size(); i++) { 139 if (!tn.equals(definition.getType().get(i).getWorkingCode())) 140 return null; // though really, we shouldn't get here - type != null when definition.getType.size() > 1, or it should be 141 } 142 return tn; 143 } else 144 return definition.getType().get(0).getWorkingCode(); 145 } 146 147 public String getType(String elementName) { 148 if (type != null) { 149 return type.getWorkingCode(); 150 } 151 if (!definition.getPath().contains(".")) 152 return definition.getPath(); 153 ElementDefinition ed = definition; 154 if (definition.hasContentReference()) { 155 String url = null; 156 String path = definition.getContentReference(); 157 if (!path.startsWith("#")) { 158 if (path.contains("#")) { 159 url = path.substring(0, path.indexOf("#")); 160 path = path.substring(path.indexOf("#")+1); 161 } else { 162 throw new Error("Illegal content reference '"+path+"'"); 163 } 164 } else { 165 path = path.substring(1); 166 } 167 StructureDefinition sd = (url == null || url.equals(structure.getUrl())) ? structure : context.fetchResource(StructureDefinition.class, url, structure); 168 if (sd == null) { 169 throw new Error("Unknown Type in content reference '"+path+"'"); 170 } 171 boolean found = false; 172 for (ElementDefinition d : sd.getSnapshot().getElement()) { 173 if (d.hasId() && d.getId().equals(path)) { 174 found = true; 175 ed = d; 176 } 177 } 178 if (!found) 179 throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+sd.getUrl()); 180 } 181 if (ed.getType().size() == 0) 182 return null; 183 else if (ed.getType().size() > 1) { 184 String t = ed.getType().get(0).getCode(); 185 boolean all = true; 186 for (TypeRefComponent tr : ed.getType()) { 187 if (!t.equals(tr.getCode())) 188 all = false; 189 } 190 if (all) 191 return t; 192 String tail = ed.getPath().substring(ed.getPath().lastIndexOf(".")+1); 193 if (tail.endsWith("[x]") && elementName != null && elementName.startsWith(tail.substring(0, tail.length()-3))) { 194 String name = elementName.substring(tail.length()-3); 195 return isPrimitive(lowFirst(name)) ? lowFirst(name) : name; 196 } else { 197 if (ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) 198 return ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); 199 throw new Error("logic error, gettype when types > 1, name mismatch for "+elementName+" on at "+ed.getPath()); 200 } 201 } else if (ed.getType().get(0).getCode() == null) { 202 if (Utilities.existsInList(ed.getId(), "Element.id", "Extension.url")) 203 return "string"; 204 else 205 return structure.getId(); 206 } else 207 return ed.getType().get(0).getWorkingCode(); 208 } 209 210 public boolean typeIsConsistent(String typeName) { 211 for (TypeRefComponent tr : definition.getType()) { 212 if (typeName.equals(tr.getWorkingCode()) || typeSpecializes(tr.getWorkingCode(), typeName)) { 213 return true; 214 } 215 } 216 return false; 217 } 218 219 220 private boolean typeSpecializes(String workingCode, String typeName) { 221 if ("string".equals(typeName)) { 222 return Utilities.existsInList(workingCode, "uri", "oid", "canonical", "url", "uuid", "id", "markdown"); 223 } 224 if ("integer".equals(typeName)) { 225 return Utilities.existsInList(workingCode, "positiveInt", "unsignedInt"); 226 } 227 return false; 228 } 229 230 231 public boolean hasType(String typeName) { 232 if (type != null) { 233 return false; // ? 234 } else if (definition.getType().size() == 0) { 235 return false; 236 } else if (isJsonPrimitiveChoice()) { 237 for (TypeRefComponent tr : definition.getType()) { 238 if (typeName.equals(tr.getWorkingCode())) { 239 return true; 240 } 241 } 242 return false; 243 } else if (definition.getType().size() > 1) { 244 String t = definition.getType().get(0).getCode(); 245 boolean all = true; 246 for (TypeRefComponent tr : definition.getType()) { 247 if (!t.equals(tr.getCode())) 248 all = false; 249 } 250 if (all) 251 return true; 252 String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 253 if (tail.endsWith("[x]") && typeName.startsWith(tail.substring(0, tail.length()-3))) { 254// String name = elementName.substring(tail.length()-3); 255 return true; 256 } else 257 return false; 258 } else 259 return true; 260 } 261 262 public StructureDefinition getStructure() { 263 return structure; 264 } 265 266 /** 267 * Is the given name a primitive 268 * 269 * @param E.g. "Observation.status" 270 */ 271 public boolean isPrimitiveName(String name) { 272 String code = getType(name); 273 return isPrimitive(code); 274 } 275 276 /** 277 * Is the given type a primitive 278 * 279 * @param E.g. "integer" 280 */ 281 public boolean isPrimitive(String code) { 282 return context.isPrimitiveType(code); 283 } 284 285 public boolean isPrimitive() { 286 return isPrimitive(getType()); 287 } 288 private String lowFirst(String t) { 289 return t.substring(0, 1).toLowerCase()+t.substring(1); 290 } 291 292 public boolean isResource() { 293 if (type != null) { 294 String tc = type.getCode(); 295 return (("Resource".equals(tc) || "DomainResource".equals(tc)) || utils.isResource(tc)); 296 } else if (definition.getType().size() > 0) { 297 String tc = definition.getType().get(0).getCode(); 298 return definition.getType().size() == 1 && (("Resource".equals(tc) || "DomainResource".equals(tc)) || utils.isResource(tc)); 299 } 300 else { 301 return !definition.getPath().contains(".") && (structure.getKind() == StructureDefinitionKind.RESOURCE); 302 } 303 } 304 305 public boolean isList() { 306 return !"1".equals(definition.getBase().hasMax() ? definition.getBase().getMax() : definition.getMax()); 307 } 308 309 /** 310 * This handles a very special case: An extension used with json extensions in CDS hooks, 311 * where the extension definition, not the base, decides whether it's an array or not 312 * @return 313 */ 314 public boolean isJsonList() { 315 if (definition.hasExtension(ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED)) { 316 return !"1".equals(definition.getMax()); 317 } else { 318 return !"1".equals(definition.getBase().hasMax() ? definition.getBase().getMax() : definition.getMax()); 319 } 320 } 321 322 public boolean isBaseList() { 323 return !"1".equals(definition.getBase().getMax()); 324 } 325 326 public String getScopedPropertyName() { 327 return definition.getBase().getPath(); 328 } 329 330 private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) { 331 boolean result = false; 332 if (!ed.getType().isEmpty()) { 333 result = true; 334 for (final ElementDefinition ele : children) { 335 if (!ele.getPath().contains("extension")) { 336 result = false; 337 break; 338 } 339 } 340 } 341 return result; 342 } 343 344 public boolean IsLogicalAndHasPrimitiveValue(String name) { 345// if (canBePrimitive!= null) 346// return canBePrimitive; 347 348 if (structure.getKind() != StructureDefinitionKind.LOGICAL) 349 return false; 350 if (!hasType(name)) 351 return false; 352 StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name)); 353 if (sd == null) 354 sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(getType(name), null)); 355 if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) 356 return true; 357 if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL) 358 return false; 359 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 360 if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) { 361 return true; 362 } 363 } 364 return false; 365 } 366 367 public boolean isChoice() { 368 if (type != null) { 369 return true; 370 } 371 if (definition.getType().size() <= 1) 372 return false; 373 String tn = definition.getType().get(0).getCode(); 374 for (int i = 1; i < definition.getType().size(); i++) 375 if (!definition.getType().get(i).getCode().equals(tn)) 376 return true; 377 return false; 378 } 379 380 381 public List<Property> getChildProperties(String elementName, String statedType) throws FHIRException { 382 String cacheKey = structure.getVUrl()+"#"+definition.getPath()+":"+elementName+"/"+statedType; 383 List<Property> cached = profileUtilities.getCachedPropertyList().get(cacheKey); 384 if (cached != null) { 385 return cached; 386 } 387 ElementDefinition ed = definition; 388 StructureDefinition sd = structure; 389 boolean isCDA = isCDAElement(structure); 390 SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed, false); 391 String url = null; 392 if (children.getList().isEmpty() || isElementWithOnlyExtension(ed, children.getList())) { 393 // ok, find the right definitions 394 String t = null; 395 if (ed.getType().size() == 1 && (statedType == null || !isCDA)) 396 t = ed.getType().get(0).getWorkingCode(); 397 else if (ed.getType().size() == 0) 398 throw new Error("types == 0, and no children found on "+getDefinition().getPath()); 399 else { 400 t = ed.getType().get(0).getWorkingCode(); 401 boolean all = true; 402 for (TypeRefComponent tr : ed.getType()) { 403 if (!tr.getWorkingCode().equals(t)) { 404 all = false; 405 break; 406 } 407 } 408 if (!all || (isCDA && statedType != null)) { 409 // ok, it's polymorphic 410 if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR) || isCDA) { 411 t = statedType; 412 if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) 413 t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); 414 boolean ok = false; 415 for (TypeRefComponent tr : ed.getType()) { 416 if (tr.getWorkingCode().equals(t)) 417 ok = true; 418 if (Utilities.isAbsoluteUrl(tr.getWorkingCode())) { 419 StructureDefinition sdt = context.fetchResource(StructureDefinition.class, tr.getWorkingCode()); 420 if (sdt != null && sdt.getTypeTail().equals(t)) { 421 url = tr.getWorkingCode(); 422 ok = true; 423 } 424 if (!ok) { 425 sdt = findAncestor(t, sdt); 426 if (sdt != null) { 427 url = sdt.getUrl(); 428 ok = true; 429 } 430 } 431 } 432 if (ok) { 433 break; 434 } 435 } 436 if (!ok) { 437 throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath()); 438 } 439 } else { 440 t = elementName.substring(tail(ed.getPath()).length() - 3); 441 if (isPrimitive(lowFirst(t))) 442 t = lowFirst(t); 443 } 444 } 445 } 446 if (!"xhtml".equals(t)) { 447 for (TypeRefComponent aType: ed.getType()) { 448 if (aType.getWorkingCode().equals(t)) { 449 if (aType.hasProfile()) { 450 assert aType.getProfile().size() == 1; 451 url = aType.getProfile().get(0).getValue(); 452 } else { 453 url = ProfileUtilities.sdNs(t, null); 454 } 455 break; 456 } 457 } 458 if (url==null) { 459 throw new FHIRException("Unable to find type " + t + " for element " + elementName + " with path " + ed.getPath()); 460 } 461 sd = context.fetchResource(StructureDefinition.class, url); 462 if (sd == null) 463 throw new DefinitionException("Unable to find definition '"+url+"' for type '"+t+"' for name '"+elementName+"' on property "+definition.getPath()); 464 children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0), false); 465 } 466 } 467 List<Property> properties = new ArrayList<Property>(); 468 for (ElementDefinition child : children.getList()) { 469 properties.add(new Property(context, child, sd, this.profileUtilities, this.utils)); 470 } 471 profileUtilities.getCachedPropertyList().put(cacheKey, properties); 472 return properties; 473 } 474 475 private StructureDefinition findAncestor(String type, StructureDefinition sdt) { 476 if (sdt != null) { 477 StructureDefinition sd = context.fetchTypeDefinition(type); 478 StructureDefinition t = sd; 479 while (t != null) { 480 if (t == sdt) { 481 return sd; 482 } 483 t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition()); 484 } 485 } 486 return null; 487 } 488 489 490 private boolean isCDAElement(StructureDefinition sd) { 491 return sd.hasUrl() && sd.getUrl().startsWith(Constants.NS_CDA_ROOT); 492 } 493 494 495 protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException { 496 ElementDefinition ed = definition; 497 StructureDefinition sd = structure; 498 SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed, false); 499 if (children.getList().isEmpty()) { 500 // ok, find the right definitions 501 String t = null; 502 if (ed.getType().size() == 1) 503 t = ed.getType().get(0).getCode(); 504 else if (ed.getType().size() == 0) 505 throw new Error("types == 0, and no children found"); 506 else { 507 t = ed.getType().get(0).getCode(); 508 boolean all = true; 509 for (TypeRefComponent tr : ed.getType()) { 510 if (!tr.getCode().equals(t)) { 511 all = false; 512 break; 513 } 514 } 515 if (!all) { 516 // ok, it's polymorphic 517 t = type.getType(); 518 } 519 } 520 if (!"xhtml".equals(t)) { 521 sd = context.fetchResource(StructureDefinition.class, t); 522 if (sd == null) 523 throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath()); 524 children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0), false); 525 } 526 } 527 List<Property> properties = new ArrayList<Property>(); 528 for (ElementDefinition child : children.getList()) { 529 properties.add(new Property(context, child, sd, this.profileUtilities, this.utils)); 530 } 531 return properties; 532 } 533 534 private String tail(String path) { 535 return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path; 536 } 537 538 public Property getChild(String elementName, String childName) throws FHIRException { 539 List<Property> children = getChildProperties(elementName, null); 540 for (Property p : children) { 541 if (p.getName().equals(childName)) { 542 return p; 543 } 544 } 545 return null; 546 } 547 548 public Property getChild(String name, TypeDetails type) throws DefinitionException { 549 List<Property> children = getChildProperties(type); 550 for (Property p : children) { 551 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 552 return p; 553 } 554 } 555 return null; 556 } 557 558 public Property getChild(String name) throws FHIRException { 559 List<Property> children = getChildProperties(name, null); 560 for (Property p : children) { 561 if (p.getName().equals(name)) { 562 return p; 563 } 564 } 565 return null; 566 } 567 568 public Property getChildSimpleName(String elementName, String name) throws FHIRException { 569 List<Property> children = getChildProperties(elementName, null); 570 for (Property p : children) { 571 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 572 return p; 573 } 574 } 575 return null; 576 } 577 578 public IWorkerContext getContext() { 579 return context; 580 } 581 582 @Override 583 public String toString() { 584 return definition.getPath(); 585 } 586 587 588 public boolean isJsonKeyArray() { 589 return definition.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY); 590 } 591 592 593 public String getJsonKeyProperty() { 594 return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_PROP_KEY); 595 } 596 597 598 public boolean hasTypeSpecifier() { 599 return definition.hasExtension(ToolingExtensions.EXT_TYPE_SPEC); 600 } 601 602 603 public List<StringPair> getTypeSpecifiers() { 604 List<StringPair> res = new ArrayList<>(); 605 for (Extension e : definition.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) { 606 res.add(new StringPair(ToolingExtensions.readStringExtension(e, "condition"), ToolingExtensions.readStringExtension(e, "type"))); 607 } 608 return res; 609 } 610 611 612 public Property cloneToType(StructureDefinition sd) { 613 Property res = new Property(context, definition.copy(), sd); 614 res.definition.getType().clear(); 615 res.definition.getType().add(new TypeRefComponent(sd.getUrl())); 616 return res; 617 } 618 619 620 public boolean hasImpliedPrefix() { 621 return definition.hasExtension(ToolingExtensions.EXT_IMPLIED_PREFIX); 622 } 623 624 625 public String getImpliedPrefix() { 626 return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_IMPLIED_PREFIX); 627 } 628 629 630 public boolean isNullable() { 631 return ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_NULLABLE); 632 } 633 634 635 public String summary() { 636 return structure.getUrl()+"#"+definition.getId(); 637 } 638 639 640 public boolean canBeEmpty() { 641 if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) { 642 return !"absent".equals(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY)); 643 } else { 644 return false; 645 } 646 } 647 648 649 public boolean isLogical() { 650 return structure.getKind() == StructureDefinitionKind.LOGICAL; 651 } 652 653 654 public ProfileUtilities getUtils() { 655 return profileUtilities; 656 } 657 public ContextUtilities getContextUtils() { 658 return utils; 659 } 660 661 public boolean isJsonPrimitiveChoice() { 662 return ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE); 663 } 664 665 public Object typeSummary() { 666 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" | "); 667 for (TypeRefComponent t : definition.getType()) { 668 b.append(t.getCode()); 669 } 670 return b.toString(); 671 } 672 673 674 public boolean hasJsonName() { 675 return definition.hasExtension(ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED); 676 } 677 678 679 public boolean isTranslatable() { 680 boolean ok = ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_TRANSLATABLE); 681 if (!ok && !definition.getPath().endsWith(".id") && !definition.getPath().endsWith(".linkId") && !Utilities.existsInList(definition.getBase().getPath(), "Resource.id", "Reference.reference", "Coding.version", "Identifier.value", "SampledData.offsets", "SampledData.data", "ContactPoint.value")) { 682 String t = getType(); 683 ok = Utilities.existsInList(t, "string", "markdown"); 684 } 685 if (Utilities.existsInList(pathForElement(getStructure().getType(), getDefinition().getBase().getPath()), "CanonicalResource.version")) { 686 return false; 687 } 688 return ok; 689 } 690 691 692 private String pathForElement(String type, String path) { 693 // special case support for metadata elements prior to R5: 694 if (utils.getCanonicalResourceNames().contains(type)) { 695 String fp = path.replace(type+".", "CanonicalResource."); 696 if (Utilities.existsInList(fp, 697 "CanonicalResource.url", "CanonicalResource.identifier", "CanonicalResource.version", "CanonicalResource.name", 698 "CanonicalResource.title", "CanonicalResource.status", "CanonicalResource.experimental", "CanonicalResource.date", 699 "CanonicalResource.publisher", "CanonicalResource.contact", "CanonicalResource.description", "CanonicalResource.useContext", 700 "CanonicalResource.jurisdiction")) { 701 return fp; 702 } 703 } 704 return path; 705 } 706 707 public String getXmlTypeName() { 708 TypeRefComponent tr = type; 709 if (tr == null) { 710 tr = definition.getTypeFirstRep(); 711 } 712 StructureDefinition sd = context.fetchTypeDefinition(tr.getWorkingCode()); 713 return sd.getSnapshot().getElementFirstRep().getPath(); 714 } 715 716 717 public boolean isReference() { 718 if (type != null) { 719 return isRef(type); 720 } 721 for (TypeRefComponent tr : definition.getType()) { 722 boolean ref = isRef(tr); 723 if (ref) { 724 return true; 725 } 726 } 727 return false; 728 } 729 730 731 private boolean isRef(TypeRefComponent tr) { 732 return Utilities.existsInList(tr.getWorkingCode(), "Reference", "url", "uri", "canonical"); 733 } 734 735 736 public boolean canBeType(String type) { 737 for (TypeRefComponent tr : getDefinition().getType()) { 738 if (type.equals(tr.getWorkingCode())) { 739 return true; 740 } 741 } 742 return false; 743 } 744 745 746}