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