001package org.hl7.fhir.r4.conformance;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Map;
010
011import org.hl7.fhir.r4.context.IWorkerContext;
012import org.hl7.fhir.r4.formats.IParser;
013import org.hl7.fhir.r4.model.Base;
014import org.hl7.fhir.r4.model.Coding;
015import org.hl7.fhir.r4.model.ElementDefinition;
016import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
017import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent;
018import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent;
019import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
020import org.hl7.fhir.r4.model.Enumerations.BindingStrength;
021import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
022import org.hl7.fhir.r4.model.IntegerType;
023import org.hl7.fhir.r4.model.PrimitiveType;
024import org.hl7.fhir.r4.model.Reference;
025import org.hl7.fhir.r4.model.StringType;
026import org.hl7.fhir.r4.model.StructureDefinition;
027import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
028import org.hl7.fhir.r4.model.Type;
029import org.hl7.fhir.r4.model.UriType;
030import org.hl7.fhir.r4.model.ValueSet;
031import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent;
032import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
033import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
034import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
035import org.hl7.fhir.r4.utils.DefinitionNavigator;
036import org.hl7.fhir.r4.utils.ToolingExtensions;
037import org.hl7.fhir.exceptions.DefinitionException;
038import org.hl7.fhir.exceptions.FHIRFormatError;
039import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
040import org.hl7.fhir.utilities.Utilities;
041import org.hl7.fhir.utilities.validation.ValidationMessage;
042import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
043
044/**
045 * A engine that generates difference analysis between two sets of structure 
046 * definitions, typically from 2 different implementation guides. 
047 * 
048 * How this class works is that you create it with access to a bunch of underying
049 * resources that includes all the structure definitions from both implementation 
050 * guides 
051 * 
052 * Once the class is created, you repeatedly pass pairs of structure definitions,
053 * one from each IG, building up a web of difference analyses. This class will
054 * automatically process any internal comparisons that it encounters
055 * 
056 * When all the comparisons have been performed, you can then generate a variety
057 * of output formats
058 * 
059 * @author Grahame Grieve
060 *
061 */
062public class ProfileComparer {
063
064  private IWorkerContext context;
065  
066  public ProfileComparer(IWorkerContext context) {
067    super();
068    this.context = context;
069  }
070
071  private static final int BOTH_NULL = 0;
072  private static final int EITHER_NULL = 1;
073
074  public class ProfileComparison {
075    private String id;
076    /**
077     * the first of two structures that were compared to generate this comparison
078     * 
079     *   In a few cases - selection of example content and value sets - left gets 
080     *   preference over right
081     */
082    private StructureDefinition left;
083
084    /**
085     * the second of two structures that were compared to generate this comparison
086     * 
087     *   In a few cases - selection of example content and value sets - left gets 
088     *   preference over right
089     */
090    private StructureDefinition right;
091
092    
093    public String getId() {
094      return id;
095    }
096    private String leftName() {
097      return left.getName();
098    }
099    private String rightName() {
100      return right.getName();
101    }
102
103    /**
104     * messages generated during the comparison. There are 4 grades of messages:
105     *   information - a list of differences between structures
106     *   warnings - notifies that the comparer is unable to fully compare the structures (constraints differ, open value sets)
107     *   errors - where the structures are incompatible
108     *   fatal errors - some error that prevented full analysis 
109     * 
110     * @return
111     */
112    private List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
113
114    /**
115     * The structure that describes all instances that will conform to both structures 
116     */
117    private StructureDefinition subset;
118
119    /**
120     * The structure that describes all instances that will conform to either structures 
121     */
122    private StructureDefinition superset;
123
124    public StructureDefinition getLeft() {
125      return left;
126    }
127
128    public StructureDefinition getRight() {
129      return right;
130    }
131
132    public List<ValidationMessage> getMessages() {
133      return messages;
134    }
135
136    public StructureDefinition getSubset() {
137      return subset;
138    }
139
140    public StructureDefinition getSuperset() {
141      return superset;
142    }
143    
144    private boolean ruleEqual(String path, ElementDefinition ed, String vLeft, String vRight, String description, boolean nullOK) {
145      if (vLeft == null && vRight == null && nullOK)
146        return true;
147      if (vLeft == null && vRight == null) {
148        messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, description+" and not null (null/null)", ValidationMessage.IssueSeverity.ERROR));
149        if (ed != null)
150          status(ed, ProfileUtilities.STATUS_ERROR);
151      }
152      if (vLeft == null || !vLeft.equals(vRight)) {
153        messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, description+" ("+vLeft+"/"+vRight+")", ValidationMessage.IssueSeverity.ERROR));
154        if (ed != null)
155          status(ed, ProfileUtilities.STATUS_ERROR);
156      }
157      return true;
158    }
159    
160    private boolean ruleCompares(ElementDefinition ed, Type vLeft, Type vRight, String path, int nullStatus) throws IOException {
161      if (vLeft == null && vRight == null && nullStatus == BOTH_NULL)
162        return true;
163      if (vLeft == null && vRight == null) {
164        messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Must be the same and not null (null/null)", ValidationMessage.IssueSeverity.ERROR));
165        status(ed, ProfileUtilities.STATUS_ERROR);
166      }
167      if (vLeft == null && nullStatus == EITHER_NULL)
168        return true;
169      if (vRight == null && nullStatus == EITHER_NULL)
170        return true;
171      if (vLeft == null || vRight == null || !Base.compareDeep(vLeft, vRight, false)) {
172        messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Must be the same ("+toString(vLeft)+"/"+toString(vRight)+")", ValidationMessage.IssueSeverity.ERROR));
173        status(ed, ProfileUtilities.STATUS_ERROR);
174      }
175      return true;
176    }
177
178    private boolean rule(ElementDefinition ed, boolean test, String path, String message) {
179      if (!test)  {
180        messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, message, ValidationMessage.IssueSeverity.ERROR));
181        status(ed, ProfileUtilities.STATUS_ERROR);
182      }
183      return test;
184    }
185
186    private boolean ruleEqual(ElementDefinition ed, boolean vLeft, boolean vRight, String path, String elementName) {
187      if (vLeft != vRight) {
188        messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, elementName+" must be the same ("+vLeft+"/"+vRight+")", ValidationMessage.IssueSeverity.ERROR));
189        status(ed, ProfileUtilities.STATUS_ERROR);
190      }
191      return true;
192    }
193
194    private String toString(Type val) throws IOException {
195      if (val instanceof PrimitiveType) 
196        return "\"" + ((PrimitiveType) val).getValueAsString()+"\"";
197      
198      IParser jp = context.newJsonParser();
199      return jp.composeString(val, "value");
200    }
201    
202    public String getErrorCount() {
203      int c = 0;
204      for (ValidationMessage vm : messages)
205        if (vm.getLevel() == ValidationMessage.IssueSeverity.ERROR)
206          c++;
207      return Integer.toString(c);
208    }
209
210    public String getWarningCount() {
211      int c = 0;
212      for (ValidationMessage vm : messages)
213        if (vm.getLevel() == ValidationMessage.IssueSeverity.WARNING)
214          c++;
215      return Integer.toString(c);
216    }
217    
218    public String getHintCount() {
219      int c = 0;
220      for (ValidationMessage vm : messages)
221        if (vm.getLevel() == ValidationMessage.IssueSeverity.INFORMATION)
222          c++;
223      return Integer.toString(c);
224    }
225  }
226  
227  /**
228   * Value sets used in the subset and superset
229   */
230  private List<ValueSet> valuesets = new ArrayList<ValueSet>();
231  private List<ProfileComparison> comparisons = new ArrayList<ProfileComparison>();
232  private String id; 
233  private String title;
234  private String leftLink;
235  private String leftName;
236  private String rightLink;
237  private String rightName;
238  
239  
240  public List<ValueSet> getValuesets() {
241    return valuesets;
242  }
243
244  public void status(ElementDefinition ed, int value) {
245    ed.setUserData(ProfileUtilities.UD_ERROR_STATUS, Math.max(value, ed.getUserInt("error-status")));
246  }
247
248  public List<ProfileComparison> getComparisons() {
249    return comparisons;
250  }
251
252  /**
253   * Compare left and right structure definitions to see whether they are consistent or not
254   * 
255   * Note that left and right are arbitrary choices. In one respect, left 
256   * is 'preferred' - the left's example value and data sets will be selected 
257   * over the right ones in the common structure definition
258   * @throws DefinitionException 
259   * @throws IOException 
260   * @throws FHIRFormatError 
261   *  
262   * @
263   */
264  public ProfileComparison compareProfiles(StructureDefinition left, StructureDefinition right) throws DefinitionException, IOException, FHIRFormatError {
265    ProfileComparison outcome = new ProfileComparison();
266    outcome.left = left;
267    outcome.right = right;
268    
269    if (left == null)
270      throw new DefinitionException("No StructureDefinition provided (left)");
271    if (right == null)
272      throw new DefinitionException("No StructureDefinition provided (right)");
273    if (!left.hasSnapshot())
274      throw new DefinitionException("StructureDefinition has no snapshot (left: "+outcome.leftName()+")");
275    if (!right.hasSnapshot())
276      throw new DefinitionException("StructureDefinition has no snapshot (right: "+outcome.rightName()+")");
277    if (left.getSnapshot().getElement().isEmpty())
278      throw new DefinitionException("StructureDefinition snapshot is empty (left: "+outcome.leftName()+")");
279    if (right.getSnapshot().getElement().isEmpty())
280      throw new DefinitionException("StructureDefinition snapshot is empty (right: "+outcome.rightName()+")");
281
282    for (ProfileComparison pc : comparisons) 
283      if (pc.left.getUrl().equals(left.getUrl()) && pc.right.getUrl().equals(right.getUrl()))
284        return pc;
285
286    outcome.id = Integer.toString(comparisons.size()+1);
287    comparisons.add(outcome);
288    
289    DefinitionNavigator ln = new DefinitionNavigator(context, left);
290    DefinitionNavigator rn = new DefinitionNavigator(context, right);
291    
292    // from here on in, any issues go in messages
293    outcome.superset = new StructureDefinition();
294    outcome.subset = new StructureDefinition();
295    if (outcome.ruleEqual(ln.path(), null,ln.path(), rn.path(), "Base Type is not compatible", false)) {
296      if (compareElements(outcome, ln.path(), ln, rn)) {
297        outcome.subset.setName("intersection of "+outcome.leftName()+" and "+outcome.rightName());
298        outcome.subset.setStatus(PublicationStatus.DRAFT);
299        outcome.subset.setKind(outcome.left.getKind());
300        outcome.subset.setType(outcome.left.getType());
301        outcome.subset.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/"+outcome.subset.getType());
302        outcome.subset.setDerivation(TypeDerivationRule.CONSTRAINT);
303        outcome.subset.setAbstract(false);
304        outcome.superset.setName("union of "+outcome.leftName()+" and "+outcome.rightName());
305        outcome.superset.setStatus(PublicationStatus.DRAFT);
306        outcome.superset.setKind(outcome.left.getKind());
307        outcome.superset.setType(outcome.left.getType());
308        outcome.superset.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/"+outcome.subset.getType());
309        outcome.superset.setAbstract(false);
310        outcome.superset.setDerivation(TypeDerivationRule.CONSTRAINT);
311      } else {
312        outcome.subset = null;
313        outcome.superset = null;
314      }
315    }
316    return outcome;
317  }
318
319  /**
320   * left and right refer to the same element. Are they compatible?   
321   * @param outcome 
322   * @param outcome
323   * @param path
324   * @param left
325   * @param right
326   * @- if there's a problem that needs fixing in this code
327   * @throws DefinitionException 
328   * @throws IOException 
329   * @throws FHIRFormatError 
330   */
331  private boolean compareElements(ProfileComparison outcome, String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError {
332//    preconditions:
333    assert(path != null);
334    assert(left != null);
335    assert(right != null);
336    assert(left.path().equals(right.path()));
337    
338    // we ignore slicing right now - we're going to clone the root one anyway, and then think about clones 
339    // simple stuff
340    ElementDefinition subset = new ElementDefinition();
341    subset.setPath(left.path());
342    
343    // not allowed to be different: 
344    subset.getRepresentation().addAll(left.current().getRepresentation()); // can't be bothered even testing this one
345    if (!outcome.ruleCompares(subset, left.current().getDefaultValue(), right.current().getDefaultValue(), path+".defaultValue[x]", BOTH_NULL))
346      return false;
347    subset.setDefaultValue(left.current().getDefaultValue());
348    if (!outcome.ruleEqual(path, subset, left.current().getMeaningWhenMissing(), right.current().getMeaningWhenMissing(), "meaningWhenMissing Must be the same", true))
349      return false;
350    subset.setMeaningWhenMissing(left.current().getMeaningWhenMissing());
351    if (!outcome.ruleEqual(subset, left.current().getIsModifier(), right.current().getIsModifier(), path, "isModifier"))
352      return false;
353    subset.setIsModifier(left.current().getIsModifier());
354    if (!outcome.ruleEqual(subset, left.current().getIsSummary(), right.current().getIsSummary(), path, "isSummary"))
355      return false;
356    subset.setIsSummary(left.current().getIsSummary());
357    
358    // descriptive properties from ElementDefinition - merge them:
359    subset.setLabel(mergeText(subset, outcome, path, "label", left.current().getLabel(), right.current().getLabel()));
360    subset.setShort(mergeText(subset, outcome, path, "short", left.current().getShort(), right.current().getShort()));
361    subset.setDefinition(mergeText(subset, outcome, path, "definition", left.current().getDefinition(), right.current().getDefinition()));
362    subset.setComment(mergeText(subset, outcome, path, "comments", left.current().getComment(), right.current().getComment()));
363    subset.setRequirements(mergeText(subset, outcome, path, "requirements", left.current().getRequirements(), right.current().getRequirements()));
364    subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode()));
365    subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias()));
366    subset.getMapping().addAll(mergeMappings(left.current().getMapping(), right.current().getMapping()));
367    // left will win for example
368    subset.setExample(left.current().hasExample() ? left.current().getExample() : right.current().getExample());
369
370    subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport());
371    ElementDefinition superset = subset.copy();
372
373
374    // compare and intersect
375    superset.setMin(unionMin(left.current().getMin(), right.current().getMin()));
376    superset.setMax(unionMax(left.current().getMax(), right.current().getMax()));
377    subset.setMin(intersectMin(left.current().getMin(), right.current().getMin()));
378    subset.setMax(intersectMax(left.current().getMax(), right.current().getMax()));
379    outcome.rule(subset, subset.getMax().equals("*") || Integer.parseInt(subset.getMax()) >= subset.getMin(), path, "Cardinality Mismatch: "+card(left)+"/"+card(right));
380    
381    superset.getType().addAll(unionTypes(path, left.current().getType(), right.current().getType()));
382    subset.getType().addAll(intersectTypes(subset, outcome, path, left.current().getType(), right.current().getType()));
383    outcome.rule(subset, !subset.getType().isEmpty() || (!left.current().hasType() && !right.current().hasType()), path, "Type Mismatch:\r\n  "+typeCode(left)+"\r\n  "+typeCode(right));
384//    <fixed[x]><!-- ?? 0..1 * Value must be exactly this --></fixed[x]>
385//    <pattern[x]><!-- ?? 0..1 * Value must have at least these property values --></pattern[x]>
386    superset.setMaxLengthElement(unionMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
387    subset.setMaxLengthElement(intersectMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
388    if (left.current().hasBinding() || right.current().hasBinding()) {
389      compareBindings(outcome, subset, superset, path, left.current(), right.current());
390    }
391
392    // note these are backwards
393    superset.getConstraint().addAll(intersectConstraints(path, left.current().getConstraint(), right.current().getConstraint()));
394    subset.getConstraint().addAll(unionConstraints(subset, outcome, path, left.current().getConstraint(), right.current().getConstraint()));
395
396    // now process the slices
397    if (left.current().hasSlicing() || right.current().hasSlicing()) {
398      if (isExtension(left.path()))
399        return compareExtensions(outcome, path, superset, subset, left, right);
400//      return true;
401      else
402        throw new DefinitionException("Slicing is not handled yet");
403    // todo: name 
404    }
405
406    // add the children
407    outcome.subset.getSnapshot().getElement().add(subset);
408    outcome.superset.getSnapshot().getElement().add(superset);
409    return compareChildren(subset, outcome, path, left, right);
410  }
411
412  private class ExtensionUsage {
413    private DefinitionNavigator defn;
414    private int minSuperset;
415    private int minSubset;
416    private String maxSuperset;
417    private String maxSubset;
418    private boolean both = false;
419    
420    public ExtensionUsage(DefinitionNavigator defn, int min, String max) {
421      super();
422      this.defn = defn;
423      this.minSubset = min;
424      this.minSuperset = min;
425      this.maxSubset = max;
426      this.maxSuperset = max;
427    }
428    
429  }
430  private boolean compareExtensions(ProfileComparison outcome, String path, ElementDefinition superset, ElementDefinition subset, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException {
431    // for now, we don't handle sealed (or ordered) extensions
432    
433    // for an extension the superset is all extensions, and the subset is.. all extensions - well, unless thay are sealed. 
434    // but it's not useful to report that. instead, we collate the defined ones, and just adjust the cardinalities
435    Map<String, ExtensionUsage> map = new HashMap<String, ExtensionUsage>();
436    
437    if (left.slices() != null)
438      for (DefinitionNavigator ex : left.slices()) {
439        String url = ex.current().getType().get(0).getProfile().get(0).getValue();
440        if (map.containsKey(url))
441          throw new DefinitionException("Duplicate Extension "+url+" at "+path);
442        else
443          map.put(url, new ExtensionUsage(ex, ex.current().getMin(), ex.current().getMax()));
444      }
445    if (right.slices() != null)
446      for (DefinitionNavigator ex : right.slices()) {
447        String url = ex.current().getType().get(0).getProfile().get(0).getValue();
448        if (map.containsKey(url)) {
449          ExtensionUsage exd = map.get(url);
450          exd.minSuperset = unionMin(exd.defn.current().getMin(), ex.current().getMin());
451          exd.maxSuperset = unionMax(exd.defn.current().getMax(), ex.current().getMax());
452          exd.minSubset = intersectMin(exd.defn.current().getMin(), ex.current().getMin());
453          exd.maxSubset = intersectMax(exd.defn.current().getMax(), ex.current().getMax());
454          exd.both = true;
455          outcome.rule(subset, exd.maxSubset.equals("*") || Integer.parseInt(exd.maxSubset) >= exd.minSubset, path, "Cardinality Mismatch on extension: "+card(exd.defn)+"/"+card(ex));
456        } else {
457          map.put(url, new ExtensionUsage(ex, ex.current().getMin(), ex.current().getMax()));
458        }
459      }
460    List<String> names = new ArrayList<String>();
461    names.addAll(map.keySet());
462    Collections.sort(names);
463    for (String name : names) {
464      ExtensionUsage exd = map.get(name);
465      if (exd.both)
466        outcome.subset.getSnapshot().getElement().add(exd.defn.current().copy().setMin(exd.minSubset).setMax(exd.maxSubset));
467      outcome.superset.getSnapshot().getElement().add(exd.defn.current().copy().setMin(exd.minSuperset).setMax(exd.maxSuperset));
468    }    
469    return true;
470  }
471
472  private boolean isExtension(String path) {
473    return path.endsWith(".extension") || path.endsWith(".modifierExtension");
474  }
475
476  private boolean compareChildren(ElementDefinition ed, ProfileComparison outcome, String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError {
477    List<DefinitionNavigator> lc = left.children();
478    List<DefinitionNavigator> rc = right.children();
479    // it's possible that one of these profiles walks into a data type and the other doesn't
480    // if it does, we have to load the children for that data into the profile that doesn't 
481    // walk into it
482    if (lc.isEmpty() && !rc.isEmpty() && right.current().getType().size() == 1 && left.hasTypeChildren(right.current().getType().get(0)))
483      lc = left.childrenFromType(right.current().getType().get(0));
484    if (rc.isEmpty() && !lc.isEmpty() && left.current().getType().size() == 1 && right.hasTypeChildren(left.current().getType().get(0)))
485      rc = right.childrenFromType(left.current().getType().get(0));
486    if (lc.size() != rc.size()) {
487      outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Different number of children at "+path+" ("+Integer.toString(lc.size())+"/"+Integer.toString(rc.size())+")", ValidationMessage.IssueSeverity.ERROR));
488      status(ed, ProfileUtilities.STATUS_ERROR);
489      return false;      
490    } else {
491      for (int i = 0; i < lc.size(); i++) {
492        DefinitionNavigator l = lc.get(i);
493        DefinitionNavigator r = rc.get(i);
494        String cpath = comparePaths(l.path(), r.path(), path, l.nameTail(), r.nameTail());
495        if (cpath != null) {
496          if (!compareElements(outcome, cpath, l, r))
497            return false;
498        } else {
499          outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Different path at "+path+"["+Integer.toString(i)+"] ("+l.path()+"/"+r.path()+")", ValidationMessage.IssueSeverity.ERROR));
500          status(ed, ProfileUtilities.STATUS_ERROR);
501          return false;
502        }
503      }
504    }
505    return true;
506  }
507
508  private String comparePaths(String path1, String path2, String path, String tail1, String tail2) {
509    if (tail1.equals(tail2)) {
510      return path+"."+tail1;
511    } else if (tail1.endsWith("[x]") && tail2.startsWith(tail1.substring(0, tail1.length()-3))) {
512      return path+"."+tail1;
513    } else if (tail2.endsWith("[x]") && tail1.startsWith(tail2.substring(0, tail2.length()-3))) {
514      return path+"."+tail2;
515    } else 
516      return null;
517  }
518
519  private boolean compareBindings(ProfileComparison outcome, ElementDefinition subset, ElementDefinition superset, String path, ElementDefinition lDef, ElementDefinition rDef) throws FHIRFormatError {
520    assert(lDef.hasBinding() || rDef.hasBinding());
521    if (!lDef.hasBinding()) {
522      subset.setBinding(rDef.getBinding());
523      // technically, the super set is unbound, but that's not very useful - so we use the provided on as an example
524      superset.setBinding(rDef.getBinding().copy());
525      superset.getBinding().setStrength(BindingStrength.EXAMPLE);
526      return true;
527    }
528    if (!rDef.hasBinding()) {
529      subset.setBinding(lDef.getBinding());
530      superset.setBinding(lDef.getBinding().copy());
531      superset.getBinding().setStrength(BindingStrength.EXAMPLE);
532      return true;
533    }
534    ElementDefinitionBindingComponent left = lDef.getBinding();
535    ElementDefinitionBindingComponent right = rDef.getBinding();
536    if (Base.compareDeep(left, right, false)) {
537      subset.setBinding(left);
538      superset.setBinding(right);      
539    }
540    
541    // if they're both examples/preferred then:
542    // subset: left wins if they're both the same
543    // superset: 
544    if (isPreferredOrExample(left) && isPreferredOrExample(right)) {
545      if (right.getStrength() == BindingStrength.PREFERRED && left.getStrength() == BindingStrength.EXAMPLE && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) { 
546        outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Example/preferred bindings differ at "+path+" using binding from "+outcome.rightName(), ValidationMessage.IssueSeverity.INFORMATION));
547        status(subset, ProfileUtilities.STATUS_HINT);
548        subset.setBinding(right);
549        superset.setBinding(unionBindings(superset, outcome, path, left, right));
550      } else {
551        if ((right.getStrength() != BindingStrength.EXAMPLE || left.getStrength() != BindingStrength.EXAMPLE) && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false) ) { 
552          outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Example/preferred bindings differ at "+path+" using binding from "+outcome.leftName(), ValidationMessage.IssueSeverity.INFORMATION));
553          status(subset, ProfileUtilities.STATUS_HINT);
554        }
555        subset.setBinding(left);
556        superset.setBinding(unionBindings(superset, outcome, path, left, right));
557      }
558      return true;
559    }
560    // if either of them are extensible/required, then it wins
561    if (isPreferredOrExample(left)) {
562      subset.setBinding(right);
563      superset.setBinding(unionBindings(superset, outcome, path, left, right));
564      return true;
565    }
566    if (isPreferredOrExample(right)) {
567      subset.setBinding(left);
568      superset.setBinding(unionBindings(superset, outcome, path, left, right));
569      return true;
570    }
571    
572    // ok, both are extensible or required.
573    ElementDefinitionBindingComponent subBinding = new ElementDefinitionBindingComponent();
574    subset.setBinding(subBinding);
575    ElementDefinitionBindingComponent superBinding = new ElementDefinitionBindingComponent();
576    superset.setBinding(superBinding);
577    subBinding.setDescription(mergeText(subset, outcome, path, "description", left.getDescription(), right.getDescription()));
578    superBinding.setDescription(mergeText(subset, outcome, null, "description", left.getDescription(), right.getDescription()));
579    if (left.getStrength() == BindingStrength.REQUIRED || right.getStrength() == BindingStrength.REQUIRED)
580      subBinding.setStrength(BindingStrength.REQUIRED);
581    else
582      subBinding.setStrength(BindingStrength.EXTENSIBLE);
583    if (left.getStrength() == BindingStrength.EXTENSIBLE || right.getStrength() == BindingStrength.EXTENSIBLE)
584      superBinding.setStrength(BindingStrength.EXTENSIBLE);
585    else
586      superBinding.setStrength(BindingStrength.REQUIRED);
587    
588    if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) {
589      subBinding.setValueSet(left.getValueSet());
590      superBinding.setValueSet(left.getValueSet());
591      return true;
592    } else if (!left.hasValueSet()) {
593      outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "No left Value set at "+path, ValidationMessage.IssueSeverity.ERROR));
594      return true;      
595    } else if (!right.hasValueSet()) {
596      outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "No right Value set at "+path, ValidationMessage.IssueSeverity.ERROR));
597      return true;      
598    } else {
599      // ok, now we compare the value sets. This may be unresolvable. 
600      ValueSet lvs = resolveVS(outcome.left, left.getValueSet());
601      ValueSet rvs = resolveVS(outcome.right, right.getValueSet());
602      if (lvs == null) {
603        outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Unable to resolve left value set "+left.getValueSet().toString()+" at "+path, ValidationMessage.IssueSeverity.ERROR));
604        return true;
605      } else if (rvs == null) {
606        outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Unable to resolve right value set "+right.getValueSet().toString()+" at "+path, ValidationMessage.IssueSeverity.ERROR));
607        return true;        
608      } else {
609        // first, we'll try to do it by definition
610        ValueSet cvs = intersectByDefinition(lvs, rvs);
611        if(cvs == null) {
612          // if that didn't work, we'll do it by expansion
613          ValueSetExpansionOutcome le;
614          ValueSetExpansionOutcome re;
615          try {
616            le = context.expandVS(lvs, true, false);
617            re = context.expandVS(rvs, true, false);
618            if (!closed(le.getValueset()) || !closed(re.getValueset())) 
619              throw new DefinitionException("unclosed value sets are not handled yet");
620            cvs = intersectByExpansion(lvs, rvs);
621            if (!cvs.getCompose().hasInclude()) {
622              outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value sets "+lvs.getUrl()+" and "+rvs.getUrl()+" do not intersect", ValidationMessage.IssueSeverity.ERROR));
623              status(subset, ProfileUtilities.STATUS_ERROR);
624              return false;
625            }
626          } catch (Exception e){
627            outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Unable to expand or process value sets "+lvs.getUrl()+" and "+rvs.getUrl()+": "+e.getMessage(), ValidationMessage.IssueSeverity.ERROR));
628            status(subset, ProfileUtilities.STATUS_ERROR);
629            return false;          
630          }
631        }
632        subBinding.setValueSet(new Reference().setReference("#"+addValueSet(cvs)));
633        superBinding.setValueSet(new Reference().setReference("#"+addValueSet(unite(superset, outcome, path, lvs, rvs))));
634      }
635    }
636    return false;
637  }
638
639  private ElementDefinitionBindingComponent unionBindings(ElementDefinition ed, ProfileComparison outcome, String path, ElementDefinitionBindingComponent left, ElementDefinitionBindingComponent right) throws FHIRFormatError {
640    ElementDefinitionBindingComponent union = new ElementDefinitionBindingComponent();
641    if (left.getStrength().compareTo(right.getStrength()) < 0)
642      union.setStrength(left.getStrength());
643    else
644      union.setStrength(right.getStrength());
645    union.setDescription(mergeText(ed, outcome, path, "binding.description", left.getDescription(), right.getDescription()));
646    if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false))
647      union.setValueSet(left.getValueSet());
648    else {
649      ValueSet lvs = resolveVS(outcome.left, left.getValueSet());
650      ValueSet rvs = resolveVS(outcome.left, right.getValueSet());
651      if (lvs != null && rvs != null)
652        union.setValueSet(new Reference().setReference("#"+addValueSet(unite(ed, outcome, path, lvs, rvs))));
653      else if (lvs != null)
654        union.setValueSet(new Reference().setReference("#"+addValueSet(lvs)));
655      else if (rvs != null)
656        union.setValueSet(new Reference().setReference("#"+addValueSet(rvs)));
657    }
658    return union;
659  }
660
661  
662  private ValueSet unite(ElementDefinition ed, ProfileComparison outcome, String path, ValueSet lvs, ValueSet rvs) {
663    ValueSet vs = new ValueSet();
664    if (lvs.hasCompose()) {
665      for (ConceptSetComponent inc : lvs.getCompose().getInclude()) 
666        vs.getCompose().getInclude().add(inc);
667      if (lvs.getCompose().hasExclude()) {
668        outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value sets "+lvs.getUrl()+" has exclude statements, and no union involving it can be correctly determined", ValidationMessage.IssueSeverity.ERROR));
669        status(ed, ProfileUtilities.STATUS_ERROR);
670      }
671    }
672    if (rvs.hasCompose()) {
673      for (ConceptSetComponent inc : rvs.getCompose().getInclude())
674        if (!mergeIntoExisting(vs.getCompose().getInclude(), inc))
675          vs.getCompose().getInclude().add(inc);
676      if (rvs.getCompose().hasExclude()) {
677        outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value sets "+lvs.getUrl()+" has exclude statements, and no union involving it can be correctly determined", ValidationMessage.IssueSeverity.ERROR));
678        status(ed, ProfileUtilities.STATUS_ERROR);
679      }
680    }    
681    return vs;
682  }
683
684  private boolean mergeIntoExisting(List<ConceptSetComponent> include, ConceptSetComponent inc) {
685    for (ConceptSetComponent dst : include) {
686      if (Base.compareDeep(dst,  inc, false))
687        return true; // they're actually the same
688      if (dst.getSystem().equals(inc.getSystem())) {
689        if (inc.hasFilter() || dst.hasFilter()) {
690          return false; // just add the new one as a a parallel
691        } else if (inc.hasConcept() && dst.hasConcept()) {
692          for (ConceptReferenceComponent cc : inc.getConcept()) {
693            boolean found = false;
694            for (ConceptReferenceComponent dd : dst.getConcept()) {
695              if (dd.getCode().equals(cc.getCode()))
696                found = true;
697              if (found) {
698                if (cc.hasDisplay() && !dd.hasDisplay())
699                  dd.setDisplay(cc.getDisplay());
700                break;
701              }
702            }
703            if (!found)
704              dst.getConcept().add(cc.copy());
705          }
706        } else
707          dst.getConcept().clear(); // one of them includes the entire code system 
708      }
709    }
710    return false;
711  }
712
713  private ValueSet resolveVS(StructureDefinition ctxtLeft, Type vsRef) {
714    if (vsRef == null)
715      return null;
716    if (vsRef instanceof UriType)
717      return null;
718    else {
719      Reference ref = (Reference) vsRef;
720      if (!ref.hasReference())
721        return null;
722      return context.fetchResource(ValueSet.class, ref.getReference());
723    }
724  }
725
726  private ValueSet intersectByDefinition(ValueSet lvs, ValueSet rvs) {
727    // this is just a stub. The idea is that we try to avoid expanding big open value sets from SCT, RxNorm, LOINC.
728    // there's a bit of long hand logic coming here, but that's ok.
729    return null;
730  }
731
732  private ValueSet intersectByExpansion(ValueSet lvs, ValueSet rvs) {
733    // this is pretty straight forward - we intersect the lists, and build a compose out of the intersection
734    ValueSet vs = new ValueSet();
735    vs.setStatus(PublicationStatus.DRAFT);
736    
737    Map<String, ValueSetExpansionContainsComponent> left = new HashMap<String, ValueSetExpansionContainsComponent>();
738    scan(lvs.getExpansion().getContains(), left);
739    Map<String, ValueSetExpansionContainsComponent> right = new HashMap<String, ValueSetExpansionContainsComponent>();
740    scan(rvs.getExpansion().getContains(), right);
741    Map<String, ConceptSetComponent> inc = new HashMap<String, ConceptSetComponent>();
742    
743    for (String s : left.keySet()) {
744      if (right.containsKey(s)) {
745        ValueSetExpansionContainsComponent cc = left.get(s);
746        ConceptSetComponent c = inc.get(cc.getSystem());
747        if (c == null) {
748          c = vs.getCompose().addInclude().setSystem(cc.getSystem());
749          inc.put(cc.getSystem(), c);
750        }
751        c.addConcept().setCode(cc.getCode()).setDisplay(cc.getDisplay());
752      }
753    }
754    return vs;
755  }
756
757  private void scan(List<ValueSetExpansionContainsComponent> list, Map<String, ValueSetExpansionContainsComponent> map) {
758    for (ValueSetExpansionContainsComponent cc : list) {
759      if (cc.hasSystem() && cc.hasCode()) {
760        String s = cc.getSystem()+"::"+cc.getCode();
761        if (!map.containsKey(s))
762          map.put(s,  cc);
763      }
764      if (cc.hasContains())
765        scan(cc.getContains(), map);
766    }
767  }
768
769  private boolean closed(ValueSet vs) {
770    return !ToolingExtensions.findBooleanExtension(vs.getExpansion(), ToolingExtensions.EXT_UNCLOSED);
771  }
772
773  private boolean isPreferredOrExample(ElementDefinitionBindingComponent binding) {
774    return binding.getStrength() == BindingStrength.EXAMPLE || binding.getStrength() == BindingStrength.PREFERRED;
775  }
776
777  private Collection<? extends TypeRefComponent> intersectTypes(ElementDefinition ed, ProfileComparison outcome, String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError {
778    List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
779    for (TypeRefComponent l : left) {
780      if (l.hasAggregation())
781        throw new DefinitionException("Aggregation not supported: "+path);
782      boolean pfound = false;
783      boolean tfound = false;
784      TypeRefComponent c = l.copy();
785      for (TypeRefComponent r : right) {
786        if (r.hasAggregation())
787          throw new DefinitionException("Aggregation not supported: "+path);
788        if (!l.hasProfile() && !r.hasProfile()) {
789          pfound = true;    
790        } else if (!r.hasProfile()) {
791          pfound = true; 
792        } else if (!l.hasProfile()) {
793          pfound = true;
794          c.setProfile(r.getProfile());
795        } else {
796          StructureDefinition sdl = resolveProfile(ed, outcome, path, l.getProfile().get(0).getValue(), outcome.leftName());
797          StructureDefinition sdr = resolveProfile(ed, outcome, path, r.getProfile().get(0).getValue(), outcome.rightName());
798          if (sdl != null && sdr != null) {
799            if (sdl == sdr) {
800              pfound = true;
801            } else if (derivesFrom(sdl, sdr)) {
802              pfound = true;
803            } else if (derivesFrom(sdr, sdl)) {
804              c.setProfile(r.getProfile());
805              pfound = true;
806            } else if (sdl.getType().equals(sdr.getType())) {
807              ProfileComparison comp = compareProfiles(sdl, sdr);
808              if (comp.getSubset() != null) {
809                pfound = true;
810                c.addProfile("#"+comp.id);
811              }
812            }
813          }
814        }
815        if (!l.hasTargetProfile() && !r.hasTargetProfile()) {
816          tfound = true;    
817        } else if (!r.hasTargetProfile()) {
818          tfound = true; 
819        } else if (!l.hasTargetProfile()) {
820          tfound = true;
821          c.setTargetProfile(r.getTargetProfile());
822        } else {
823          StructureDefinition sdl = resolveProfile(ed, outcome, path, l.getProfile().get(0).getValue(), outcome.leftName());
824          StructureDefinition sdr = resolveProfile(ed, outcome, path, r.getProfile().get(0).getValue(), outcome.rightName());
825          if (sdl != null && sdr != null) {
826            if (sdl == sdr) {
827              tfound = true;
828            } else if (derivesFrom(sdl, sdr)) {
829              tfound = true;
830            } else if (derivesFrom(sdr, sdl)) {
831              c.setTargetProfile(r.getTargetProfile());
832              tfound = true;
833            } else if (sdl.getType().equals(sdr.getType())) {
834              ProfileComparison comp = compareProfiles(sdl, sdr);
835              if (comp.getSubset() != null) {
836                tfound = true;
837                c.addTargetProfile("#"+comp.id);
838              }
839            }
840          }
841        }
842      }
843      if (pfound && tfound)
844        result.add(c);
845    }
846    return result;
847  }
848
849  private StructureDefinition resolveProfile(ElementDefinition ed, ProfileComparison outcome, String path, String url, String name) {
850    StructureDefinition res = context.fetchResource(StructureDefinition.class, url);
851    if (res == null) {
852      outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.INFORMATIONAL, path, "Unable to resolve profile "+url+" in profile "+name, ValidationMessage.IssueSeverity.WARNING));
853      status(ed, ProfileUtilities.STATUS_HINT);
854    }
855    return res;
856  }
857
858  private Collection<? extends TypeRefComponent> unionTypes(String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError {
859    List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
860    for (TypeRefComponent l : left) 
861      checkAddTypeUnion(path, result, l);
862    for (TypeRefComponent r : right) 
863      checkAddTypeUnion(path, result, r);
864    return result;
865  }    
866    
867  private void checkAddTypeUnion(String path, List<TypeRefComponent> results, TypeRefComponent nw) throws DefinitionException, IOException, FHIRFormatError {
868    boolean pfound = false;
869    boolean tfound = false;
870    nw = nw.copy();
871    if (nw.hasAggregation())
872      throw new DefinitionException("Aggregation not supported: "+path);
873    for (TypeRefComponent ex : results) {
874      if (Utilities.equals(ex.getCode(), nw.getCode())) {
875        if (!ex.hasProfile() && !nw.hasProfile())
876          pfound = true;
877        else if (!ex.hasProfile()) {
878          pfound = true; 
879        } else if (!nw.hasProfile()) {
880          pfound = true;
881          ex.setProfile(null);
882        } else {
883          // both have profiles. Is one derived from the other? 
884          StructureDefinition sdex = context.fetchResource(StructureDefinition.class, ex.getProfile().get(0).getValue());
885          StructureDefinition sdnw = context.fetchResource(StructureDefinition.class, nw.getProfile().get(0).getValue());
886          if (sdex != null && sdnw != null) {
887            if (sdex == sdnw) {
888              pfound = true;
889            } else if (derivesFrom(sdex, sdnw)) {
890              ex.setProfile(nw.getProfile());
891              pfound = true;
892            } else if (derivesFrom(sdnw, sdex)) {
893              pfound = true;
894            } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
895              ProfileComparison comp = compareProfiles(sdex, sdnw);
896              if (comp.getSuperset() != null) {
897                pfound = true;
898                ex.addProfile("#"+comp.id);
899              }
900            }
901          }
902        }        
903        if (!ex.hasTargetProfile() && !nw.hasTargetProfile())
904          tfound = true;
905        else if (!ex.hasTargetProfile()) {
906          tfound = true; 
907        } else if (!nw.hasTargetProfile()) {
908          tfound = true;
909          ex.setTargetProfile(null);
910        } else {
911          // both have profiles. Is one derived from the other? 
912          StructureDefinition sdex = context.fetchResource(StructureDefinition.class, ex.getTargetProfile().get(0).getValue());
913          StructureDefinition sdnw = context.fetchResource(StructureDefinition.class, nw.getTargetProfile().get(0).getValue());
914          if (sdex != null && sdnw != null) {
915            if (sdex == sdnw) {
916              tfound = true;
917            } else if (derivesFrom(sdex, sdnw)) {
918              ex.setTargetProfile(nw.getTargetProfile());
919              tfound = true;
920            } else if (derivesFrom(sdnw, sdex)) {
921              tfound = true;
922            } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
923              ProfileComparison comp = compareProfiles(sdex, sdnw);
924              if (comp.getSuperset() != null) {
925                tfound = true;
926                ex.addTargetProfile("#"+comp.id);
927              }
928            }
929          }
930        }        
931      }
932    }
933    if (!tfound || !pfound)
934      results.add(nw);      
935  }
936
937  
938  private boolean derivesFrom(StructureDefinition left, StructureDefinition right) {
939    // left derives from right if it's base is the same as right
940    // todo: recursive...
941    return left.hasBaseDefinition() && left.getBaseDefinition().equals(right.getUrl());
942  }
943
944
945  private String mergeText(ElementDefinition ed, ProfileComparison outcome, String path, String name, String left, String right) {
946    if (left == null && right == null)
947      return null;
948    if (left == null)
949      return right;
950    if (right == null)
951      return left;
952    if (left.equalsIgnoreCase(right))
953      return left;
954    if (path != null) {
955      outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.INFORMATIONAL, path, "Elements differ in definition for "+name+":\r\n  \""+left+"\"\r\n  \""+right+"\"", 
956          "Elements differ in definition for "+name+":<br/>\""+Utilities.escapeXml(left)+"\"<br/>\""+Utilities.escapeXml(right)+"\"", ValidationMessage.IssueSeverity.INFORMATION));
957      status(ed, ProfileUtilities.STATUS_HINT);
958    }
959    return "left: "+left+"; right: "+right;
960  }
961
962  private List<Coding> mergeCodings(List<Coding> left, List<Coding> right) {
963    List<Coding> result = new ArrayList<Coding>();
964    result.addAll(left);
965    for (Coding c : right) {
966      boolean found = false;
967      for (Coding ct : left)
968        if (Utilities.equals(c.getSystem(), ct.getSystem()) && Utilities.equals(c.getCode(), ct.getCode()))
969          found = true;
970      if (!found)
971        result.add(c);
972    }
973    return result;
974  }
975
976  private List<StringType> mergeStrings(List<StringType> left, List<StringType> right) {
977    List<StringType> result = new ArrayList<StringType>();
978    result.addAll(left);
979    for (StringType c : right) {
980      boolean found = false;
981      for (StringType ct : left)
982        if (Utilities.equals(c.getValue(), ct.getValue()))
983          found = true;
984      if (!found)
985        result.add(c);
986    }
987    return result;
988  }
989
990  private List<ElementDefinitionMappingComponent> mergeMappings(List<ElementDefinitionMappingComponent> left, List<ElementDefinitionMappingComponent> right) {
991    List<ElementDefinitionMappingComponent> result = new ArrayList<ElementDefinitionMappingComponent>();
992    result.addAll(left);
993    for (ElementDefinitionMappingComponent c : right) {
994      boolean found = false;
995      for (ElementDefinitionMappingComponent ct : left)
996        if (Utilities.equals(c.getIdentity(), ct.getIdentity()) && Utilities.equals(c.getLanguage(), ct.getLanguage()) && Utilities.equals(c.getMap(), ct.getMap()))
997          found = true;
998      if (!found)
999        result.add(c);
1000    }
1001    return result;
1002  }
1003
1004  // we can't really know about constraints. We create warnings, and collate them 
1005  private List<ElementDefinitionConstraintComponent> unionConstraints(ElementDefinition ed, ProfileComparison outcome, String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
1006    List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
1007    for (ElementDefinitionConstraintComponent l : left) {
1008      boolean found = false;
1009      for (ElementDefinitionConstraintComponent r : right)
1010        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity()))
1011          found = true;
1012      if (!found) {
1013        outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "StructureDefinition "+outcome.leftName()+" has a constraint that is not found in "+outcome.rightName()+" and it is uncertain whether they are compatible ("+l.getXpath()+")", ValidationMessage.IssueSeverity.INFORMATION));
1014        status(ed, ProfileUtilities.STATUS_WARNING);
1015      }
1016      result.add(l);
1017    }
1018    for (ElementDefinitionConstraintComponent r : right) {
1019      boolean found = false;
1020      for (ElementDefinitionConstraintComponent l : left)
1021        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity()))
1022          found = true;
1023      if (!found) {
1024        outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "StructureDefinition "+outcome.rightName()+" has a constraint that is not found in "+outcome.leftName()+" and it is uncertain whether they are compatible ("+r.getXpath()+")", ValidationMessage.IssueSeverity.INFORMATION));
1025        status(ed, ProfileUtilities.STATUS_WARNING);
1026        result.add(r);
1027      }
1028    }
1029    return result;
1030  }
1031
1032
1033  private List<ElementDefinitionConstraintComponent> intersectConstraints(String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
1034  List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
1035  for (ElementDefinitionConstraintComponent l : left) {
1036    boolean found = false;
1037    for (ElementDefinitionConstraintComponent r : right)
1038      if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity()))
1039        found = true;
1040    if (found)
1041      result.add(l);
1042  }
1043  return result;
1044}
1045
1046  private String card(DefinitionNavigator defn) {
1047    return Integer.toString(defn.current().getMin())+".."+defn.current().getMax();
1048  }
1049  
1050  private String typeCode(DefinitionNavigator defn) {
1051    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1052    for (TypeRefComponent t : defn.current().getType())
1053      b.append(t.getCode()+(t.hasProfile() ? "("+t.getProfile()+")" : "")+(t.hasTargetProfile() ? "("+t.getTargetProfile()+")" : "")); // todo: other properties
1054    return b.toString();
1055  }
1056
1057  private int intersectMin(int left, int right) {
1058    if (left > right)
1059      return left;
1060    else
1061      return right;
1062  }
1063
1064  private int unionMin(int left, int right) {
1065    if (left > right)
1066      return right;
1067    else
1068      return left;
1069  }
1070
1071  private String intersectMax(String left, String right) {
1072    int l = "*".equals(left) ? Integer.MAX_VALUE : Integer.parseInt(left);
1073    int r = "*".equals(right) ? Integer.MAX_VALUE : Integer.parseInt(right);
1074    if (l < r)
1075      return left;
1076    else
1077      return right;
1078  }
1079
1080  private String unionMax(String left, String right) {
1081    int l = "*".equals(left) ? Integer.MAX_VALUE : Integer.parseInt(left);
1082    int r = "*".equals(right) ? Integer.MAX_VALUE : Integer.parseInt(right);
1083    if (l < r)
1084      return right;
1085    else
1086      return left;
1087  }
1088
1089  private IntegerType intersectMaxLength(int left, int right) {
1090    if (left == 0) 
1091      left = Integer.MAX_VALUE;
1092    if (right == 0) 
1093      right = Integer.MAX_VALUE;
1094    if (left < right)
1095      return left == Integer.MAX_VALUE ? null : new IntegerType(left);
1096    else
1097      return right == Integer.MAX_VALUE ? null : new IntegerType(right);
1098  }
1099
1100  private IntegerType unionMaxLength(int left, int right) {
1101    if (left == 0) 
1102      left = Integer.MAX_VALUE;
1103    if (right == 0) 
1104      right = Integer.MAX_VALUE;
1105    if (left < right)
1106      return right == Integer.MAX_VALUE ? null : new IntegerType(right);
1107    else
1108      return left == Integer.MAX_VALUE ? null : new IntegerType(left);
1109  }
1110
1111  
1112  public String addValueSet(ValueSet cvs) {
1113    String id = Integer.toString(valuesets.size()+1);
1114    cvs.setId(id);
1115    valuesets.add(cvs);
1116    return id;
1117  }
1118
1119  
1120  
1121  public String getId() {
1122    return id;
1123  }
1124
1125  public void setId(String id) {
1126    this.id = id;
1127  }
1128
1129  public String getTitle() {
1130    return title;
1131  }
1132
1133  public void setTitle(String title) {
1134    this.title = title;
1135  }
1136
1137  public String getLeftLink() {
1138    return leftLink;
1139  }
1140
1141  public void setLeftLink(String leftLink) {
1142    this.leftLink = leftLink;
1143  }
1144
1145  public String getLeftName() {
1146    return leftName;
1147  }
1148
1149  public void setLeftName(String leftName) {
1150    this.leftName = leftName;
1151  }
1152
1153  public String getRightLink() {
1154    return rightLink;
1155  }
1156
1157  public void setRightLink(String rightLink) {
1158    this.rightLink = rightLink;
1159  }
1160
1161  public String getRightName() {
1162    return rightName;
1163  }
1164
1165  public void setRightName(String rightName) {
1166    this.rightName = rightName;
1167  }
1168
1169
1170  
1171  
1172}