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