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