
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 globalPath; 067 private String localPath; 068 private boolean diff; 069 private boolean followTypes; 070 private boolean inlineChildren; 071 private ElementDefinitionSlicingComponent manualSlice; 072 private TypeRefComponent manualType; 073 074 public DefinitionNavigator(IWorkerContext context, StructureDefinition structure, boolean diff, boolean followTypes) throws DefinitionException { 075 if (!diff && !structure.hasSnapshot()) 076 throw new DefinitionException("Snapshot required"); 077 this.context = context; 078 this.structure = structure; 079 this.index = 0; 080 this.diff = diff; 081 this.followTypes = followTypes; 082 if (diff) { 083 this.globalPath = structure.getType(); // fragile? 084 indexMatches = this.globalPath.equals(list().get(0).getPath()); 085 } else { 086 indexMatches = true; 087 this.globalPath = current().getPath(); // first element 088 } 089 this.localPath = this.globalPath; 090 names.add(nameTail()); 091 } 092 093 private DefinitionNavigator(IWorkerContext context, StructureDefinition structure, boolean diff, boolean followTypes, int index, String globalPath, String localPath, List<String> names, String type) { 094 this.globalPath = globalPath; 095 this.localPath = localPath; 096 this.context = context; 097 this.structure = structure; 098 this.diff = diff; 099 this.followTypes = followTypes; 100 this.index = index; 101 this.indexMatches = true; 102 if (type == null) 103 for (String name : names) 104 this.names.add(name+"."+nameTail()); 105 else { 106 this.names.addAll(names); 107 this.names.add(type); 108 } 109 } 110 111 /** 112 * Special case - changing the value of followTypes, for special use case 113 * @param other 114 * @param followTypes 115 */ 116 public DefinitionNavigator(DefinitionNavigator other, boolean followTypes) { 117 this.context = other.context; 118 this.structure = other.structure; 119 this.index = other.index; 120 this.diff = other.diff; 121 this.followTypes = followTypes; 122 this.globalPath = other.globalPath; 123 this.localPath = other.localPath; 124 this.indexMatches = other.indexMatches; 125 this.typeOfChildren = other.typeOfChildren; 126 this.inlineChildren = other.inlineChildren; 127 this.manualSlice = other.manualSlice; 128 this.manualType = other.manualType; 129 } 130 131 /** 132 * When you walk a tree, and you walk into a typed structure, an element can simultaineously 133 * be covered by multiple types at once. Take, for example, the string label for an identifer value. 134 * It has the following paths: 135 * Patient.identifier.value.value 136 * Identifier.value.value 137 * String.value 138 * value 139 * If you started in a bundle, the list might be even longer and deeper 140 * 141 * Any of these names might be relevant. This function returns the names in an ordered list 142 * in the order above 143 * @return 144 */ 145 public List<String> getNames() { 146 return names; 147 } 148 149 private List<ElementDefinition> list() { 150 if (diff) { 151 return structure.getDifferential().getElement(); 152 } else { 153 return structure.getSnapshot().getElement(); 154 } 155 } 156 public ElementDefinition current() { 157 return indexMatches ? list().get(index) : null; 158 } 159 160 public List<DefinitionNavigator> slices() throws DefinitionException { 161 if (children == null) { 162 loadChildren(); 163 } 164 // never return null: if no slices were found, give back an empty list 165 return slices != null ? slices : Collections.emptyList(); 166 } 167 168 public List<DefinitionNavigator> children() throws DefinitionException { 169 if (children == null) { 170 loadChildren(); 171 } 172 return children; 173 } 174 175 private void loadChildren() throws DefinitionException { 176 children = new ArrayList<DefinitionNavigator>(); 177 String prefix = localPath+"."; 178 Map<String, DefinitionNavigator> nameMap = new HashMap<String, DefinitionNavigator>(); 179 180 int workingIndex = index; 181 ElementDefinition curr = current(); 182 if (curr != null && curr.hasContentReference()) { 183 if (!(workingIndex < list().size()-1 && list().get(workingIndex+1).getPath().startsWith(prefix))) { 184 String ref = curr.getContentReference(); 185 if (ref.contains("#")) { 186 ref = ref.substring(ref.indexOf("#")+1); 187 } 188 prefix = ref; 189 workingIndex = getById(list(), ref); 190 } 191 } 192 193 DefinitionNavigator last = null; 194 String polymorphicRoot = null; 195 DefinitionNavigator polymorphicDN = null; 196 for (int i = indexMatches ? workingIndex + 1 : workingIndex; i < list().size(); i++) { 197 String path = list().get(i).getPath(); 198 if (path.startsWith(prefix)) { 199 if (!path.substring(prefix.length()).contains(".")) { 200 // immediate child 201 DefinitionNavigator dn = new DefinitionNavigator(context, structure, diff, followTypes, i, this.globalPath+"."+tail(path), path, names, null); 202 last = dn; 203 204 if (nameMap.containsKey(path)) { 205 DefinitionNavigator master = nameMap.get(path); 206 ElementDefinition cm = master.current(); 207 // Skip missing slicing error for extensions: they are implicitly sliced by url 208 if (!diff && !cm.hasSlicing()) { 209 String cmPath = cm.getPath(); 210 boolean isExtension = cmPath.endsWith(".extension") 211 || cmPath.endsWith(".modifierExtension"); 212 if (!isExtension) { 213 throw new DefinitionException("Found slices with no slicing details at " + dn.current().getPath()); 214 } 215 } 216 if (master.slices == null) { 217 master.slices = new ArrayList<DefinitionNavigator>(); 218 } 219 master.slices.add(dn); 220 221 } else if (polymorphicRoot != null && path.startsWith(polymorphicRoot) && !path.substring(polymorphicRoot.length()).contains(".")) { 222 if (polymorphicDN.slices == null) { 223 polymorphicDN.slices = new ArrayList<DefinitionNavigator>(); 224 polymorphicDN.manualSlice = new ElementDefinitionSlicingComponent(); 225 polymorphicDN.manualSlice.setUserData(UserDataNames.DN_TRANSIENT, "true"); 226 polymorphicDN.manualSlice.setRules(SlicingRules.CLOSED); 227 polymorphicDN.manualSlice.setOrdered(false); 228 polymorphicDN.manualSlice.addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 229 } 230 polymorphicDN.slices.add(dn); 231 if (!dn.current().hasType()) { 232 String t = path.substring(polymorphicRoot.length()); 233 StructureDefinition sd = context.fetchTypeDefinition(t); 234 if (sd == null) { 235 sd = context.fetchTypeDefinition(Utilities.uncapitalize(t)); 236 } 237 if (sd != null) { 238 dn.manualType = new TypeRefComponent(sd.getType()); 239 } 240 } 241 } else if (dn.current().hasSliceName()) { 242 // this is an error unless we're dealing with extensions, which are auto-sliced (for legacy reasons) 243 if (diff && "extension".equals(dn.current().getName())) { 244 StructureDefinition vsd = new StructureDefinition(); // fake wrapper for placeholder element 245 vsd.getDifferential().getElement().add(makeExtensionDefinitionElement(path)); 246 DefinitionNavigator master = new DefinitionNavigator(context, vsd, diff, followTypes, 0, this.globalPath+"."+tail(path), path, names, null); 247 nameMap.put(path, master); 248 children.add(master); 249 master.slices = new ArrayList<DefinitionNavigator>(); 250 master.slices.add(dn); 251 } else { 252 throw new DefinitionException(context.formatMessage(I18nConstants.DN_SLICE_NO_DEFINITION, path)); 253 } 254 } else { 255 nameMap.put(path, dn); 256 children.add(dn); 257 if (diff && path.endsWith("[x]")) { 258 // we're going to infer slicing if we need to 259 polymorphicRoot = path.substring(0, path.length() -3); 260 polymorphicDN = dn; 261 } else { 262 polymorphicRoot = null; 263 polymorphicDN = null; 264 265 } 266 } 267 } else if (last == null || !path.startsWith(last.localPath)) { 268 // implied child 269 DefinitionNavigator dn = new DefinitionNavigator(context, structure, diff, followTypes, i, this.globalPath+"."+tail(path), path, names, null); 270 nameMap.put(path, dn); 271 children.add(dn); 272 } 273 } else if (path.length() < prefix.length()) { 274 break; 275 } 276 } 277 if (children.isEmpty() && current().hasContentReference()) { 278 throw new Error("What?"); 279 } 280 inlineChildren = !children.isEmpty(); 281 if (children.isEmpty() && followTypes) { 282 ElementDefinition ed = current(); 283 if (ed.getType().size() != 1) { 284 // well, we can't walk into it 285 return; // what to do? 286 } 287 TypeRefComponent tr = ed.getTypeFirstRep(); 288 StructureDefinition sdt = null; 289 if (tr.getProfile().size() > 1) { 290 return; 291 } else if (tr.getProfile().size() == 1) { 292 sdt = context.fetchResource(StructureDefinition.class, tr.getProfile().get(0).asStringValue()); 293 } else { 294 sdt = context.fetchTypeDefinition(ed.getTypeFirstRep().getWorkingCode()); 295 } 296 if (sdt == null) { 297 return; 298 } 299 List<ElementDefinition> list = diff ? sdt.getDifferential().getElement() : sdt.getSnapshot().getElement(); 300 for (int i = 0; i < list.size(); i++) { 301 ElementDefinition edt = list.get(i); 302 if (Utilities.charCount(edt.getPath(), '.') == 1) { 303 DefinitionNavigator dn = new DefinitionNavigator(context, sdt, diff, followTypes, i, this.globalPath+"."+tail(edt.getPath()), edt.getPath(), names, null); 304 children.add(dn); 305 } 306 } 307 } 308 } 309 310 private int getById(List<ElementDefinition> list, String ref) { 311 for (ElementDefinition ed : list) { 312 if (ref.equals(ed.getPath())) { 313 return list.indexOf(ed); 314 } 315 } 316 return -1; 317 } 318 319 private ElementDefinition makeExtensionDefinitionElement(String path) { 320 ElementDefinition ed = new ElementDefinition(path); 321 ed.setUserData(UserDataNames.DN_TRANSIENT, "true"); 322 ed.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 323 return ed; 324 } 325 326 public String globalPath() { 327 return globalPath; 328 } 329 330 public String localPath() { 331 return localPath; 332 } 333 334 private String tail(String p) { 335 if (p.contains(".")) 336 return p.substring(p.lastIndexOf('.')+1); 337 else 338 return p; 339 } 340 341 public String nameTail() { 342 return tail(localPath); 343 } 344 345 /** 346 * if you have a typed element, the tree might end at that point. 347 * And you may or may not want to walk into the tree of that type 348 * It depends what you are doing. So this is a choice. You can 349 * ask for the children, and then, if you get no children, you 350 * can see if there are children defined for the type, and then 351 * get them 352 * 353 * you have to provide a type if there's more than one type 354 * for current() since this library doesn't know how to choose 355 * @param res 356 * @throws DefinitionException 357 * @ 358 */ 359 public boolean hasTypeChildren(TypeRefComponent type, Resource res) throws DefinitionException { 360 if (typeChildren == null || typeOfChildren != type) { 361 loadTypedChildren(type, res); 362 } 363 return !typeChildren.isEmpty(); 364 } 365 366 private void loadTypedChildren(TypeRefComponent type, Resource src) throws DefinitionException { 367 typeOfChildren = null; 368 StructureDefinition sd = context.fetchResource(StructureDefinition.class, /* GF#13465 : this somehow needs to be revisited type.hasProfile() ? type.getProfile() : */ type.getWorkingCode(), src); 369 if (sd != null) { 370 DefinitionNavigator dn = new DefinitionNavigator(context, sd, diff, followTypes, 0, globalPath, localPath, names, sd.getType()); 371 typeChildren = dn.children(); 372 } else 373 throw new DefinitionException("Unable to find definition for "+type.getWorkingCode()+(type.hasProfile() ? "("+type.getProfile()+")" : "")); 374 typeOfChildren = type; 375 } 376 377 /** 378 * 379 * @param res 380 * @return 381 * @throws DefinitionException 382 * @ 383 */ 384 public List<DefinitionNavigator> childrenFromType(TypeRefComponent type, Resource res) throws DefinitionException { 385 if (typeChildren == null || typeOfChildren != type) { 386 loadTypedChildren(type, res); 387 } 388 return typeChildren; 389 } 390 391 public StructureDefinition getStructure() { 392 return structure; 393 } 394 395 @Override 396 public String toString() { 397 return getId(); 398 } 399 400 public String getId() { 401 return current() == null ? localPath : current().hasSliceName() ? current().getPath()+":"+current().getSliceName() : current().getPath(); 402 } 403 404 public Base parent() { 405 // TODO Auto-generated method stub 406 return null; 407 } 408 409 public boolean sliced() { 410 if (manualSlice != null) { 411 return true; 412 } else if (current() == null) { 413 return false; 414 } else { 415 return current().hasSlicing(); 416 } 417 } 418 419 public DefinitionNavigator childByPath(String path) { 420 for (DefinitionNavigator child : children()) { 421 if (child.globalPath().equals(path)) { 422 return child; 423 } 424 } 425 return null; 426 } 427 428 public boolean hasChildren() { 429 return !children().isEmpty(); 430 } 431 432 public boolean hasSlices() { 433 return sliced() && slices != null && !slices.isEmpty(); 434 } 435 436 public boolean hasInlineChildren() { 437 if (children == null) { 438 loadChildren(); 439 } 440 return inlineChildren; 441 } 442 443 public DefinitionNavigator childByName(String name) { 444 for (DefinitionNavigator child : children()) { 445 if (child.current().getName().equals(name)) { 446 return child; 447 } 448 if (child.current().getName().startsWith(name+"[x]")) { 449 return child; 450 } 451 } 452 return null; 453 } 454 455 public boolean isManualSliced() { 456 return manualSlice != null; 457 } 458 459 public ElementDefinitionSlicingComponent getSlicing() { 460 return manualSlice != null ? manualSlice : current().getSlicing(); 461 } 462 463 public void setManualSliced(ElementDefinitionSlicingComponent manualSliced) { 464 this.manualSlice = manualSliced; 465 } 466 467 public TypeRefComponent getManualType() { 468 return manualType; 469 } 470 471 472}