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