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