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.HashMap;
036import java.util.List;
037import java.util.Map;
038
039import org.hl7.fhir.exceptions.DefinitionException;
040import org.hl7.fhir.r5.context.IWorkerContext;
041import org.hl7.fhir.r5.model.Base;
042import org.hl7.fhir.r5.model.ElementDefinition;
043import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
044import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
045import org.hl7.fhir.r5.model.Resource;
046import org.hl7.fhir.r5.model.StructureDefinition;
047
048@MarkedToMoveToAdjunctPackage
049public class DefinitionNavigator {
050
051  private IWorkerContext context;
052  private StructureDefinition structure;
053  private int index;
054  private boolean indexMatches; // 
055  private List<DefinitionNavigator> children;
056  private List<DefinitionNavigator> typeChildren;
057  private List<DefinitionNavigator> slices;
058  private List<String> names = new ArrayList<String>();
059  private TypeRefComponent typeOfChildren;
060  private String path;
061  private boolean diff;
062  
063  public DefinitionNavigator(IWorkerContext context, StructureDefinition structure, boolean diff) throws DefinitionException {
064    if (!diff && !structure.hasSnapshot())
065      throw new DefinitionException("Snapshot required");
066    this.context = context;
067    this.structure = structure;
068    this.index = 0;
069    this.diff = diff;
070    if (diff) {
071      this.path = structure.getType(); // fragile?
072      indexMatches = this.path.equals(list().get(0).getPath());
073    } else {
074      indexMatches = true;
075      this.path = current().getPath(); // first element
076    }
077    names.add(nameTail());
078  }
079  
080  private DefinitionNavigator(IWorkerContext context, StructureDefinition structure, boolean diff, int index, String path, List<String> names, String type) {
081    this.path = path;
082    this.context = context;
083    this.structure = structure;
084    this.diff = diff;
085    this.index = index;
086    this.indexMatches = true;
087    if (type == null)
088      for (String name : names)
089        this.names.add(name+"."+nameTail());
090    else {
091      this.names.addAll(names);
092      this.names.add(type);
093    }
094  }
095  
096  /**
097   * When you walk a tree, and you walk into a typed structure, an element can simultaineously 
098   * be covered by multiple types at once. Take, for example, the string label for an identifer value.
099   * It has the following paths:
100   *   Patient.identifier.value.value
101   *   Identifier.value.value
102   *   String.value
103   *   value
104   * If you started in a bundle, the list might be even longer and deeper
105   *   
106   * Any of these names might be relevant. This function returns the names in an ordered list
107   * in the order above  
108   * @return
109   */
110  public List<String> getNames() {
111    return names;
112  }
113  
114  private List<ElementDefinition> list() {
115    if (diff) {
116      return structure.getDifferential().getElement();      
117    } else {
118      return structure.getSnapshot().getElement();
119    }    
120  }
121  public ElementDefinition current() {
122   return indexMatches ? list().get(index) : null;      
123  }
124  
125  public List<DefinitionNavigator> slices() throws DefinitionException {
126    if (children == null) {
127      loadChildren();
128    }
129    return slices;
130  }
131  
132  public List<DefinitionNavigator> children() throws DefinitionException {
133    if (children == null) {
134      loadChildren();
135    }
136    return children;
137  }
138
139  private void loadChildren() throws DefinitionException {
140    children = new ArrayList<DefinitionNavigator>();
141    String prefix = path+".";
142    Map<String, DefinitionNavigator> nameMap = new HashMap<String, DefinitionNavigator>();
143
144    DefinitionNavigator last = null;
145    for (int i = indexMatches ? index + 1 : index; i < list().size(); i++) {
146      String path = list().get(i).getPath();
147      if (path.startsWith(prefix)) {
148        if (!path.substring(prefix.length()).contains(".")) {
149          // immediate child
150          DefinitionNavigator dn = new DefinitionNavigator(context, structure, diff, i, this.path+"."+tail(path), names, null);
151          last = dn;
152
153          if (nameMap.containsKey(path)) {
154            DefinitionNavigator master = nameMap.get(path);
155            ElementDefinition cm = master.current();
156            if (diff) { 
157              // slice name - jumped straight into slicing
158              children.add(dn);
159            } else {
160              if (!cm.hasSlicing()) {
161                throw new DefinitionException("Found slices with no slicing details at "+dn.current().getPath());
162              }
163              if (master.slices == null) {
164                master.slices = new ArrayList<DefinitionNavigator>();
165              }
166              master.slices.add(dn);
167            }
168          } else {
169            nameMap.put(path, dn);
170            children.add(dn);
171          }
172        } else if (last == null || !path.startsWith(last.path)) {
173          // implied child
174          DefinitionNavigator dn = new DefinitionNavigator(context, structure, diff, i, this.path+"."+tail(path), names, null);
175          nameMap.put(path, dn);
176          children.add(dn);
177        }
178      } else if (path.length() < prefix.length())
179        break;
180    }
181  }
182
183  public String path() {
184    return path;
185  }
186  
187  private String tail(String p) {
188    if (p.contains("."))
189      return p.substring(p.lastIndexOf('.')+1);
190    else
191      return p;
192  }
193
194  public String nameTail() {
195    return tail(path);
196  }
197
198  /**
199   * if you have a typed element, the tree might end at that point.
200   * And you may or may not want to walk into the tree of that type
201   * It depends what you are doing. So this is a choice. You can 
202   * ask for the children, and then, if you get no children, you 
203   * can see if there are children defined for the type, and then 
204   * get them
205   * 
206   * you have to provide a type if there's more than one type 
207   * for current() since this library doesn't know how to choose
208   * @param res 
209   * @throws DefinitionException 
210   * @
211   */
212  public boolean hasTypeChildren(TypeRefComponent type, Resource res) throws DefinitionException {
213    if (typeChildren == null || typeOfChildren != type) {
214      loadTypedChildren(type, res);
215    }
216    return !typeChildren.isEmpty();
217  }
218
219  private void loadTypedChildren(TypeRefComponent type, Resource src) throws DefinitionException {
220    typeOfChildren = null;
221    StructureDefinition sd = context.fetchResource(StructureDefinition.class, /* GF#13465 : this somehow needs to be revisited type.hasProfile() ? type.getProfile() : */ type.getWorkingCode(), src);
222    if (sd != null) {
223      DefinitionNavigator dn = new DefinitionNavigator(context, sd, diff, 0, path, names, sd.getType());
224      typeChildren = dn.children();
225    } else
226      throw new DefinitionException("Unable to find definition for "+type.getWorkingCode()+(type.hasProfile() ? "("+type.getProfile()+")" : ""));
227    typeOfChildren = type;
228  }
229
230  /**
231   * 
232   * @param res 
233   * @return
234   * @throws DefinitionException 
235   * @
236   */
237  public List<DefinitionNavigator> childrenFromType(TypeRefComponent type, Resource res) throws DefinitionException {
238    if (typeChildren == null || typeOfChildren != type) {
239      loadTypedChildren(type, res);
240    }
241    return typeChildren;
242  }
243
244  public StructureDefinition getStructure() {
245    return structure;
246  }
247
248  @Override
249  public String toString() {
250    return getId();
251  }
252
253  public String getId() {
254    return current() == null ? path : current().hasSliceName() ? current().getPath()+":"+current().getSliceName() : current().getPath();
255  }
256
257  public Base parent() {
258    // TODO Auto-generated method stub
259    return null;
260  }
261  
262
263}