001package org.hl7.fhir.r4.utils; 002 003/*- 004 * #%L 005 * org.hl7.fhir.r4 006 * %% 007 * Copyright (C) 2014 - 2019 Health Level 7 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023 024import java.util.ArrayList; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028 029import org.hl7.fhir.exceptions.DefinitionException; 030import org.hl7.fhir.r4.context.IWorkerContext; 031import org.hl7.fhir.r4.model.ElementDefinition; 032import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 033import org.hl7.fhir.r4.model.StructureDefinition; 034 035public class DefinitionNavigator { 036 037 private IWorkerContext context; 038 private StructureDefinition structure; 039 private int index; 040 private List<DefinitionNavigator> children; 041 private List<DefinitionNavigator> typeChildren; 042 private List<DefinitionNavigator> slices; 043 private List<String> names = new ArrayList<String>(); 044 private TypeRefComponent typeOfChildren; 045 private String path; 046 047 public DefinitionNavigator(IWorkerContext context, StructureDefinition structure) throws DefinitionException { 048 if (!structure.hasSnapshot()) 049 throw new DefinitionException("Snapshot required"); 050 this.context = context; 051 this.structure = structure; 052 this.index = 0; 053 this.path = current().getPath(); 054 names.add(nameTail()); 055 } 056 057 private DefinitionNavigator(IWorkerContext context, StructureDefinition structure, int index, String path, List<String> names, String type) { 058 this.path = path; 059 this.context = context; 060 this.structure = structure; 061 this.index = index; 062 if (type == null) 063 for (String name : names) 064 this.names.add(name+"."+nameTail()); 065 else { 066 this.names.addAll(names); 067 this.names.add(type); 068 } 069 } 070 071 /** 072 * When you walk a tree, and you walk into a typed structure, an element can simultaineously 073 * be covered by multiple types at once. Take, for example, the string label for an identifer value. 074 * It has the following paths: 075 * Patient.identifier.value.value 076 * Identifier.value.value 077 * String.value 078 * value 079 * If you started in a bundle, the list might be even longer and deeper 080 * 081 * Any of these names might be relevant. This function returns the names in an ordered list 082 * in the order above 083 * @return 084 */ 085 public List<String> getNames() { 086 return names; 087 } 088 public ElementDefinition current() { 089 return structure.getSnapshot().getElement().get(index); 090 } 091 092 public List<DefinitionNavigator> slices() throws DefinitionException { 093 if (children == null) { 094 loadChildren(); 095 } 096 return slices; 097 } 098 099 public List<DefinitionNavigator> children() throws DefinitionException { 100 if (children == null) { 101 loadChildren(); 102 } 103 return children; 104 } 105 106 private void loadChildren() throws DefinitionException { 107 children = new ArrayList<DefinitionNavigator>(); 108 String prefix = current().getPath()+"."; 109 Map<String, DefinitionNavigator> nameMap = new HashMap<String, DefinitionNavigator>(); 110 111 for (int i = index + 1; i < structure.getSnapshot().getElement().size(); i++) { 112 String path = structure.getSnapshot().getElement().get(i).getPath(); 113 if (path.startsWith(prefix) && !path.substring(prefix.length()).contains(".")) { 114 DefinitionNavigator dn = new DefinitionNavigator(context, structure, i, this.path+"."+tail(path), names, null); 115 116 if (nameMap.containsKey(path)) { 117 DefinitionNavigator master = nameMap.get(path); 118 if (!master.current().hasSlicing()) 119 throw new DefinitionException("Found slices with no slicing details at "+dn.current().getPath()); 120 if (master.slices == null) 121 master.slices = new ArrayList<DefinitionNavigator>(); 122 master.slices.add(dn); 123 } else { 124 nameMap.put(path, dn); 125 children.add(dn); 126 } 127 } else if (path.length() < prefix.length()) 128 break; 129 } 130 } 131 132 public String path() { 133 return path; 134 } 135 136 private String tail(String p) { 137 if (p.contains(".")) 138 return p.substring(p.lastIndexOf('.')+1); 139 else 140 return p; 141 } 142 143 public String nameTail() { 144 return tail(path); 145 } 146 147 /** 148 * if you have a typed element, the tree might end at that point. 149 * And you may or may not want to walk into the tree of that type 150 * It depends what you are doing. So this is a choice. You can 151 * ask for the children, and then, if you get no children, you 152 * can see if there are children defined for the type, and then 153 * get them 154 * 155 * you have to provide a type if there's more than one type 156 * for current() since this library doesn't know how to choose 157 * @throws DefinitionException 158 * @ 159 */ 160 public boolean hasTypeChildren(TypeRefComponent type) throws DefinitionException { 161 if (typeChildren == null || typeOfChildren != type) { 162 loadTypedChildren(type); 163 } 164 return !typeChildren.isEmpty(); 165 } 166 167 private void loadTypedChildren(TypeRefComponent type) throws DefinitionException { 168 typeOfChildren = null; 169 StructureDefinition sd = context.fetchResource(StructureDefinition.class, /* GF#13465 : this somehow needs to be revisited type.hasProfile() ? type.getProfile() : */ type.getWorkingCode()); 170 if (sd != null) { 171 DefinitionNavigator dn = new DefinitionNavigator(context, sd, 0, path, names, sd.getType()); 172 typeChildren = dn.children(); 173 } else 174 throw new DefinitionException("Unable to find definition for "+type.getWorkingCode()+(type.hasProfile() ? "("+type.getProfile()+")" : "")); 175 typeOfChildren = type; 176 } 177 178 /** 179 * 180 * @return 181 * @throws DefinitionException 182 * @ 183 */ 184 public List<DefinitionNavigator> childrenFromType(TypeRefComponent type) throws DefinitionException { 185 if (typeChildren == null || typeOfChildren != type) { 186 loadTypedChildren(type); 187 } 188 return typeChildren; 189 } 190 191 192}