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