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