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