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