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