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