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