
001package org.hl7.fhir.r4.fhirpath; 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 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Collection; 035import java.util.Collections; 036import java.util.Comparator; 037import java.util.HashSet; 038import java.util.List; 039import java.util.Set; 040 041import org.hl7.fhir.exceptions.DefinitionException; 042import org.hl7.fhir.r4.context.IWorkerContext; 043import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus; 044import org.hl7.fhir.r4.model.CanonicalType; 045import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 046import org.hl7.fhir.r4.model.StructureDefinition; 047import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 048import org.hl7.fhir.r4.model.UriType; 049import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 050import org.hl7.fhir.utilities.Utilities; 051 052 053public class TypeDetails { 054 public class ProfiledTypeSorter implements Comparator<ProfiledType> { 055 056 @Override 057 public int compare(ProfiledType o1, ProfiledType o2) { 058 return o1.uri.compareTo(o2.uri); 059 } 060 061 } 062 063 public static final String FHIR_NS = "http://hl7.org/fhir/StructureDefinition/"; 064 public static final String FP_NS = "http://hl7.org/fhirpath/"; 065 public static final String FP_String = "http://hl7.org/fhirpath/System.String"; 066 public static final String FP_Boolean = "http://hl7.org/fhirpath/System.Boolean"; 067 public static final String FP_Integer = "http://hl7.org/fhirpath/System.Integer"; 068 public static final String FP_Decimal = "http://hl7.org/fhirpath/System.Decimal"; 069 public static final String FP_Quantity = "http://hl7.org/fhirpath/System.Quantity"; 070 public static final String FP_DateTime = "http://hl7.org/fhirpath/System.DateTime"; 071 public static final String FP_Time = "http://hl7.org/fhirpath/System.Time"; 072 public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/System.SimpleTypeInfo"; 073 public static final String FP_ClassInfo = "http://hl7.org/fhirpath/System.ClassInfo"; 074 public static final Set<String> FP_NUMBERS = new HashSet<String>(Arrays.asList(FP_Integer, FP_Decimal)); 075 076 public static class ProfiledType { 077 @Override 078 public String toString() { 079 return uri; 080 } 081 082 private String uri; 083 private List<String> profiles; // or, not and 084 private List<ElementDefinitionBindingComponent> bindings; 085 086 public ProfiledType(String n) { 087 uri = ns(n); 088 } 089 090 public String getUri() { 091 return uri; 092 } 093 094 public boolean hasProfiles() { 095 return profiles != null && profiles.size() > 0; 096 } 097 public List<String> getProfiles() { 098 return profiles; 099 } 100 101 public boolean hasBindings() { 102 return bindings != null && bindings.size() > 0; 103 } 104 public List<ElementDefinitionBindingComponent> getBindings() { 105 return bindings; 106 } 107 108 public static String ns(String n) { 109 return Utilities.isAbsoluteUrl(n) ? n : FHIR_NS+n; 110 } 111 112 public void addProfile(String profile) { 113 if (profiles == null) 114 profiles = new ArrayList<String>(); 115 profiles.add(profile); 116 } 117 118 public void addBinding(ElementDefinitionBindingComponent binding) { 119 bindings = new ArrayList<ElementDefinitionBindingComponent>(); 120 bindings.add(binding); 121 } 122 123 public boolean hasBinding(ElementDefinitionBindingComponent b) { 124 return false; // todo: do we need to do this? 125 } 126 127 public void addProfiles(List<CanonicalType> list) { 128 if (profiles == null) 129 profiles = new ArrayList<String>(); 130 for (UriType u : list) 131 profiles.add(u.getValue()); 132 } 133 public boolean isSystemType() { 134 return uri.startsWith(FP_NS); 135 } 136 137 public String describeMin() { 138 if (uri.startsWith(FP_NS)) { 139 return "System."+uri.substring(FP_NS.length()); 140 } 141 if (uri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 142 return "FHIR."+uri.substring("http://hl7.org/fhir/StructureDefinition/".length()); 143 } 144 return uri; 145 } 146 147 } 148 149 private List<ProfiledType> types = new ArrayList<ProfiledType>(); 150 private CollectionStatus collectionStatus; 151 private Set<String> targets; // or, not and, canonical urls 152 private boolean choice; 153 154 public TypeDetails(CollectionStatus collectionStatus, String... names) { 155 super(); 156 this.collectionStatus = collectionStatus; 157 for (String n : names) { 158 this.types.add(new ProfiledType(n)); 159 } 160 } 161 public TypeDetails(CollectionStatus collectionStatus, Set<String> names) { 162 super(); 163 this.collectionStatus = collectionStatus; 164 for (String n : names) { 165 addType(new ProfiledType(n)); 166 } 167 } 168 public TypeDetails(CollectionStatus collectionStatus, ProfiledType pt) { 169 super(); 170 this.collectionStatus = collectionStatus; 171 this.types.add(pt); 172 } 173 174 private TypeDetails() { 175 } 176 177 public String addType(String n) { 178 ProfiledType pt = new ProfiledType(n); 179 String res = pt.uri; 180 addType(pt); 181 return res; 182 } 183 public String addType(String n, String p) { 184 ProfiledType pt = new ProfiledType(n); 185 pt.addProfile(p); 186 String res = pt.uri; 187 addType(pt); 188 return res; 189 } 190 191 public void addType(ProfiledType pt) { 192 for (ProfiledType et : types) { 193 if (et.uri.equals(pt.uri)) { 194 if (pt.profiles != null) { 195 for (String p : pt.profiles) { 196 if (et.profiles == null) 197 et.profiles = new ArrayList<String>(); 198 if (!et.profiles.contains(p)) 199 et.profiles.add(p); 200 } 201 } 202 if (pt.bindings != null) { 203 for (ElementDefinitionBindingComponent b : pt.bindings) { 204 if (et.bindings == null) 205 et.bindings = new ArrayList<ElementDefinitionBindingComponent>(); 206 if (!et.hasBinding(b)) 207 et.bindings.add(b); 208 } 209 } 210 return; 211 } 212 } 213 types.add(pt); 214 } 215 216 public void addType(CollectionStatus status, ProfiledType pt) { 217 addType(pt); 218 if (collectionStatus == null) { 219 collectionStatus = status; 220 } else { 221 switch (status) { 222 case ORDERED: 223 if (collectionStatus == CollectionStatus.SINGLETON) { 224 collectionStatus = status; 225 } 226 break; 227 case SINGLETON: 228 break; 229 case UNORDERED: 230 collectionStatus = status; 231 break; 232 default: 233 break; 234 } 235 } 236 } 237 238 public void addTypes(Collection<String> names) { 239 for (String n : names) 240 addType(new ProfiledType(n)); 241 } 242 243 public boolean hasType(IWorkerContext context, String... tn) { 244 for (String n: tn) { 245 String t = ProfiledType.ns(n); 246 if (typesContains(t)) 247 return true; 248 if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) { 249 t = FP_NS+"System."+Utilities.capitalize(n); 250 if (typesContains(t)) { 251 return true; 252 } 253 } 254 t = ProfiledType.ns(n); 255 StructureDefinition sd = context.fetchTypeDefinition(t); 256 if (sd != null && sd.getKind() != StructureDefinitionKind.LOGICAL && Utilities.existsInList(sd.getType(), "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time")) { 257 t = FP_NS+"System."+Utilities.capitalize(sd.getType()); 258 if (typesContains(t)) { 259 return true; 260 } 261 } 262 } 263 for (String n: tn) { 264 String id = n.contains("#") ? n.substring(0, n.indexOf("#")) : n; 265 String tail = null; 266 if (n.contains("#")) { 267 tail = n.substring( n.indexOf("#")+1); 268 tail = tail.substring(tail.indexOf(".")); 269 } 270 List<StructureDefinition> list = new ArrayList<>(); 271 if (!Utilities.isAbsoluteUrl(n)) { 272 list.addAll(context.fetchTypeDefinitions(n)); 273 } else { 274 String t = ProfiledType.ns(n); 275 StructureDefinition sd = context.fetchResource(StructureDefinition.class, t); 276 if (sd != null) { 277 list.add(sd); 278 } 279 } 280 for (int i = 0; i < list.size(); i++) { 281 StructureDefinition sd = list.get(i); 282 while (sd != null) { 283 if (tail == null && typesContains(sd.getUrl())) 284 return true; 285 if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl()))) 286 return true; 287 if (tail != null && typesContains(sd.getUrl()+"#"+sd.getType()+tail)) 288 return true; 289 if ("http://hl7.org/fhir/StructureDefinition/string".equals(sd.getUrl()) && typesContains(FP_String)) { 290 return true; // this is work around for R3 291 } 292 if (sd.hasBaseDefinition()) { 293 if (sd.getType().equals("uri")) 294 sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string"); 295 else 296 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 297 } else { 298 sd = null; 299 } 300 } 301 } 302 } 303 return false; 304 } 305 306 private String getSystemType(String url) { 307 if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 308 String code = url.substring(40); 309 if (Utilities.existsInList(code, "string", "boolean", "integer", "decimal", "dateTime", "time", "Quantity")) 310 return FP_NS+"System.."+Utilities.capitalize(code); 311 } 312 return null; 313 } 314 315 private boolean typesContains(String t) { 316 for (ProfiledType pt : types) 317 if (pt.uri.equals(t)) 318 return true; 319 return false; 320 } 321 322 public void update(TypeDetails source) { 323 for (ProfiledType pt : source.types) 324 addType(pt); 325 if (collectionStatus == null || collectionStatus == CollectionStatus.SINGLETON) 326 collectionStatus = source.collectionStatus; 327 else if (source.collectionStatus == CollectionStatus.UNORDERED) 328 collectionStatus = source.collectionStatus; 329 else 330 collectionStatus = CollectionStatus.ORDERED; 331 if (source.targets != null) { 332 if (targets == null) { 333 targets = new HashSet<>(); 334 } 335 targets.addAll(source.targets); 336 } 337 if (source.isChoice()) { 338 choice = true; 339 } 340 } 341 342 public TypeDetails union(TypeDetails right) { 343 TypeDetails result = new TypeDetails(null); 344 if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) 345 result.collectionStatus = CollectionStatus.UNORDERED; 346 else 347 result.collectionStatus = CollectionStatus.ORDERED; 348 for (ProfiledType pt : types) 349 result.addType(pt); 350 for (ProfiledType pt : right.types) 351 result.addType(pt); 352 if (targets != null || right.targets != null) { 353 result.targets = new HashSet<>(); 354 if (targets != null) { 355 result.targets.addAll(targets); 356 } 357 if (right.targets != null) { 358 result.targets.addAll(right.targets); 359 } 360 } 361 362 return result; 363 } 364 365 public TypeDetails intersect(TypeDetails right) { 366 TypeDetails result = new TypeDetails(null); 367 if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) 368 result.collectionStatus = CollectionStatus.UNORDERED; 369 else 370 result.collectionStatus = CollectionStatus.ORDERED; 371 for (ProfiledType pt : types) { 372 boolean found = false; 373 for (ProfiledType r : right.types) 374 found = found || pt.uri.equals(r.uri); 375 if (found) 376 result.addType(pt); 377 } 378 for (ProfiledType pt : right.types) 379 result.addType(pt); 380 if (targets != null && right.targets != null) { 381 result.targets = new HashSet<>(); 382 for (String s : targets) { 383 if (right.targets.contains(s)) { 384 result.targets.add(s); 385 } 386 } 387 } 388 389 return result; 390 } 391 392 public boolean hasNoTypes() { 393 return types.isEmpty(); 394 } 395 public Set<String> getTypes() { 396 Set<String> res = new HashSet<String>(); 397 for (ProfiledType pt : types) 398 res.add(pt.uri); 399 return res; 400 } 401 public TypeDetails toSingleton() { 402 TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); 403 result.types.addAll(types); 404 return result; 405 } 406 public TypeDetails toOrdered() { 407 TypeDetails result = new TypeDetails(CollectionStatus.ORDERED); 408 result.types.addAll(types); 409 return result; 410 } 411 public TypeDetails toUnordered() { 412 TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); 413 result.types.addAll(types); 414 return result; 415 } 416 public CollectionStatus getCollectionStatus() { 417 return collectionStatus; 418 } 419 420 private boolean hasType(ProfiledType pt) { 421 return hasType(pt.uri); 422 } 423 424 public boolean hasType(String n) { 425 String t = ProfiledType.ns(n); 426 if (typesContains(t)) 427 return true; 428 if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) { 429 t = FP_NS+"System."+Utilities.capitalize(n); 430 if (typesContains(t)) 431 return true; 432 } 433 return false; 434 } 435 436 public boolean hasType(Set<String> tn) { 437 for (String n: tn) { 438 String t = ProfiledType.ns(n); 439 if (typesContains(t)) 440 return true; 441 if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) { 442 t = FP_NS+"System."+Utilities.capitalize(n); 443 if (typesContains(t)) 444 return true; 445 } 446 } 447 return false; 448 } 449 450 public String describe() { 451 return Utilities.sorted(getTypes()).toString(); 452 } 453 454 public String describeMin() { 455 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 456 for (ProfiledType pt : sortedTypes(types)) 457 b.append(pt.describeMin()); 458 return b.toString(); 459 } 460 461 private List<ProfiledType> sortedTypes(List<ProfiledType> types2) { 462 List<ProfiledType> list = new ArrayList<>(); 463 Collections.sort(list, new ProfiledTypeSorter()); 464 return list; 465 } 466 467 public String getType() { 468 for (ProfiledType pt : types) 469 return pt.uri; 470 return null; 471 } 472 473 @Override 474 public String toString() { 475 return (collectionStatus == null ? collectionStatus.SINGLETON.toString() : collectionStatus.toString()) + getTypes().toString(); 476 } 477 public String getTypeCode() throws DefinitionException { 478 if (types.size() != 1) 479 throw new DefinitionException("Multiple types? ("+types.toString()+")"); 480 for (ProfiledType pt : types) 481 if (pt.uri.startsWith("http://hl7.org/fhir/StructureDefinition/")) 482 return pt.uri.substring(40); 483 else 484 return pt.uri; 485 return null; 486 } 487 public List<ProfiledType> getProfiledTypes() { 488 return types; 489 } 490 public boolean hasBinding() { 491 for (ProfiledType pt : types) { 492 if (pt.hasBindings()) 493 return true; 494 } 495 return false; 496 } 497 public ElementDefinitionBindingComponent getBinding() { 498 for (ProfiledType pt : types) { 499 for (ElementDefinitionBindingComponent b : pt.getBindings()) 500 return b; 501 } 502 return null; 503 } 504 505 506 public void addTarget(String url) { 507 if (targets == null) { 508 targets = new HashSet<>(); 509 } 510 targets.add(url); 511 } 512 public Set<String> getTargets() { 513 return targets; 514 } 515 public boolean typesHaveTargets() { 516 for (ProfiledType pt : types) { 517 if (Utilities.existsInList(pt.getUri(), "Reference", "CodeableReference", "canonical", "http://hl7.org/fhir/StructureDefinition/Reference", "http://hl7.org/fhir/StructureDefinition/CodeableReference", "http://hl7.org/fhir/StructureDefinition/canonical")) { 518 return true; 519 } 520 } 521 return false; 522 } 523 public void addTargets(Set<String> src) { 524 if (src != null) { 525 for (String s : src) { 526 addTarget(s); 527 } 528 } 529 530 } 531 public TypeDetails copy() { 532 TypeDetails td = new TypeDetails(); 533 td.types.addAll(types); 534 td.collectionStatus = collectionStatus; 535 if (targets != null ) { 536 td.targets = new HashSet<>(); 537 td.targets.addAll(targets); 538 } 539 return td; 540 } 541 542 public boolean matches(TypeDetails other) { 543 boolean result = collectionStatus == other.collectionStatus && types.equals(other.types); 544 if (targets == null) { 545 return result && other.targets == null; 546 } else { 547 return result && targets.equals(other.targets); 548 } 549 550 } 551 public void addTypes(TypeDetails other) { 552 if (other.collectionStatus != CollectionStatus.SINGLETON) { 553 if (other.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) { 554 collectionStatus = CollectionStatus.UNORDERED; 555 } else { 556 collectionStatus = CollectionStatus.ORDERED; 557 } 558 } 559 for (ProfiledType pt : other.types) { 560 addType(pt); 561 } 562 if (other.targets != null) { 563 if (targets == null) { 564 targets = new HashSet<>(); 565 } 566 targets.addAll(other.targets); 567 } 568 } 569 570 public boolean contains(TypeDetails other) { 571 // TODO Auto-generated method stub 572 if (other.collectionStatus != collectionStatus) { 573 return false; 574 } 575 for (ProfiledType pt : other.types) { 576 if (!hasType(pt)) { 577 return false; 578 } 579 } 580 return true; 581 } 582 public static TypeDetails empty() { 583 return new TypeDetails(CollectionStatus.SINGLETON); 584 } 585 public boolean isList() { 586 return collectionStatus != null && collectionStatus.isList(); 587 } 588 589 // for SQL-on-FHIR: warnings when .ofType() is not paired with a choice element 590 public void setChoice(boolean b) { 591 choice = true; 592 } 593 public boolean isChoice() { 594 return choice; 595 } 596 public boolean isEmpty() { 597 return types.isEmpty(); 598 } 599 public List<String> getProfiles(String t) { 600 t = ProfiledType.ns(t); 601 for (ProfiledType pt : types) { 602 if (t.equals(pt.uri)) { 603 return pt.getProfiles(); 604 } 605 } 606 return new ArrayList<String>(); 607 } 608 609}