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