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.Collections;
036import java.util.Comparator;
037import java.util.HashSet;
038import java.util.List;
039import java.util.Set;
040
041import org.hl7.fhir.exceptions.DefinitionException;
042import org.hl7.fhir.r4.context.IWorkerContext;
043import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus;
044import org.hl7.fhir.r4.model.CanonicalType;
045import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
046import org.hl7.fhir.r4.model.StructureDefinition;
047import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
048import org.hl7.fhir.r4.model.UriType;
049import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
050import org.hl7.fhir.utilities.Utilities;
051
052
053public class TypeDetails {
054  public class ProfiledTypeSorter implements Comparator<ProfiledType> {
055
056    @Override
057    public int compare(ProfiledType o1, ProfiledType o2) {
058      return o1.uri.compareTo(o2.uri);
059    }
060
061  }
062
063  public static final String FHIR_NS = "http://hl7.org/fhir/StructureDefinition/";
064  public static final String FP_NS = "http://hl7.org/fhirpath/";
065  public static final String FP_String = "http://hl7.org/fhirpath/System.String";
066  public static final String FP_Boolean = "http://hl7.org/fhirpath/System.Boolean";
067  public static final String FP_Integer = "http://hl7.org/fhirpath/System.Integer";
068  public static final String FP_Decimal = "http://hl7.org/fhirpath/System.Decimal";
069  public static final String FP_Quantity = "http://hl7.org/fhirpath/System.Quantity";
070  public static final String FP_DateTime = "http://hl7.org/fhirpath/System.DateTime";
071  public static final String FP_Time = "http://hl7.org/fhirpath/System.Time";
072  public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/System.SimpleTypeInfo";
073  public static final String FP_ClassInfo = "http://hl7.org/fhirpath/System.ClassInfo";
074  public static final Set<String> FP_NUMBERS = new HashSet<String>(Arrays.asList(FP_Integer, FP_Decimal));
075
076  public static class ProfiledType {
077    @Override
078    public String toString() {
079      return uri;
080    }
081
082    private String uri;
083    private List<String> profiles; // or, not and
084    private List<ElementDefinitionBindingComponent> bindings;
085    
086    public ProfiledType(String n) {
087      uri = ns(n); 
088    }
089    
090    public String getUri() {
091      return uri;
092    }
093
094    public boolean hasProfiles() {
095      return profiles != null && profiles.size() > 0;
096    }
097    public List<String> getProfiles() {
098      return profiles;
099    }
100
101    public boolean hasBindings() {
102      return bindings != null && bindings.size() > 0;
103    }
104    public List<ElementDefinitionBindingComponent> getBindings() {
105      return bindings;
106    }
107
108    public static String ns(String n) {
109      return Utilities.isAbsoluteUrl(n) ? n : FHIR_NS+n;
110    }
111
112    public void addProfile(String profile) {
113      if (profiles == null)
114        profiles = new ArrayList<String>();
115      profiles.add(profile);
116    }
117
118    public void addBinding(ElementDefinitionBindingComponent binding) {
119      bindings = new ArrayList<ElementDefinitionBindingComponent>();
120      bindings.add(binding);
121    }
122
123    public boolean hasBinding(ElementDefinitionBindingComponent b) {
124      return false; // todo: do we need to do this?
125    }
126
127    public void addProfiles(List<CanonicalType> list) {
128      if (profiles == null)
129        profiles = new ArrayList<String>();
130      for (UriType u : list)
131        profiles.add(u.getValue());
132    }
133    public boolean isSystemType() {
134      return uri.startsWith(FP_NS);
135    }
136
137    public String describeMin() {
138      if (uri.startsWith(FP_NS)) {
139        return "System."+uri.substring(FP_NS.length());
140      }
141      if (uri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
142        return "FHIR."+uri.substring("http://hl7.org/fhir/StructureDefinition/".length());
143      }
144      return uri;
145    }
146    
147  }
148  
149  private List<ProfiledType> types = new ArrayList<ProfiledType>();
150  private CollectionStatus collectionStatus;
151  private Set<String> targets; // or, not and, canonical urls
152  private boolean choice;
153
154  public TypeDetails(CollectionStatus collectionStatus, String... names) {
155    super();
156    this.collectionStatus = collectionStatus;
157    for (String n : names) {
158      this.types.add(new ProfiledType(n));
159    }
160  }
161  public TypeDetails(CollectionStatus collectionStatus, Set<String> names) {
162    super();
163    this.collectionStatus = collectionStatus;
164    for (String n : names) {
165      addType(new ProfiledType(n));
166    }
167  }
168  public TypeDetails(CollectionStatus collectionStatus, ProfiledType pt) {
169    super();
170    this.collectionStatus = collectionStatus;
171    this.types.add(pt);
172  }
173  
174  private TypeDetails() {
175  }
176  
177  public String addType(String n) {
178    ProfiledType pt = new ProfiledType(n);
179    String res = pt.uri;
180    addType(pt);
181    return res;
182  }
183  public String addType(String n, String p) {
184    ProfiledType pt = new ProfiledType(n);
185    pt.addProfile(p);
186    String res = pt.uri;
187    addType(pt);
188    return res;
189  }
190  
191  public void addType(ProfiledType pt) {
192    for (ProfiledType et : types) {
193      if (et.uri.equals(pt.uri)) {
194        if (pt.profiles != null) {
195          for (String p : pt.profiles) {
196            if (et.profiles == null)
197              et.profiles = new ArrayList<String>();
198            if (!et.profiles.contains(p))
199              et.profiles.add(p);
200          }
201        }
202        if (pt.bindings != null) {
203          for (ElementDefinitionBindingComponent b : pt.bindings) {
204            if (et.bindings == null)
205              et.bindings = new ArrayList<ElementDefinitionBindingComponent>();
206            if (!et.hasBinding(b))
207              et.bindings.add(b);
208          }
209        }
210        return;
211      }
212    }
213    types.add(pt); 
214  }
215
216  public void addType(CollectionStatus status, ProfiledType pt) {
217    addType(pt);
218    if (collectionStatus == null) {
219      collectionStatus = status;      
220    } else {
221      switch (status) {
222      case ORDERED:
223        if (collectionStatus == CollectionStatus.SINGLETON) {
224          collectionStatus = status;
225        }
226        break;
227      case SINGLETON:
228        break;
229      case UNORDERED:
230        collectionStatus = status;
231        break;
232      default:
233        break;    
234      }
235    }
236  }
237
238  public void addTypes(Collection<String> names) {
239    for (String n : names) 
240      addType(new ProfiledType(n));
241  }
242  
243  public boolean hasType(IWorkerContext context, String... tn) {
244    for (String n: tn) {
245      String t = ProfiledType.ns(n);
246      if (typesContains(t))
247        return true;
248      if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
249        t = FP_NS+"System."+Utilities.capitalize(n);
250        if (typesContains(t)) {
251          return true;
252        }
253      }
254      t = ProfiledType.ns(n);
255      StructureDefinition sd = context.fetchTypeDefinition(t);
256      if (sd != null && sd.getKind() != StructureDefinitionKind.LOGICAL && Utilities.existsInList(sd.getType(), "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time")) {
257        t = FP_NS+"System."+Utilities.capitalize(sd.getType());
258        if (typesContains(t)) {
259          return true;
260        }
261      }
262    }
263    for (String n: tn) {
264      String id = n.contains("#") ? n.substring(0, n.indexOf("#")) : n;
265      String tail = null;
266      if (n.contains("#")) {
267        tail = n.substring( n.indexOf("#")+1);
268        tail = tail.substring(tail.indexOf("."));
269      }
270      List<StructureDefinition> list = new ArrayList<>();
271      if (!Utilities.isAbsoluteUrl(n)) {
272        list.addAll(context.fetchTypeDefinitions(n));
273      } else {
274        String t = ProfiledType.ns(n);
275        StructureDefinition sd = context.fetchResource(StructureDefinition.class, t);
276        if (sd != null) {
277          list.add(sd);
278        }
279      }
280      for (int i = 0; i < list.size(); i++) {
281        StructureDefinition sd = list.get(i);
282        while (sd != null) {
283          if (tail == null && typesContains(sd.getUrl()))
284            return true;
285          if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl())))
286            return true;
287          if (tail != null && typesContains(sd.getUrl()+"#"+sd.getType()+tail))
288            return true;
289          if ("http://hl7.org/fhir/StructureDefinition/string".equals(sd.getUrl()) && typesContains(FP_String)) {
290            return true; // this is work around for R3
291          }
292          if (sd.hasBaseDefinition()) {
293            if (sd.getType().equals("uri"))
294              sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string");
295            else
296              sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
297          } else {
298            sd = null;
299          }
300        }
301      }
302    }
303    return false;
304  }
305  
306  private String getSystemType(String url) {
307    if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
308      String code = url.substring(40);
309      if (Utilities.existsInList(code, "string",  "boolean", "integer", "decimal", "dateTime", "time", "Quantity"))
310        return FP_NS+"System.."+Utilities.capitalize(code);
311    }
312    return null;
313  }
314  
315  private boolean typesContains(String t) {
316    for (ProfiledType pt : types)
317      if (pt.uri.equals(t))
318        return true;
319    return false;
320  }
321  
322  public void update(TypeDetails source) {
323    for (ProfiledType pt : source.types)
324      addType(pt);
325    if (collectionStatus == null || collectionStatus == CollectionStatus.SINGLETON)
326      collectionStatus = source.collectionStatus;
327    else if (source.collectionStatus == CollectionStatus.UNORDERED)
328      collectionStatus = source.collectionStatus;
329    else
330      collectionStatus = CollectionStatus.ORDERED;
331    if (source.targets != null) {
332      if (targets == null) {
333        targets = new HashSet<>();
334      }
335      targets.addAll(source.targets);
336    }
337    if (source.isChoice()) {
338      choice = true;
339    }
340  }
341  
342  public TypeDetails union(TypeDetails right) {
343    TypeDetails result = new TypeDetails(null);
344    if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED)
345      result.collectionStatus = CollectionStatus.UNORDERED;
346    else 
347      result.collectionStatus = CollectionStatus.ORDERED;
348    for (ProfiledType pt : types)
349      result.addType(pt);
350    for (ProfiledType pt : right.types)
351      result.addType(pt);
352    if (targets != null || right.targets != null) {
353      result.targets = new HashSet<>();
354      if (targets != null) {
355        result.targets.addAll(targets);
356      }
357      if (right.targets != null) {
358        result.targets.addAll(right.targets);
359      }
360    }
361
362    return result;
363  }
364  
365  public TypeDetails intersect(TypeDetails right) {
366    TypeDetails result = new TypeDetails(null);
367    if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED)
368      result.collectionStatus = CollectionStatus.UNORDERED;
369    else 
370      result.collectionStatus = CollectionStatus.ORDERED;
371    for (ProfiledType pt : types) {
372      boolean found = false;
373      for (ProfiledType r : right.types)
374        found = found || pt.uri.equals(r.uri);
375      if (found)
376        result.addType(pt);
377    }
378    for (ProfiledType pt : right.types)
379      result.addType(pt);
380    if (targets != null && right.targets != null) {
381      result.targets = new HashSet<>();
382      for (String s : targets) {
383        if (right.targets.contains(s)) {
384          result.targets.add(s);
385        }
386      }
387    }
388
389    return result;
390  }
391  
392  public boolean hasNoTypes() {
393    return types.isEmpty();
394  }
395  public Set<String> getTypes() {
396    Set<String> res = new HashSet<String>();
397    for (ProfiledType pt : types)
398      res.add(pt.uri);
399    return res;
400  }
401  public TypeDetails toSingleton() {
402    TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
403    result.types.addAll(types);
404    return result;
405  }
406  public TypeDetails toOrdered() {
407    TypeDetails result = new TypeDetails(CollectionStatus.ORDERED);
408    result.types.addAll(types);
409    return result;
410  }
411  public TypeDetails toUnordered() {
412    TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
413    result.types.addAll(types);
414    return result;
415  }
416  public CollectionStatus getCollectionStatus() {
417    return collectionStatus;
418  }
419  
420  private boolean hasType(ProfiledType pt) {
421    return hasType(pt.uri);
422  }
423  
424  public boolean hasType(String n) {
425    String t = ProfiledType.ns(n);
426    if (typesContains(t))
427      return true;
428    if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
429      t = FP_NS+"System."+Utilities.capitalize(n);
430      if (typesContains(t))
431        return true;
432    }
433    return false;
434  }
435  
436  public boolean hasType(Set<String> tn) {
437    for (String n: tn) {
438      String t = ProfiledType.ns(n);
439      if (typesContains(t))
440        return true;
441      if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
442        t = FP_NS+"System."+Utilities.capitalize(n);
443        if (typesContains(t))
444          return true;
445      }
446    }
447    return false;
448  }
449  
450  public String describe() {
451    return Utilities.sorted(getTypes()).toString();
452  }
453  
454  public String describeMin() {
455    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
456    for (ProfiledType pt : sortedTypes(types))
457      b.append(pt.describeMin());
458    return b.toString();
459  }
460  
461  private List<ProfiledType> sortedTypes(List<ProfiledType> types2) {
462    List<ProfiledType> list = new ArrayList<>();
463    Collections.sort(list, new ProfiledTypeSorter());
464    return list;
465  }
466  
467  public String getType() {
468    for (ProfiledType pt : types)
469      return pt.uri;
470    return null;
471  }
472  
473  @Override
474  public String toString() {
475    return (collectionStatus == null ? collectionStatus.SINGLETON.toString() : collectionStatus.toString()) + getTypes().toString();
476  }
477  public String getTypeCode() throws DefinitionException {
478    if (types.size() != 1)
479      throw new DefinitionException("Multiple types? ("+types.toString()+")");
480    for (ProfiledType pt : types)
481      if (pt.uri.startsWith("http://hl7.org/fhir/StructureDefinition/"))
482        return pt.uri.substring(40);
483      else
484        return pt.uri;
485    return null;
486  }
487  public List<ProfiledType> getProfiledTypes() {
488    return types;
489  }
490  public boolean hasBinding() {
491    for (ProfiledType pt : types) {
492      if (pt.hasBindings())
493        return true;
494    }
495    return false;
496  }
497  public ElementDefinitionBindingComponent getBinding() {
498    for (ProfiledType pt : types) {
499      for (ElementDefinitionBindingComponent b : pt.getBindings())
500        return b;
501    }
502    return null;
503  }
504 
505
506  public void addTarget(String url) {
507    if (targets == null) {
508      targets = new HashSet<>();
509    }
510    targets.add(url);
511  }
512  public Set<String> getTargets() {
513    return targets;
514  }
515  public boolean typesHaveTargets() {
516    for (ProfiledType pt : types) {
517      if (Utilities.existsInList(pt.getUri(), "Reference", "CodeableReference", "canonical",  "http://hl7.org/fhir/StructureDefinition/Reference", "http://hl7.org/fhir/StructureDefinition/CodeableReference", "http://hl7.org/fhir/StructureDefinition/canonical")) {
518        return true;
519      }
520    }
521    return false;
522  }
523  public void addTargets(Set<String> src) {
524    if (src != null) {
525      for (String s : src) {
526        addTarget(s);
527      }
528    }
529    
530  }
531  public TypeDetails copy() {
532    TypeDetails td = new TypeDetails();
533    td.types.addAll(types);
534    td.collectionStatus = collectionStatus;
535    if (targets != null ) {
536      td.targets = new HashSet<>();
537      td.targets.addAll(targets);
538    }
539    return td;
540  }
541  
542  public boolean matches(TypeDetails other) {
543    boolean result = collectionStatus == other.collectionStatus && types.equals(other.types);
544    if (targets == null) {
545      return result && other.targets == null;
546    } else {
547      return result && targets.equals(other.targets);
548    }
549    
550  }
551  public void addTypes(TypeDetails other) {
552    if (other.collectionStatus != CollectionStatus.SINGLETON) {
553      if (other.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) {
554        collectionStatus = CollectionStatus.UNORDERED;
555      } else {
556        collectionStatus = CollectionStatus.ORDERED;
557      }
558    }
559    for (ProfiledType pt : other.types) {
560      addType(pt);
561    }
562    if (other.targets != null) {
563      if (targets == null) {
564        targets = new HashSet<>();
565      }
566      targets.addAll(other.targets);
567    }
568  }
569  
570  public boolean contains(TypeDetails other) {
571    // TODO Auto-generated method stub
572    if (other.collectionStatus != collectionStatus) {
573      return false;
574    }
575    for (ProfiledType pt : other.types) {
576      if (!hasType(pt)) {
577        return false;
578      }
579    }
580    return true;
581  }
582  public static TypeDetails empty() {
583    return new TypeDetails(CollectionStatus.SINGLETON);
584  }
585  public boolean isList() {
586    return collectionStatus != null && collectionStatus.isList();
587  }
588  
589  // for SQL-on-FHIR: warnings when .ofType() is not paired with a choice element
590  public void setChoice(boolean b) {
591    choice = true;
592  }
593  public boolean isChoice() {
594    return choice;
595  }
596  public boolean isEmpty() {
597    return types.isEmpty();
598  }
599  public List<String> getProfiles(String t) {
600    t = ProfiledType.ns(t);
601    for (ProfiledType pt : types) {
602      if (t.equals(pt.uri)) {
603        return pt.getProfiles();
604      }
605    }
606    return new ArrayList<String>();
607  }
608  
609}