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