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