001package org.hl7.fhir.r4.model;
002
003/*-
004 * #%L
005 * org.hl7.fhir.r4
006 * %%
007 * Copyright (C) 2014 - 2019 Health Level 7
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Set;
029
030import org.hl7.fhir.exceptions.DefinitionException;
031import org.hl7.fhir.r4.context.IWorkerContext;
032import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
033import org.hl7.fhir.r4.model.ExpressionNode.CollectionStatus;
034import org.hl7.fhir.utilities.Utilities;
035
036
037
038public class TypeDetails {
039  public static final String FHIR_NS = "http://hl7.org/fhir/StructureDefinition/";
040  public static final String FP_NS = "http://hl7.org/fhirpath/";
041  public static final String FP_String = "http://hl7.org/fhirpath/String";
042  public static final String FP_Boolean = "http://hl7.org/fhirpath/Boolean";
043  public static final String FP_Integer = "http://hl7.org/fhirpath/Integer";
044  public static final String FP_Decimal = "http://hl7.org/fhirpath/Decimal";
045  public static final String FP_Quantity = "http://hl7.org/fhirpath/Quantity";
046  public static final String FP_DateTime = "http://hl7.org/fhirpath/DateTime";
047  public static final String FP_Time = "http://hl7.org/fhirpath/Time";
048  public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/SimpleTypeInfo";
049  public static final String FP_ClassInfo = "http://hl7.org/fhirpath/ClassInfo";
050
051  public static class ProfiledType {
052    private String uri;
053    private List<String> profiles; // or, not and
054    private List<ElementDefinitionBindingComponent> bindings;
055    
056    public ProfiledType(String n) {
057      uri = ns(n);    
058    }
059    
060    public String getUri() {
061      return uri;
062    }
063
064    public boolean hasProfiles() {
065      return profiles != null && profiles.size() > 0;
066    }
067    public List<String> getProfiles() {
068      return profiles;
069    }
070
071    public boolean hasBindings() {
072      return bindings != null && bindings.size() > 0;
073    }
074    public List<ElementDefinitionBindingComponent> getBindings() {
075      return bindings;
076    }
077
078    public static String ns(String n) {
079      return Utilities.isAbsoluteUrl(n) ? n : FHIR_NS+n;
080    }
081
082    public void addProfile(String profile) {
083      if (profiles == null)
084        profiles = new ArrayList<String>();
085      profiles.add(profile);
086    }
087
088    public void addBinding(ElementDefinitionBindingComponent binding) {
089      bindings = new ArrayList<ElementDefinitionBindingComponent>();
090      bindings.add(binding);
091    }
092
093    public boolean hasBinding(ElementDefinitionBindingComponent b) {
094      return false; // todo: do we need to do this?
095    }
096
097    public void addProfiles(List<CanonicalType> list) {
098      if (profiles == null)
099        profiles = new ArrayList<String>();
100      for (UriType u : list)
101        profiles.add(u.getValue());
102    }
103    public boolean isSystemType() {
104      return uri.startsWith(FP_NS);
105    }
106  }
107  
108  private List<ProfiledType> types = new ArrayList<ProfiledType>();
109  private CollectionStatus collectionStatus;
110  public TypeDetails(CollectionStatus collectionStatus, String... names) {
111    super();
112    this.collectionStatus = collectionStatus;
113    for (String n : names) {
114      this.types.add(new ProfiledType(n));
115    }
116  }
117  public TypeDetails(CollectionStatus collectionStatus, Set<String> names) {
118    super();
119    this.collectionStatus = collectionStatus;
120    for (String n : names) {
121      addType(new ProfiledType(n));
122    }
123  }
124  public TypeDetails(CollectionStatus collectionStatus, ProfiledType pt) {
125    super();
126    this.collectionStatus = collectionStatus;
127    this.types.add(pt);
128  }
129  public String addType(String n) {
130    ProfiledType pt = new ProfiledType(n);
131    String res = pt.uri;
132    addType(pt);
133    return res;
134  }
135  public String addType(String n, String p) {
136    ProfiledType pt = new ProfiledType(n);
137    pt.addProfile(p);
138    String res = pt.uri;
139    addType(pt);
140    return res;
141  }
142  public void addType(ProfiledType pt) {
143    for (ProfiledType et : types) {
144      if (et.uri.equals(pt.uri)) {
145        if (pt.profiles != null) {
146          for (String p : pt.profiles) {
147            if (et.profiles == null)
148              et.profiles = new ArrayList<String>();
149            if (!et.profiles.contains(p))
150              et.profiles.add(p);
151          }
152        }
153        if (pt.bindings != null) {
154          for (ElementDefinitionBindingComponent b : pt.bindings) {
155            if (et.bindings == null)
156              et.bindings = new ArrayList<ElementDefinitionBindingComponent>();
157            if (!et.hasBinding(b))
158              et.bindings.add(b);
159          }
160        }
161        return;
162      }
163    }
164    types.add(pt); 
165  }
166  
167  public void addTypes(Collection<String> names) {
168    for (String n : names) 
169      addType(new ProfiledType(n));
170  }
171  
172  public boolean hasType(IWorkerContext context, String... tn) {
173    for (String n: tn) {
174      String t = ProfiledType.ns(n);
175      if (typesContains(t))
176        return true;
177      if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
178        t = FP_NS+Utilities.capitalize(n);
179        if (typesContains(t))
180          return true;
181      }
182    }
183    for (String n: tn) {
184      String id = n.contains("#") ? n.substring(0, n.indexOf("#")) : n;
185      String tail = null;
186      if (n.contains("#")) {
187        tail = n.substring( n.indexOf("#")+1);
188        tail = tail.substring(tail.indexOf("."));
189      }
190      String t = ProfiledType.ns(n);
191      StructureDefinition sd = context.fetchResource(StructureDefinition.class, t);
192      while (sd != null) {
193        if (tail == null && typesContains(sd.getUrl()))
194          return true;
195        if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl())))
196          return true;
197        if (tail != null && typesContains(sd.getUrl()+"#"+sd.getType()+tail))
198          return true;
199        if (sd.hasBaseDefinition()) {
200          if (sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Element") && !sd.getType().equals("string") && sd.getType().equals("uri"))
201            sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string");
202          else
203            sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
204        } else
205          sd = null;
206      }
207    }
208    return false;
209  }
210  
211  private String getSystemType(String url) {
212    if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
213      String code = url.substring(40);
214      if (Utilities.existsInList(code, "string",  "boolean", "integer", "decimal", "dateTime", "time", "Quantity"))
215        return FP_NS+Utilities.capitalize(code);
216    }
217    return null;
218  }
219  
220  private boolean typesContains(String t) {
221    for (ProfiledType pt : types)
222      if (pt.uri.equals(t))
223        return true;
224    return false;
225  }
226  
227  public void update(TypeDetails source) {
228    for (ProfiledType pt : source.types)
229      addType(pt);
230    if (collectionStatus == null)
231      collectionStatus = source.collectionStatus;
232    else if (source.collectionStatus == CollectionStatus.UNORDERED)
233      collectionStatus = source.collectionStatus;
234    else
235      collectionStatus = CollectionStatus.ORDERED;
236  }
237  public TypeDetails union(TypeDetails right) {
238    TypeDetails result = new TypeDetails(null);
239    if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED)
240      result.collectionStatus = CollectionStatus.UNORDERED;
241    else 
242      result.collectionStatus = CollectionStatus.ORDERED;
243    for (ProfiledType pt : types)
244      result.addType(pt);
245    for (ProfiledType pt : right.types)
246      result.addType(pt);
247    return result;
248  }
249  
250  public TypeDetails intersect(TypeDetails right) {
251    TypeDetails result = new TypeDetails(null);
252    if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED)
253      result.collectionStatus = CollectionStatus.UNORDERED;
254    else 
255      result.collectionStatus = CollectionStatus.ORDERED;
256    for (ProfiledType pt : types) {
257      boolean found = false;
258      for (ProfiledType r : right.types)
259        found = found || pt.uri.equals(r.uri);
260      if (found)
261        result.addType(pt);
262    }
263    for (ProfiledType pt : right.types)
264      result.addType(pt);
265    return result;
266  }
267  
268  public boolean hasNoTypes() {
269    return types.isEmpty();
270  }
271  public Set<String> getTypes() {
272    Set<String> res = new HashSet<String>();
273    for (ProfiledType pt : types)
274      res.add(pt.uri);
275    return res;
276  }
277  public TypeDetails toSingleton() {
278    TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
279    result.types.addAll(types);
280    return result;
281  }
282  public CollectionStatus getCollectionStatus() {
283    return collectionStatus;
284  }
285  public boolean hasType(String n) {
286    String t = ProfiledType.ns(n);
287    if (typesContains(t))
288      return true;
289    if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
290      t = FP_NS+Utilities.capitalize(n);
291      if (typesContains(t))
292        return true;
293    }
294    return false;
295  }
296  
297  public boolean hasType(Set<String> tn) {
298    for (String n: tn) {
299      String t = ProfiledType.ns(n);
300      if (typesContains(t))
301        return true;
302      if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
303        t = FP_NS+Utilities.capitalize(n);
304        if (typesContains(t))
305          return true;
306      }
307    }
308    return false;
309  }
310  public String describe() {
311    return getTypes().toString();
312  }
313  public String getType() {
314    for (ProfiledType pt : types)
315      return pt.uri;
316    return null;
317  }
318  @Override
319  public String toString() {
320    return (collectionStatus == null ? collectionStatus.SINGLETON.toString() : collectionStatus.toString()) + getTypes().toString();
321  }
322  public String getTypeCode() throws DefinitionException {
323    if (types.size() != 1)
324      throw new DefinitionException("Multiple types? ("+types.toString()+")");
325    for (ProfiledType pt : types)
326      if (pt.uri.startsWith("http://hl7.org/fhir/StructureDefinition/"))
327        return pt.uri.substring(40);
328      else
329        return pt.uri;
330    return null;
331  }
332  public List<ProfiledType> getProfiledTypes() {
333    return types;
334  }
335  public boolean hasBinding() {
336    for (ProfiledType pt : types) {
337      if (pt.hasBindings())
338        return true;
339    }
340    return false;
341  }
342  public ElementDefinitionBindingComponent getBinding() {
343    for (ProfiledType pt : types) {
344      for (ElementDefinitionBindingComponent b : pt.getBindings())
345        return b;
346    }
347    return null;
348  }
349 
350  
351}