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