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