
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}