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