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