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