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