001package org.hl7.fhir.r5.conformance.profile;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Set;
006
007import org.hl7.fhir.exceptions.FHIRException;
008import org.hl7.fhir.r5.context.IWorkerContext;
009import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
010import org.hl7.fhir.r5.extensions.ExtensionUtilities;
011import org.hl7.fhir.r5.model.BooleanType;
012import org.hl7.fhir.r5.model.CanonicalType;
013import org.hl7.fhir.r5.model.DataType;
014import org.hl7.fhir.r5.model.DateTimeType;
015import org.hl7.fhir.r5.model.DecimalType;
016import org.hl7.fhir.r5.model.ElementDefinition;
017import org.hl7.fhir.r5.model.ElementDefinition.ConstraintSeverity;
018import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
019import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
020import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
021import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
022import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
023import org.hl7.fhir.r5.model.Extension;
024import org.hl7.fhir.r5.model.Property;
025import org.hl7.fhir.r5.model.Quantity;
026import org.hl7.fhir.r5.model.StringType;
027import org.hl7.fhir.r5.model.StructureDefinition;
028import org.hl7.fhir.r5.model.ValueSet;
029import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
030import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
031import org.hl7.fhir.r5.utils.DefinitionNavigator;
032import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
033import org.hl7.fhir.utilities.UUIDUtilities;
034import org.hl7.fhir.utilities.Utilities;
035import org.hl7.fhir.utilities.i18n.I18nConstants;
036import org.hl7.fhir.utilities.validation.ValidationMessage;
037import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
038import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
039import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
040
041public class CompliesWithChecker {
042
043  private IWorkerContext context;
044  private String id;
045
046  public CompliesWithChecker(IWorkerContext context) {
047    super();
048    this.context = context;
049    this.id = UUIDUtilities.makeUuidLC();
050  }
051
052  public List<ValidationMessage> checkCompliesWith(StructureDefinition claimee, StructureDefinition authority) {
053    List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
054    
055    DefinitionNavigator cn = new DefinitionNavigator(context, claimee, false, true);
056    DefinitionNavigator an = new DefinitionNavigator(context, authority, false, true);
057
058    String path = claimee.getType();
059    if (!path.equals(authority.getType())) {
060      messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_WRONG_TYPE, path, authority.getType()), IssueSeverity.ERROR));
061    } else {
062      checkCompliesWith(messages, path, cn, an, false);
063    }
064    return messages;
065  }
066
067  private void checkCompliesWith(List<ValidationMessage> messages, String path, DefinitionNavigator claimee, DefinitionNavigator authority, boolean isSlice) {
068    ElementDefinition c = claimee.current();
069    ElementDefinition a = authority.current();
070    if (checkElementComplies(messages, path, c, a, isSlice)) {
071
072      // if the type and children are the same on both sides, we can stop checking 
073      if (!typesIdentical(c, a) || claimee.hasInlineChildren() || authority.hasInlineChildren()) {
074        // in principle, there is always children, but if the profile 
075        // doesn't walk into them, and there's more than one type, the 
076        // 
077        for (int i = 0; i < authority.children().size(); i++) {
078          DefinitionNavigator anChild = authority.children().get(i);
079          checkCompilesWith(messages, path, claimee, anChild);
080        }
081      }
082    }
083  }
084
085  private void checkCompilesWith(List<ValidationMessage> messages, String path, DefinitionNavigator claimee, DefinitionNavigator anChild) {
086    DefinitionNavigator cnChild = claimee.childByName(anChild.current().getName());
087    String cpath = path+"."+anChild.current().getName();
088    if (cnChild == null) {
089      messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, cpath, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_MISSING, anChild.globalPath()), IssueSeverity.ERROR));
090    } else if (anChild.sliced() || cnChild.sliced()) {
091      if (!cnChild.hasSlices()) {
092        if (anChild.hasSlices()) {
093          // do we care? if the authority slicing is closed, or any are mandatory
094          boolean wecare = anChild.current().getSlicing().getRules() == SlicingRules.CLOSED;
095          for (DefinitionNavigator anSlice : anChild.slices()) {
096            wecare = wecare || anSlice.current().getMin() > 0;
097          }
098          if (wecare) {
099            messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, cpath, 
100              context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_UNSLICED, cpath), IssueSeverity.ERROR));
101          }
102        } 
103        checkCompliesWith(messages, cpath, cnChild, anChild, false);
104     } else if (!anChild.hasSlices()) {
105        for (DefinitionNavigator cc : cnChild.slices()) {
106          checkCompliesWith(messages, cpath+":"+cnChild.current().getSliceName(), cc, anChild, true);
107        }
108      } else {
109        List<ElementDefinitionSlicingDiscriminatorComponent> discriminators = new ArrayList<>();
110        if (slicingCompliesWith(messages, cpath, anChild.current(), cnChild.current(), discriminators)) {
111          List<DefinitionNavigator> processed = new ArrayList<DefinitionNavigator>();
112          for (DefinitionNavigator anSlice : anChild.slices()) {
113            String spath = cpath+":"+anSlice.current().getSliceName();
114            List<DataType> discriminatorValues = new ArrayList<>();
115            List<DefinitionNavigator> cnSlices = findMatchingSlices(cnChild.slices(), discriminators, anSlice, discriminatorValues);
116            if (cnSlices.isEmpty() && anSlice.current().getSlicing().getRules() != SlicingRules.CLOSED) {
117              // if it's closed, then we just don't have any. But if it's not closed, we need the slice 
118              messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, cpath, 
119                  context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_NO_SLICE, spath, discriminatorsToString(discriminators), 
120                      valuesToString(discriminatorValues)), IssueSeverity.ERROR));                      
121            }
122            for (DefinitionNavigator cnSlice : cnSlices) {
123              spath = cpath+":"+cnSlice.current().getSliceName();
124              if (!processed.contains(cnSlice)) {
125                // it's weird if it does - is that a problem?
126                processed.add(cnSlice);
127              }
128              checkCompliesWith(messages, spath, cnSlice, anSlice, false);
129            }
130          }
131          for (DefinitionNavigator cnSlice : cnChild.slices()) {
132            if (!processed.contains(cnSlice)) {
133              if (anChild.current().getSlicing().getRules() != SlicingRules.OPEN) {
134                messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, cpath, 
135                    context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_EXTRA_SLICE, cpath, cnSlice.current().getSliceName()), IssueSeverity.ERROR));
136              }
137              String spath = cpath+":"+cnSlice.current().getSliceName();
138              checkCompliesWith(messages, spath, cnSlice, anChild, true);
139            }
140          }            
141        }
142      }
143    } else {
144      checkCompliesWith(messages, cpath, cnChild, anChild, false);
145    }
146  }
147
148  private Object discriminatorsToString(List<ElementDefinitionSlicingDiscriminatorComponent> discriminators) {
149    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("|"); 
150    for (ElementDefinitionSlicingDiscriminatorComponent dt : discriminators) {
151      b.append(dt.getType().toCode()+":"+dt.getPath());
152    }
153    return b.toString();
154  }
155
156  private String valuesToString(List<DataType> diffValues) {
157    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("|"); 
158    for (DataType dt : diffValues) {
159      b.append(dt.toString());
160    }
161    return b.toString();
162  }
163
164  private List<DefinitionNavigator> findMatchingSlices(List<DefinitionNavigator> slices, List<ElementDefinitionSlicingDiscriminatorComponent> discriminators, DefinitionNavigator anSlice, List<DataType> discriminatorValues) {
165    List<DefinitionNavigator> list = new ArrayList<DefinitionNavigator>();
166    for (ElementDefinitionSlicingDiscriminatorComponent ad : discriminators) {
167      // first, determine the values for each of the discriminators in the authority slice
168      discriminatorValues.add(getDisciminatorValue(anSlice, ad));
169    }
170    for (DefinitionNavigator slice : slices) {
171      List<DataType> values = new ArrayList<>();      
172      for (ElementDefinitionSlicingDiscriminatorComponent ad : discriminators) {
173        // first, determine the values for each of the discriminators in the authority slice
174        values.add(getDisciminatorValue(slice, ad));
175      }
176      boolean ok = true;
177      for (int i = 0; i < discriminators.size(); i++) {
178        if (!isMatch(discriminators.get(i), discriminatorValues.get(i), values.get(i))) {
179          ok = false;
180          break;
181        }
182      }
183      if (ok) {
184        list.add(slice);
185      }
186    }
187    return list;
188  }
189
190  private boolean isMatch(ElementDefinitionSlicingDiscriminatorComponent discriminator, DataType dt1, DataType dt2) {
191    if (dt1 == null) {
192      return dt2 == null;
193    } else if (dt2 == null) {
194      return false;
195    } else {
196      switch (discriminator.getType()) {
197      case EXISTS: return false;
198      case NULL: return false;
199      case PATTERN: return dt1.equalsDeep(dt2); // todo
200      case POSITION: return false;
201      case PROFILE: 
202        StructureDefinition sd1 = context.fetchResource(StructureDefinition.class, dt1.primitiveValue());
203        StructureDefinition sd2 = context.fetchResource(StructureDefinition.class, dt2.primitiveValue());
204        if (sd1 == null || sd2 == null) {
205          return false;
206        }
207        for (Extension ex : sd2.getExtensionsByUrl(ExtensionDefinitions.EXT_SD_COMPLIES_WITH_PROFILE)) {
208          String url = ex.getValue().primitiveValue();
209          if (url != null) {
210            StructureDefinition sde = sd1;
211            while (sde != null) {
212              if (url.equals(sde.getUrl()) || url.equals(sde.getVersionedUrl())) {
213                return true;
214              }
215              sde = context.fetchResource(StructureDefinition.class, sde.getBaseDefinition());
216            }
217          }
218        }
219        for (Extension ex : sd2.getExtensionsByUrl(ExtensionDefinitions.EXT_SD_IMPOSE_PROFILE)) {
220          String url = ex.getValue().primitiveValue();
221          if (url != null) {
222            StructureDefinition sde = sd1;
223            while (sde != null) {
224              if (url.equals(sde.getUrl()) || url.equals(sde.getVersionedUrl())) {
225                return true;
226              }
227              sde = context.fetchResource(StructureDefinition.class, sde.getBaseDefinition());
228            }
229          }
230        }
231        StructureDefinition sde = sd1;
232        while (sde != null) {
233          if (sd2.getVersionedUrl().equals(sde.getVersionedUrl())) {
234            return true;
235          }
236          sde = context.fetchResource(StructureDefinition.class, sde.getBaseDefinition());
237        }
238        return false;
239      case TYPE: return dt1.primitiveValue().equals(dt2.primitiveValue());
240      case VALUE: return dt1.equalsDeep(dt2);
241      default:
242        return false;
243      
244      }
245    }
246  }
247
248  private DataType getDisciminatorValue(DefinitionNavigator anSlice, ElementDefinitionSlicingDiscriminatorComponent ad) {
249    switch (ad.getType()) {
250    case EXISTS: return getExistsDiscriminatorValue(anSlice, ad.getPath());
251    case NULL:throw new FHIRException("Discriminator type 'Null' Not supported yet");
252    case POSITION:throw new FHIRException("Discriminator type 'Position' Not supported yet");
253    case PROFILE: return getProfileDiscriminatorValue(anSlice, ad.getPath());
254    case TYPE: return getTypeDiscriminatorValue(anSlice, ad.getPath());
255    case PATTERN:
256    case VALUE: return getValueDiscriminatorValue(anSlice, ad.getPath());
257    default:
258      throw new FHIRException("Not supported yet");    
259    }
260  }
261
262  private DataType getProfileDiscriminatorValue(DefinitionNavigator anSlice, String path) {
263    DefinitionNavigator pathDN = getByPath(anSlice, path);
264    if (pathDN == null) {
265      return null;
266    }
267    ElementDefinition ed = pathDN.current();
268    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) {
269      return null;
270    } else {
271      return new CanonicalType(ed.getTypeFirstRep().getProfile().get(0).asStringValue());
272    }
273  }
274
275  private DataType getExistsDiscriminatorValue(DefinitionNavigator anSlice, String path) {
276    DefinitionNavigator pathDN = getByPath(anSlice, path);
277    if (pathDN == null) {
278      return null;
279    }
280    ElementDefinition ed = pathDN.current();
281    DataType dt = new BooleanType("1".equals(ed.getMax()));
282    return dt;
283  }
284
285  private DataType getTypeDiscriminatorValue(DefinitionNavigator anSlice, String path) {
286    DefinitionNavigator pathDN = getByPath(anSlice, path);
287    if (pathDN == null) {
288      return null;
289    }
290    ElementDefinition ed = pathDN.current();
291    DataType dt = new StringType(ed.typeSummary());
292    return dt;
293  }
294  
295  private DataType getValueDiscriminatorValue(DefinitionNavigator anSlice, String path) {
296    DefinitionNavigator pathDN = getByPath(anSlice, path);
297    if (pathDN == null) {
298      return null;
299    }
300    ElementDefinition ed = pathDN.current();
301    DataType dt = ed.hasFixed() ? ed.getFixed() : ed.getPattern();
302    return dt;
303  }
304
305  private DefinitionNavigator getByPath(DefinitionNavigator focus, String path) {
306    String segment = path.contains(".") ? path.substring(0, path.indexOf(".")) : path;
307    if ("$this".equals(segment)) {
308      return focus;
309    }
310    DefinitionNavigator p = focus.childByName(segment);
311    if (p != null && path.contains(".")) {
312      return getByPath(p, path.substring(path.indexOf(".")+1)); 
313    } else {
314      return p;
315    }
316  }
317
318  private boolean slicingCompliesWith(List<ValidationMessage> messages, String path, ElementDefinition a, ElementDefinition c, List<ElementDefinitionSlicingDiscriminatorComponent> discriminators) {
319    // the child must be sliced the same as the authority
320    if (!(a.getSlicing().getRules() == SlicingRules.OPEN || c.getSlicing().getRules() == a.getSlicing().getRules())) {
321      messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_RULES, path, a.getSlicing().getRules().toCode(), c.getSlicing().getRules().toCode()), IssueSeverity.ERROR));      
322      return false;
323    } else if (a.getSlicing().getOrdered() && !c.getSlicing().getOrdered()) {
324      messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_ORDER, path), IssueSeverity.ERROR));      
325      return false;
326    } else {
327      // every discriminator that the authority has, the child has to have. The order of discriminators doesn't matter
328      for (ElementDefinitionSlicingDiscriminatorComponent ad : a.getSlicing().getDiscriminator()) {
329        discriminators.add(ad);
330        ElementDefinitionSlicingDiscriminatorComponent cd = null;
331        for (ElementDefinitionSlicingDiscriminatorComponent t : c.getSlicing().getDiscriminator()) {
332          if (t.getType() == ad.getType() && t.getPath().equals(ad.getPath())) {
333            cd = t;
334          }
335        }
336        if (cd == null) {
337          messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_DISCRIMINATOR, path, ad.getType(), ad.getPath()), IssueSeverity.ERROR));
338          return false;
339        }
340      }
341      return true;
342    }
343  }
344
345  private boolean checkElementComplies(List<ValidationMessage> messages, String path, ElementDefinition c, ElementDefinition a, boolean inSlice) {
346    boolean doInner = true;
347    if (!inSlice) {
348      if (a.getMin() > c.getMin()) {
349        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "min", a.getMin(), c.getMin(), c.getId()), IssueSeverity.ERROR));
350      }
351    }
352    if (a.getMaxAsInt() < c.getMaxAsInt()) {
353      messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "max", a.getMax(), c.getMax(), c.getId()), IssueSeverity.ERROR));
354    }
355    if (a.hasFixed()) {
356      if (!c.hasFixed()) {
357        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "fixed", a.getFixed(), null, c.getId()), IssueSeverity.ERROR));
358      } else if (!compliesWith(a.getFixed(), c.getFixed())) {
359        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "fixed", a.getFixed(), c.getFixed(), c.getId()), IssueSeverity.ERROR));
360      }
361    } else if (a.hasPattern()) {
362      if (!c.hasFixed() && !c.hasPattern()) {
363        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "pattern", a.getFixed(), null, c.getId()), IssueSeverity.ERROR));
364      } else if (c.hasFixed()) {
365        if (!compliesWith(a.getFixed(), c.getFixed())) {
366          messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "pattern", a.getFixed(), c.getFixed(), c.getId()), IssueSeverity.ERROR));
367        }
368      } else { // if (c.hasPattern()) 
369        if (!compliesWith(a.getPattern(), c.getPattern())) {
370          messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "pattern", a.getFixed(), c.getPattern(), c.getId()), IssueSeverity.ERROR));
371        }
372      }
373    }
374    if (!"Resource.id".equals(c.getBase().getPath())) { // tricky... there's definitional problems with Resource.id for legacy reasons, but whatever issues there are aren't due to anything the profile did
375      for (TypeRefComponent tr : c.getType()) {
376        if (!hasType(tr, a.getType())) {
377          messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_BAD_TYPE, tr.getWorkingCode()), IssueSeverity.ERROR));        
378        }
379        doInner = false;
380      }
381    }
382    if (a.hasMinValue()) {
383      if (notGreaterThan(a.getMinValue(), c.getMinValue())) {
384        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "minValue", a.getMinValue(), c.getMinValue(), c.getId()), IssueSeverity.ERROR));
385      }
386    }
387    if (a.hasMaxValue()) {
388      if (notLessThan(a.getMaxValue(), c.getMaxValue())) {
389        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "maxValue", a.getMaxValue(), c.getMaxValue(), c.getId()), IssueSeverity.ERROR));
390      }
391    }
392    if (a.hasMaxLength()) {
393      if (a.getMaxLength() < c.getMaxLength()) {
394        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "maxLength", a.getMaxValue(), c.getMaxValue(), c.getId()), IssueSeverity.ERROR));
395      }
396    }
397    if (a.hasMustHaveValue()) {
398      if (a.getMustHaveValue() && !c.getMustHaveValue()) {
399        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "mustHaveValue", a.getMustHaveValue(), c.getMustHaveValue(), c.getId()), IssueSeverity.ERROR));
400      }
401    }
402    if (a.hasValueAlternatives()) {
403      for (CanonicalType ct : c.getValueAlternatives()) {
404        if (!hasCanonical(ct, a.getValueAlternatives())) {
405          messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_BAD_ELEMENT, "valueAlternatives", ct.toString()), IssueSeverity.ERROR));
406        }
407      }
408    }
409    for (ElementDefinitionConstraintComponent cc : a.getConstraint()) {
410      if (cc.getSeverity() == ConstraintSeverity.ERROR) {
411        if (!hasConstraint(cc, c.getConstraint())) {
412          messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_MISSING_ELEMENT, "constraint", cc.getExpression()), IssueSeverity.ERROR));
413        }        
414      }
415    }
416
417    if (a.hasBinding() && a.getBinding().hasValueSet() && (a.getBinding().getStrength() == BindingStrength.REQUIRED || a.getBinding().getStrength() == BindingStrength.EXTENSIBLE)) {
418      if (!c.hasBinding()) {
419        if (isBindableType(c)) {
420          messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "binding", a.getBinding().getValueSet(), "null", c.getId()), IssueSeverity.ERROR));
421        }
422      } else if (c.getBinding().getStrength() != BindingStrength.REQUIRED && c.getBinding().getStrength() != BindingStrength.EXTENSIBLE) {
423        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "binding.strength", a.getBinding().getStrength(), c.getBinding().getStrength(), c.getId()), IssueSeverity.ERROR));
424      } else if (c.getBinding().getStrength() == BindingStrength.EXTENSIBLE && a.getBinding().getStrength() == BindingStrength.REQUIRED) {
425        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "binding.strength", a.getBinding().getStrength(), c.getBinding().getStrength(), c.getId()), IssueSeverity.ERROR));
426      } else if (!c.getBinding().getValueSet().equals(a.getBinding().getValueSet())) {
427        ValueSet cVS = context.fetchResource(ValueSet.class, c.getBinding().getValueSet());
428        ValueSet aVS = context.fetchResource(ValueSet.class, a.getBinding().getValueSet());
429        if (aVS == null || cVS == null) {
430          if (aVS == null) {
431            messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS, a.getBinding().getValueSet()), IssueSeverity.WARNING));
432          } else {
433            messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS, c.getBinding().getValueSet()), IssueSeverity.WARNING));
434          }
435        } else {
436          ValueSetExpansionOutcome cExp = context.expandVS(cVS, true, false);
437          ValueSetExpansionOutcome aExp = context.expandVS(aVS, true, false);
438          if (!cExp.isOk() || !aExp.isOk()) {
439            if (!aExp.isOk()) {
440             messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS_EXP, aVS.getVersionedUrl(), aExp.getError()), IssueSeverity.WARNING));
441            } else {
442              messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS_EXP, cVS.getVersionedUrl(), cExp.getError()), IssueSeverity.WARNING));  
443            }            
444          } else {
445            Set<String> wrong = ValueSetUtilities.checkExpansionSubset(aExp.getValueset(), cExp.getValueset());
446            if (!wrong.isEmpty()) {
447              messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS_NO, cVS.getVersionedUrl(), aVS.getVersionedUrl(), 
448                    CommaSeparatedStringBuilder.joinToLimit(", ", 5, "etc", wrong)), c.getBinding().getStrength() == BindingStrength.REQUIRED ? IssueSeverity.ERROR : IssueSeverity.WARNING));
449            }
450          }
451        }
452      }
453    }
454    return doInner;
455  }
456
457  private boolean isBindableType(ElementDefinition c) {
458    for (TypeRefComponent t : c.getType()) {
459      if (isBindableType(t.getWorkingCode())) {
460        return true;
461      }
462    }
463    return false;
464  }
465
466  private boolean isBindableType(String type) {
467    if (Utilities.existsInList(type, "CodeableConcept", "Coding", "code", "CodeableReference", "string", "uri", "Quantity")) {
468      return true;
469    }
470    StructureDefinition sd = context.fetchTypeDefinition(type);
471    if (sd == null) {
472      return false;
473    }
474    for (Extension ex : sd.getExtensionsByUrl(ExtensionDefinitions.EXT_TYPE_CHARACTERISTICS)) {
475      if ("can-bind".equals(ex.getValue().primitiveValue())) {
476        return true;
477      }
478    }
479    return false;
480  }
481
482  private boolean typesIdentical(ElementDefinition c, ElementDefinition a) {
483    if (c.getType().size() != a.getType().size()) {
484      return false;
485    }
486    for (TypeRefComponent ct : c.getType()) {
487      TypeRefComponent at = getType(a.getType(), ct.getCode());
488      if (at == null || !at.equalsDeep(ct)) {
489        return false;
490      }
491    }
492    return true;
493  }
494
495  private TypeRefComponent getType(List<TypeRefComponent> types, String code) {
496    for (TypeRefComponent t : types) {
497      if (code.equals(t.getCode())) {
498        return t;
499      }
500    }
501    return null;
502  }
503
504  private boolean hasConstraint(ElementDefinitionConstraintComponent cc, List<ElementDefinitionConstraintComponent> list) {
505    for (ElementDefinitionConstraintComponent  t : list) {
506      if (t.getSeverity() == ConstraintSeverity.ERROR && t.getExpression().equals(cc.getExpression())) {
507        return true;
508      }
509    }
510    return false;
511  }
512
513  private boolean hasCanonical(CanonicalType ct, List<CanonicalType> list) {
514    for (CanonicalType t : list) {
515      if (t.hasValue() && t.getValue().equals(ct.getValue())) {
516        return true;
517      }
518    }
519    return false;
520  }
521
522  private boolean notLessThan(DataType v1, DataType v2) {
523    if (v2 == null) {
524      return true;
525    }
526    if (!v1.fhirType().equals(v2.fhirType())) {
527      return true;
528    }
529    switch (v1.fhirType()) {
530    case "date" :
531    case "dateTime":
532    case "instant":
533      return !((DateTimeType) v1).before((DateTimeType) v2);
534    case "time":
535      return v1.primitiveValue().compareTo(v2.primitiveValue()) >= 0;
536    case "decimal":
537      return ((DecimalType) v1).compareTo((DecimalType) v2) >= 0;
538    case "integer":
539    case "integer64":
540    case "positiveInt":
541    case "unsignedInt":
542      int i1 = Integer.parseInt(v1.toString());
543      int i2 = Integer.parseInt(v2.toString());
544      return i1 >= i2; 
545    case "Quantity":
546      Quantity q1 = (Quantity) v1;
547      Quantity q2 = (Quantity) v2;
548      
549    default: 
550      return true;
551    }
552  }
553
554  private boolean notGreaterThan(DataType minValue, DataType minValue2) {
555    // TODO Auto-generated method stub
556    return false;
557  }
558
559  private boolean hasType(TypeRefComponent tr, List<TypeRefComponent> types) {
560    for (TypeRefComponent t : types) {
561      if (t.getWorkingCode().equals(tr.getWorkingCode())) {
562        boolean ok = t.getVersioning() == tr.getVersioning();
563        // we don't care about profile - that's the whole point, we just go ehad and check that
564//        for (CanonicalType ct : tr.getProfile()) {
565//          if (!t.hasProfile(ct.asStringValue())) {
566//            ok = false;
567//          }
568//        }
569        // todo: we have to check that the targets are compatible
570//        for (CanonicalType ct : tr.getTargetProfile()) {
571//          if (!t.hasTargetProfile(ct.asStringValue())) {
572//            ok = false;
573//          }
574//        }
575        if (ok) {
576          return true;
577        }
578      }
579    }
580    return false;
581  }
582
583  private boolean compliesWith(DataType authority, DataType test) {
584    if (!authority.fhirType().equals(test.fhirType())) {
585      return false;
586    }
587    if (authority.isPrimitive()) {
588      if (!authority.primitiveValue().equals(test.primitiveValue())) {
589        return false;
590      }
591    } 
592    for (Property p : authority.children()) {
593      if (p.hasValues()) {
594        Property pt = test.getNamedProperty(p.getName());
595        if (p.getValues().size() > pt.getValues().size()) {
596          return false;
597        } else {
598          for (int i = 0; i < pt.getValues().size(); i++) {
599            DataType v = (DataType) p.getValues().get(i);
600            DataType vt = (DataType) pt.getValues().get(i);
601            if (!compliesWith(v, vt)) {
602              return false;
603            }
604          }
605        }
606      }      
607    }
608    return true;
609  }
610}