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