001package org.hl7.fhir.r5.conformance.profile;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.HashSet;
006import java.util.List;
007import java.util.Map;
008import java.util.Set;
009
010import org.hl7.fhir.exceptions.DefinitionException;
011import org.hl7.fhir.r5.model.Element;
012import org.hl7.fhir.r5.model.ElementDefinition;
013import org.hl7.fhir.r5.model.StructureDefinition;
014import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
015import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent;
016import org.hl7.fhir.r5.utils.ToolingExtensions;
017import org.hl7.fhir.r5.utils.UserDataNames;
018import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
019import org.hl7.fhir.utilities.Utilities;
020import org.hl7.fhir.utilities.VersionUtilities;
021import org.hl7.fhir.utilities.i18n.I18nConstants;
022
023@MarkedToMoveToAdjunctPackage
024public class MappingAssistant {
025
026
027  public enum MappingMergeModeOption {
028    DUPLICATE, // if there's more than one mapping for the same URI, just keep them all
029    IGNORE, // if there's more than one, keep the first 
030    OVERWRITE, // if there's opre than, keep the last 
031    APPEND, // if there's more than one, append them with ';' 
032  }
033  
034  private MappingMergeModeOption mappingMergeMode = MappingMergeModeOption.APPEND;
035  private StructureDefinition base;
036  private StructureDefinition derived;
037  
038  private List<StructureDefinitionMappingComponent> masterList= new ArrayList<StructureDefinition.StructureDefinitionMappingComponent>();
039  private Map<String, String> renames = new HashMap<>();
040  private String version;
041
042  public MappingAssistant(MappingMergeModeOption mappingMergeMode, StructureDefinition base, StructureDefinition derived, String version) {
043    this.mappingMergeMode = mappingMergeMode;
044    this.base = base;
045    this.derived = derived;
046    this.version = version;
047    
048    // figure out where we're going to be: 
049    // mappings declared in derived get priority; we do not change them either 
050    for (StructureDefinitionMappingComponent m : derived.getMapping()) {
051      masterList.add(m);
052      if (!isSuppressed(m)) {
053        m.setUserData(UserDataNames.mappings_inherited, true);
054      }
055    }
056    
057    // now, look at the base profile. If mappings in there match one in the derived, then we use that, otherwise, we add it to the list 
058    for (StructureDefinitionMappingComponent m : base.getMapping()) {
059      StructureDefinitionMappingComponent md = findMatchInDerived(m);
060      if (md == null) {
061        if (nameExists(m.getIdentity())) {
062          int i = 1;
063          String n = m.getIdentity() + i;
064          while (nameExists(n)) {
065            i++;
066            n = m.getIdentity() + i;
067          }
068          renames.put(m.getIdentity(), n);
069          masterList.add(m.copy().setName(n));
070        } else {
071          masterList.add(m.copy());
072        }
073      } else {
074        if (!md.hasName() && m.hasName()) {
075          md.setName(m.getName());
076        }
077        if (!md.hasUri() && m.hasUri()) {
078          md.setUri(m.getUri());
079        }
080        if (!md.hasComment() && m.hasComment()) {
081          md.setComment(m.getComment());
082        }
083        if (!m.getIdentity().equals(md.getIdentity())) {
084          renames.put(m.getIdentity(), md.getIdentity());
085        }
086      }
087    }
088  }
089
090  private boolean nameExists(String n) {
091    for (StructureDefinitionMappingComponent md : masterList) {
092      if (n.equals(md.getIdentity())) {
093        return true;
094      }      
095    }
096    return false;
097  }
098
099  private StructureDefinitionMappingComponent findMatchInDerived(StructureDefinitionMappingComponent m) {
100    for (StructureDefinitionMappingComponent md : derived.getMapping()) {
101      // if the URIs match, they match, irregardless of anything else
102      if (md.hasUri() && m.hasUri() && md.getUri().equals(m.getUri())) {
103        return md;
104      }
105      // if the codes match
106      if (md.hasIdentity() && m.hasIdentity() && md.getIdentity().equals(m.getIdentity())) {
107        // the names have to match if present
108        if (!md.hasName() || !m.hasName() || md.getName().equals(m.getName())) {
109          return md;
110        }
111      }
112      
113    }
114    return null;
115  }
116
117  public void update() {
118
119    Set<StructureDefinitionMappingComponent> usedList= new HashSet<StructureDefinition.StructureDefinitionMappingComponent>();
120    for (ElementDefinition ed : derived.getSnapshot().getElement()) {
121      for (ElementDefinitionMappingComponent m : ed.getMapping()) {
122        StructureDefinitionMappingComponent def = findDefinition(m.getIdentity());
123        if (def != null) {
124          usedList.add(def);
125        } else {
126          // not sure what to do?
127        }
128      }
129    }
130    
131    derived.getMapping().clear();
132    for (StructureDefinitionMappingComponent t : masterList) {
133      if (usedList.contains(t) || t.hasUserData(UserDataNames.mappings_inherited)) {
134        derived.getMapping().add(t);
135      }
136    }
137  }
138
139  public void merge(ElementDefinition base, ElementDefinition derived) {
140    List<ElementDefinitionMappingComponent> list = new ArrayList<>();
141    addMappings(list, base.getMapping(), renames);
142    if (derived.hasMapping()) {
143      addMappings(list, derived.getMapping(), null);
144    }
145    derived.setMapping(list);
146    
147    // trim anything
148    for (ElementDefinitionMappingComponent m : base.getMapping()) {
149      if (m.hasMap()) {
150        m.setMap(m.getMap().trim());
151      }
152    }
153
154  }
155
156  private void addMappings(List<ElementDefinitionMappingComponent> destination, List<ElementDefinitionMappingComponent> source, Map<String, String> renames2) {
157    for (ElementDefinitionMappingComponent s : source) {
158      if (!isSuppressed(s)) {
159        String name = s.getIdentity();
160        if (!isSuppressed(name)) {
161          if (renames2 != null && renames2.containsKey(name)) {
162            name = renames2.get(name);
163          }
164
165          boolean found = false;
166          for (ElementDefinitionMappingComponent d : destination) {
167            if (compareMaps(name, s, d)) {
168              found = true;
169              d.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
170              break;
171            }
172          }
173          if (!found) {
174            destination.add(s.setIdentity(name));
175          }
176        }
177      }
178    }
179  }
180
181  private boolean isSuppressed(String name) {
182    StructureDefinitionMappingComponent m = findDefinition(name);
183    return m != null && isSuppressed(m);
184  }
185
186  private boolean isSuppressed(Element s) {
187    return ToolingExtensions.readBoolExtension(s, ToolingExtensions.EXT_SUPPRESSED);
188  }
189
190  private StructureDefinitionMappingComponent findDefinition(String name) {
191    for (StructureDefinitionMappingComponent t : masterList) {
192      if (t.getIdentity().equals(name)) {
193        return t;
194      }
195    }
196    return null;
197  }
198
199  private boolean compareMaps(String name, ElementDefinitionMappingComponent s, ElementDefinitionMappingComponent d) {
200    
201    if (d.getIdentity().equals(name) && d.getMap().equals(s.getMap())) {
202      return true;
203    }
204    if (VersionUtilities.isR5Plus(version)) {
205      if (d.getIdentity().equals(name)) {
206        switch (mappingMergeMode) {
207        case APPEND:
208          if (!Utilities.splitStrings(d.getMap(), "\\,").contains(s.getMap())) {
209            d.setMap(d.getMap()+","+s.getMap());
210          }
211          return true;
212        case DUPLICATE:
213          return false;
214        case IGNORE:
215          d.setMap(s.getMap());
216          return true;
217        case OVERWRITE:
218          return true;
219        default:
220          return false;
221        }
222      } else {
223        return false;
224      }
225    } else {
226      return false;
227    }
228  }
229  
230}