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