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