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