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