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}