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