
001package org.hl7.fhir.r5.utils; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.HashMap; 037import java.util.List; 038import java.util.Map; 039 040import org.hl7.fhir.exceptions.DefinitionException; 041import org.hl7.fhir.r5.context.IWorkerContext; 042import org.hl7.fhir.r5.model.Base; 043import org.hl7.fhir.r5.model.ElementDefinition; 044import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 045import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; 046import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 047import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 048import org.hl7.fhir.r5.model.Resource; 049import org.hl7.fhir.r5.model.StructureDefinition; 050import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 051import org.hl7.fhir.utilities.Utilities; 052import org.hl7.fhir.utilities.i18n.I18nConstants; 053 054@MarkedToMoveToAdjunctPackage 055public class DefinitionNavigator { 056 057 private IWorkerContext context; 058 private StructureDefinition structure; 059 private int index; 060 private boolean indexMatches; // 061 private List<DefinitionNavigator> children; 062 private List<DefinitionNavigator> typeChildren; 063 private List<DefinitionNavigator> slices; 064 private List<String> names = new ArrayList<String>(); 065 private TypeRefComponent typeOfChildren; 066 private String path; 067 private boolean diff; 068 private boolean followTypes; 069 private boolean inlineChildren; 070 private ElementDefinitionSlicingComponent manualSlice; 071 private TypeRefComponent manualType; 072 073 public DefinitionNavigator(IWorkerContext context, StructureDefinition structure, boolean diff, boolean followTypes) throws DefinitionException { 074 if (!diff && !structure.hasSnapshot()) 075 throw new DefinitionException("Snapshot required"); 076 this.context = context; 077 this.structure = structure; 078 this.index = 0; 079 this.diff = diff; 080 this.followTypes = followTypes; 081 if (diff) { 082 this.path = structure.getType(); // fragile? 083 indexMatches = this.path.equals(list().get(0).getPath()); 084 } else { 085 indexMatches = true; 086 this.path = current().getPath(); // first element 087 } 088 names.add(nameTail()); 089 } 090 091 private DefinitionNavigator(IWorkerContext context, StructureDefinition structure, boolean diff, boolean followTypes, int index, String path, List<String> names, String type) { 092 this.path = path; 093 this.context = context; 094 this.structure = structure; 095 this.diff = diff; 096 this.followTypes = followTypes; 097 this.index = index; 098 this.indexMatches = true; 099 if (type == null) 100 for (String name : names) 101 this.names.add(name+"."+nameTail()); 102 else { 103 this.names.addAll(names); 104 this.names.add(type); 105 } 106 } 107 108 /** 109 * Special case - changing the value of followTypes, for special use case 110 * @param other 111 * @param followTypes 112 */ 113 public DefinitionNavigator(DefinitionNavigator other, boolean followTypes) { 114 this.context = other.context; 115 this.structure = other.structure; 116 this.index = other.index; 117 this.diff = other.diff; 118 this.followTypes = followTypes; 119 this.path = other.path; 120 this.indexMatches = other.indexMatches; 121 this.typeOfChildren = other.typeOfChildren; 122 this.inlineChildren = other.inlineChildren; 123 this.manualSlice = other.manualSlice; 124 this.manualType = other.manualType; 125 } 126 127 /** 128 * When you walk a tree, and you walk into a typed structure, an element can simultaineously 129 * be covered by multiple types at once. Take, for example, the string label for an identifer value. 130 * It has the following paths: 131 * Patient.identifier.value.value 132 * Identifier.value.value 133 * String.value 134 * value 135 * If you started in a bundle, the list might be even longer and deeper 136 * 137 * Any of these names might be relevant. This function returns the names in an ordered list 138 * in the order above 139 * @return 140 */ 141 public List<String> getNames() { 142 return names; 143 } 144 145 private List<ElementDefinition> list() { 146 if (diff) { 147 return structure.getDifferential().getElement(); 148 } else { 149 return structure.getSnapshot().getElement(); 150 } 151 } 152 public ElementDefinition current() { 153 return indexMatches ? list().get(index) : null; 154 } 155 156 public List<DefinitionNavigator> slices() throws DefinitionException { 157 if (children == null) { 158 loadChildren(); 159 } 160 // never return null: if no slices were found, give back an empty list 161 return slices != null ? slices : Collections.emptyList(); 162 } 163 164 public List<DefinitionNavigator> children() throws DefinitionException { 165 if (children == null) { 166 loadChildren(); 167 } 168 return children; 169 } 170 171 private void loadChildren() throws DefinitionException { 172 children = new ArrayList<DefinitionNavigator>(); 173 String prefix = path+"."; 174 Map<String, DefinitionNavigator> nameMap = new HashMap<String, DefinitionNavigator>(); 175 176 DefinitionNavigator last = null; 177 String polymorphicRoot = null; 178 DefinitionNavigator polymorphicDN = null; 179 for (int i = indexMatches ? index + 1 : index; i < list().size(); i++) { 180 String path = list().get(i).getPath(); 181 if (path.startsWith(prefix)) { 182 if (!path.substring(prefix.length()).contains(".")) { 183 // immediate child 184 DefinitionNavigator dn = new DefinitionNavigator(context, structure, diff, followTypes, i, this.path+"."+tail(path), names, null); 185 last = dn; 186 187 if (nameMap.containsKey(path)) { 188 DefinitionNavigator master = nameMap.get(path); 189 ElementDefinition cm = master.current(); 190 // Skip missing slicing error for extensions: they are implicitly sliced by url 191 if (!cm.hasSlicing()) { 192 String cmPath = cm.getPath(); 193 boolean isExtension = cmPath.endsWith(".extension") 194 || cmPath.endsWith(".modifierExtension"); 195 if (!isExtension) { 196 throw new DefinitionException("Found slices with no slicing details at " + dn.current().getPath()); 197 } 198 } 199 if (master.slices == null) { 200 master.slices = new ArrayList<DefinitionNavigator>(); 201 } 202 master.slices.add(dn); 203 204 } else if (polymorphicRoot != null && path.startsWith(polymorphicRoot) && !path.substring(polymorphicRoot.length()).contains(".")) { 205 if (polymorphicDN.slices == null) { 206 polymorphicDN.slices = new ArrayList<DefinitionNavigator>(); 207 polymorphicDN.manualSlice = new ElementDefinitionSlicingComponent(); 208 polymorphicDN.manualSlice.setUserData(UserDataNames.DN_TRANSIENT, "true"); 209 polymorphicDN.manualSlice.setRules(SlicingRules.CLOSED); 210 polymorphicDN.manualSlice.setOrdered(false); 211 polymorphicDN.manualSlice.addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 212 } 213 polymorphicDN.slices.add(dn); 214 if (!dn.current().hasType()) { 215 String t = path.substring(polymorphicRoot.length()); 216 StructureDefinition sd = context.fetchTypeDefinition(t); 217 if (sd == null) { 218 sd = context.fetchTypeDefinition(Utilities.uncapitalize(t)); 219 } 220 if (sd != null) { 221 dn.manualType = new TypeRefComponent(sd.getType()); 222 } 223 } 224 } else if (dn.current().hasSliceName()) { 225 // this is an error unless we're dealing with extensions, which are auto-sliced (for legacy reasons) 226 if (diff && "extension".equals(dn.current().getName())) { 227 StructureDefinition vsd = new StructureDefinition(); // fake wrapper for placeholder element 228 vsd.getDifferential().getElement().add(makeExtensionDefinitionElement(path)); 229 DefinitionNavigator master = new DefinitionNavigator(context, vsd, diff, followTypes, 0, this.path+"."+tail(path), names, null); 230 nameMap.put(path, master); 231 children.add(master); 232 master.slices = new ArrayList<DefinitionNavigator>(); 233 master.slices.add(dn); 234 } else { 235 throw new DefinitionException(context.formatMessage(I18nConstants.DN_SLICE_NO_DEFINITION, path)); 236 } 237 } else { 238 nameMap.put(path, dn); 239 children.add(dn); 240 if (diff && path.endsWith("[x]")) { 241 // we're going to infer slicing if we need to 242 polymorphicRoot = path.substring(0, path.length() -3); 243 polymorphicDN = dn; 244 } else { 245 polymorphicRoot = null; 246 polymorphicDN = null; 247 248 } 249 } 250 } else if (last == null || !path.startsWith(last.path)) { 251 // implied child 252 DefinitionNavigator dn = new DefinitionNavigator(context, structure, diff, followTypes, i, this.path+"."+tail(path), names, null); 253 nameMap.put(path, dn); 254 children.add(dn); 255 } 256 } else if (path.length() < prefix.length()) { 257 break; 258 } 259 } 260 inlineChildren = !children.isEmpty(); 261 if (children.isEmpty() && followTypes) { 262 ElementDefinition ed = current(); 263 if (ed.getType().size() != 1) { 264 // well, we can't walk into it 265 return; // what to do? 266 } 267 TypeRefComponent tr = ed.getTypeFirstRep(); 268 StructureDefinition sdt = null; 269 if (tr.getProfile().size() > 1) { 270 return; 271 } else if (tr.getProfile().size() == 1) { 272 sdt = context.fetchResource(StructureDefinition.class, tr.getProfile().get(0).asStringValue()); 273 } else { 274 sdt = context.fetchTypeDefinition(ed.getTypeFirstRep().getWorkingCode()); 275 } 276 if (sdt == null) { 277 return; 278 } 279 List<ElementDefinition> list = diff ? sdt.getDifferential().getElement() : sdt.getSnapshot().getElement(); 280 for (int i = 0; i < list.size(); i++) { 281 ElementDefinition edt = list.get(i); 282 if (Utilities.charCount(edt.getPath(), '.') == 1) { 283 DefinitionNavigator dn = new DefinitionNavigator(context, sdt, diff, followTypes, i, ed.getPath(), names, null); 284 children.add(dn); 285 } 286 } 287 } 288 } 289 290 private ElementDefinition makeExtensionDefinitionElement(String path) { 291 ElementDefinition ed = new ElementDefinition(path); 292 ed.setUserData(UserDataNames.DN_TRANSIENT, "true"); 293 ed.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 294 return ed; 295 } 296 297 public String path() { 298 return path; 299 } 300 301 private String tail(String p) { 302 if (p.contains(".")) 303 return p.substring(p.lastIndexOf('.')+1); 304 else 305 return p; 306 } 307 308 public String nameTail() { 309 return tail(path); 310 } 311 312 /** 313 * if you have a typed element, the tree might end at that point. 314 * And you may or may not want to walk into the tree of that type 315 * It depends what you are doing. So this is a choice. You can 316 * ask for the children, and then, if you get no children, you 317 * can see if there are children defined for the type, and then 318 * get them 319 * 320 * you have to provide a type if there's more than one type 321 * for current() since this library doesn't know how to choose 322 * @param res 323 * @throws DefinitionException 324 * @ 325 */ 326 public boolean hasTypeChildren(TypeRefComponent type, Resource res) throws DefinitionException { 327 if (typeChildren == null || typeOfChildren != type) { 328 loadTypedChildren(type, res); 329 } 330 return !typeChildren.isEmpty(); 331 } 332 333 private void loadTypedChildren(TypeRefComponent type, Resource src) throws DefinitionException { 334 typeOfChildren = null; 335 StructureDefinition sd = context.fetchResource(StructureDefinition.class, /* GF#13465 : this somehow needs to be revisited type.hasProfile() ? type.getProfile() : */ type.getWorkingCode(), src); 336 if (sd != null) { 337 DefinitionNavigator dn = new DefinitionNavigator(context, sd, diff, followTypes, 0, path, names, sd.getType()); 338 typeChildren = dn.children(); 339 } else 340 throw new DefinitionException("Unable to find definition for "+type.getWorkingCode()+(type.hasProfile() ? "("+type.getProfile()+")" : "")); 341 typeOfChildren = type; 342 } 343 344 /** 345 * 346 * @param res 347 * @return 348 * @throws DefinitionException 349 * @ 350 */ 351 public List<DefinitionNavigator> childrenFromType(TypeRefComponent type, Resource res) throws DefinitionException { 352 if (typeChildren == null || typeOfChildren != type) { 353 loadTypedChildren(type, res); 354 } 355 return typeChildren; 356 } 357 358 public StructureDefinition getStructure() { 359 return structure; 360 } 361 362 @Override 363 public String toString() { 364 return getId(); 365 } 366 367 public String getId() { 368 return current() == null ? path : current().hasSliceName() ? current().getPath()+":"+current().getSliceName() : current().getPath(); 369 } 370 371 public Base parent() { 372 // TODO Auto-generated method stub 373 return null; 374 } 375 376 public boolean sliced() { 377 if (manualSlice != null) { 378 return true; 379 } else if (current() == null) { 380 return false; 381 } else { 382 return current().hasSlicing(); 383 } 384 } 385 386 public DefinitionNavigator childByPath(String path) { 387 for (DefinitionNavigator child : children()) { 388 if (child.path().equals(path)) { 389 return child; 390 } 391 } 392 return null; 393 } 394 395 public boolean hasChildren() { 396 return !children().isEmpty(); 397 } 398 399 public boolean hasSlices() { 400 return sliced() && slices != null && !slices.isEmpty(); 401 } 402 403 public boolean hasInlineChildren() { 404 if (children == null) { 405 loadChildren(); 406 } 407 return inlineChildren; 408 } 409 410 public DefinitionNavigator childByName(String name) { 411 for (DefinitionNavigator child : children()) { 412 if (child.current().getName().equals(name)) { 413 return child; 414 } 415 if (child.current().getName().startsWith(name+"[x]")) { 416 return child; 417 } 418 } 419 return null; 420 } 421 422 public boolean isManualSliced() { 423 return manualSlice != null; 424 } 425 426 public ElementDefinitionSlicingComponent getSlicing() { 427 return manualSlice != null ? manualSlice : current().getSlicing(); 428 } 429 430 public void setManualSliced(ElementDefinitionSlicingComponent manualSliced) { 431 this.manualSlice = manualSliced; 432 } 433 434 public TypeRefComponent getManualType() { 435 return manualType; 436 } 437 438 439}