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.HashSet; 036import java.util.List; 037import java.util.Set; 038 039import org.hl7.fhir.exceptions.DefinitionException; 040import org.hl7.fhir.r4.context.IWorkerContext; 041import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus; 042import org.hl7.fhir.r4.model.CanonicalType; 043import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 044import org.hl7.fhir.r4.model.StructureDefinition; 045import org.hl7.fhir.r4.model.UriType; 046import org.hl7.fhir.utilities.Utilities; 047 048public class TypeDetails { 049 public static final String FHIR_NS = "http://hl7.org/fhir/StructureDefinition/"; 050 public static final String FP_NS = "http://hl7.org/fhirpath/"; 051 public static final String FP_String = "http://hl7.org/fhirpath/String"; 052 public static final String FP_Boolean = "http://hl7.org/fhirpath/Boolean"; 053 public static final String FP_Integer = "http://hl7.org/fhirpath/Integer"; 054 public static final String FP_Decimal = "http://hl7.org/fhirpath/Decimal"; 055 public static final String FP_Quantity = "http://hl7.org/fhirpath/Quantity"; 056 public static final String FP_DateTime = "http://hl7.org/fhirpath/DateTime"; 057 public static final String FP_Time = "http://hl7.org/fhirpath/Time"; 058 public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/SimpleTypeInfo"; 059 public static final String FP_ClassInfo = "http://hl7.org/fhirpath/ClassInfo"; 060 public static final Set<String> FP_NUMBERS = new HashSet<String>(Arrays.asList(FP_Integer, FP_Decimal)); 061 062 public static class ProfiledType { 063 private String uri; 064 private List<String> profiles; // or, not and 065 private List<ElementDefinitionBindingComponent> bindings; 066 067 public ProfiledType(String n) { 068 uri = ns(n); 069 } 070 071 public String getUri() { 072 return uri; 073 } 074 075 public boolean hasProfiles() { 076 return profiles != null && profiles.size() > 0; 077 } 078 079 public List<String> getProfiles() { 080 return profiles; 081 } 082 083 public boolean hasBindings() { 084 return bindings != null && bindings.size() > 0; 085 } 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 117 public boolean isSystemType() { 118 return uri.startsWith(FP_NS); 119 } 120 } 121 122 private List<ProfiledType> types = new ArrayList<ProfiledType>(); 123 private CollectionStatus collectionStatus; 124 125 public TypeDetails(CollectionStatus collectionStatus, String... names) { 126 super(); 127 this.collectionStatus = collectionStatus; 128 for (String n : names) { 129 this.types.add(new ProfiledType(n)); 130 } 131 } 132 133 public TypeDetails(CollectionStatus collectionStatus, Set<String> names) { 134 super(); 135 this.collectionStatus = collectionStatus; 136 for (String n : names) { 137 addType(new ProfiledType(n)); 138 } 139 } 140 141 public TypeDetails(CollectionStatus collectionStatus, ProfiledType pt) { 142 super(); 143 this.collectionStatus = collectionStatus; 144 this.types.add(pt); 145 } 146 147 public String addType(String n) { 148 ProfiledType pt = new ProfiledType(n); 149 String res = pt.uri; 150 addType(pt); 151 return res; 152 } 153 154 public String addType(String n, String p) { 155 ProfiledType pt = new ProfiledType(n); 156 pt.addProfile(p); 157 String res = pt.uri; 158 addType(pt); 159 return res; 160 } 161 162 public void addType(ProfiledType pt) { 163 for (ProfiledType et : types) { 164 if (et.uri.equals(pt.uri)) { 165 if (pt.profiles != null) { 166 for (String p : pt.profiles) { 167 if (et.profiles == null) 168 et.profiles = new ArrayList<String>(); 169 if (!et.profiles.contains(p)) 170 et.profiles.add(p); 171 } 172 } 173 if (pt.bindings != null) { 174 for (ElementDefinitionBindingComponent b : pt.bindings) { 175 if (et.bindings == null) 176 et.bindings = new ArrayList<ElementDefinitionBindingComponent>(); 177 if (!et.hasBinding(b)) 178 et.bindings.add(b); 179 } 180 } 181 return; 182 } 183 } 184 types.add(pt); 185 } 186 187 public void addTypes(Collection<String> names) { 188 for (String n : names) 189 addType(new ProfiledType(n)); 190 } 191 192 public boolean hasType(IWorkerContext context, String... tn) { 193 for (String n : tn) { 194 String t = ProfiledType.ns(n); 195 if (typesContains(t)) 196 return true; 197 if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", 198 "ClassInfo", "SimpleTypeInfo")) { 199 t = FP_NS + Utilities.capitalize(n); 200 if (typesContains(t)) 201 return true; 202 } 203 } 204 for (String n : tn) { 205 String id = n.contains("#") ? n.substring(0, n.indexOf("#")) : n; 206 String tail = null; 207 if (n.contains("#")) { 208 tail = n.substring(n.indexOf("#") + 1); 209 tail = tail.substring(tail.indexOf(".")); 210 } 211 String t = ProfiledType.ns(n); 212 StructureDefinition sd = context.fetchResource(StructureDefinition.class, t); 213 while (sd != null) { 214 if (tail == null && typesContains(sd.getUrl())) 215 return true; 216 if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl()))) 217 return true; 218 if (tail != null && typesContains(sd.getUrl() + "#" + sd.getType() + tail)) 219 return true; 220 if (sd.hasBaseDefinition()) { 221 if (sd.getType().equals("uri")) 222 sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string"); 223 else 224 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 225 } else 226 sd = null; 227 } 228 } 229 return false; 230 } 231 232 private String getSystemType(String url) { 233 if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 234 String code = url.substring(40); 235 if (Utilities.existsInList(code, "string", "boolean", "integer", "decimal", "dateTime", "time", "Quantity")) 236 return FP_NS + Utilities.capitalize(code); 237 } 238 return null; 239 } 240 241 private boolean typesContains(String t) { 242 for (ProfiledType pt : types) 243 if (pt.uri.equals(t)) 244 return true; 245 return false; 246 } 247 248 public void update(TypeDetails source) { 249 for (ProfiledType pt : source.types) 250 addType(pt); 251 if (collectionStatus == null) 252 collectionStatus = source.collectionStatus; 253 else if (source.collectionStatus == CollectionStatus.UNORDERED) 254 collectionStatus = source.collectionStatus; 255 else 256 collectionStatus = CollectionStatus.ORDERED; 257 } 258 259 public TypeDetails union(TypeDetails right) { 260 TypeDetails result = new TypeDetails(null); 261 if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) 262 result.collectionStatus = CollectionStatus.UNORDERED; 263 else 264 result.collectionStatus = CollectionStatus.ORDERED; 265 for (ProfiledType pt : types) 266 result.addType(pt); 267 for (ProfiledType pt : right.types) 268 result.addType(pt); 269 return result; 270 } 271 272 public TypeDetails intersect(TypeDetails right) { 273 TypeDetails result = new TypeDetails(null); 274 if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) 275 result.collectionStatus = CollectionStatus.UNORDERED; 276 else 277 result.collectionStatus = CollectionStatus.ORDERED; 278 for (ProfiledType pt : types) { 279 boolean found = false; 280 for (ProfiledType r : right.types) 281 found = found || pt.uri.equals(r.uri); 282 if (found) 283 result.addType(pt); 284 } 285 for (ProfiledType pt : right.types) 286 result.addType(pt); 287 return result; 288 } 289 290 public boolean hasNoTypes() { 291 return types.isEmpty(); 292 } 293 294 public Set<String> getTypes() { 295 Set<String> res = new HashSet<String>(); 296 for (ProfiledType pt : types) 297 res.add(pt.uri); 298 return res; 299 } 300 301 public TypeDetails toSingleton() { 302 TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); 303 result.types.addAll(types); 304 return result; 305 } 306 307 public CollectionStatus getCollectionStatus() { 308 return collectionStatus; 309 } 310 311 public boolean hasType(String n) { 312 String t = ProfiledType.ns(n); 313 if (typesContains(t)) 314 return true; 315 if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", 316 "ClassInfo", "SimpleTypeInfo")) { 317 t = FP_NS + Utilities.capitalize(n); 318 if (typesContains(t)) 319 return true; 320 } 321 return false; 322 } 323 324 public boolean hasType(Set<String> tn) { 325 for (String n : tn) { 326 String t = ProfiledType.ns(n); 327 if (typesContains(t)) 328 return true; 329 if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", 330 "ClassInfo", "SimpleTypeInfo")) { 331 t = FP_NS + Utilities.capitalize(n); 332 if (typesContains(t)) 333 return true; 334 } 335 } 336 return false; 337 } 338 339 public String describe() { 340 return getTypes().toString(); 341 } 342 343 public String getType() { 344 for (ProfiledType pt : types) 345 return pt.uri; 346 return null; 347 } 348 349 @Override 350 public String toString() { 351 return (collectionStatus == null ? collectionStatus.SINGLETON.toString() : collectionStatus.toString()) 352 + getTypes().toString(); 353 } 354 355 public String getTypeCode() throws DefinitionException { 356 if (types.size() != 1) 357 throw new DefinitionException("Multiple types? (" + types.toString() + ")"); 358 for (ProfiledType pt : types) 359 if (pt.uri.startsWith("http://hl7.org/fhir/StructureDefinition/")) 360 return pt.uri.substring(40); 361 else 362 return pt.uri; 363 return null; 364 } 365 366 public List<ProfiledType> getProfiledTypes() { 367 return types; 368 } 369 370 public boolean hasBinding() { 371 for (ProfiledType pt : types) { 372 if (pt.hasBindings()) 373 return true; 374 } 375 return false; 376 } 377 378 public ElementDefinitionBindingComponent getBinding() { 379 for (ProfiledType pt : types) { 380 for (ElementDefinitionBindingComponent b : pt.getBindings()) 381 return b; 382 } 383 return null; 384 } 385 386}