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