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