001package org.hl7.fhir.r4.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.exceptions.DefinitionException;
038import org.hl7.fhir.r4.context.IWorkerContext;
039import org.hl7.fhir.r4.model.ElementDefinition;
040import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
041import org.hl7.fhir.r4.model.StructureDefinition;
042
043public class DefinitionNavigator {
044
045  private IWorkerContext context;
046  private StructureDefinition structure;
047  private int index;
048  private List<DefinitionNavigator> children;
049  private List<DefinitionNavigator> typeChildren;
050  private List<DefinitionNavigator> slices;
051  private List<String> names = new ArrayList<String>();
052  private TypeRefComponent typeOfChildren;
053  private String path;
054
055  public DefinitionNavigator(IWorkerContext context, StructureDefinition structure) throws DefinitionException {
056    if (!structure.hasSnapshot())
057      throw new DefinitionException("Snapshot required");
058    this.context = context;
059    this.structure = structure;
060    this.index = 0;
061    this.path = current().getPath();
062    names.add(nameTail());
063  }
064
065  private DefinitionNavigator(IWorkerContext context, StructureDefinition structure, int index, String path,
066      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
082   * simultaineously be covered by multiple types at once. Take, for example, the
083   * string label for an identifer value. It has the following paths:
084   * Patient.identifier.value.value Identifier.value.value String.value value If
085   * you started in a bundle, the list might be even longer and deeper
086   * 
087   * Any of these names might be relevant. This function returns the names in an
088   * ordered list in the order above
089   * 
090   * @return
091   */
092  public List<String> getNames() {
093    return names;
094  }
095
096  public ElementDefinition current() {
097    return structure.getSnapshot().getElement().get(index);
098  }
099
100  public List<DefinitionNavigator> slices() throws DefinitionException {
101    if (children == null) {
102      loadChildren();
103    }
104    return slices;
105  }
106
107  public List<DefinitionNavigator> children() throws DefinitionException {
108    if (children == null) {
109      loadChildren();
110    }
111    return children;
112  }
113
114  private void loadChildren() throws DefinitionException {
115    children = new ArrayList<DefinitionNavigator>();
116    String prefix = current().getPath() + ".";
117    Map<String, DefinitionNavigator> nameMap = new HashMap<String, DefinitionNavigator>();
118
119    for (int i = index + 1; i < structure.getSnapshot().getElement().size(); i++) {
120      String path = structure.getSnapshot().getElement().get(i).getPath();
121      if (path.startsWith(prefix) && !path.substring(prefix.length()).contains(".")) {
122        DefinitionNavigator dn = new DefinitionNavigator(context, structure, i, this.path + "." + tail(path), names,
123            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. And you may or
158   * may not want to walk into the tree of that type It depends what you are
159   * doing. So this is a choice. You can ask for the children, and then, if you
160   * get no children, you can see if there are children defined for the type, and
161   * then get them
162   * 
163   * you have to provide a type if there's more than one type for current() since
164   * this library doesn't know how to choose
165   * 
166   * @throws DefinitionException @
167   */
168  public boolean hasTypeChildren(TypeRefComponent type) throws DefinitionException {
169    if (typeChildren == null || typeOfChildren != type) {
170      loadTypedChildren(type);
171    }
172    return !typeChildren.isEmpty();
173  }
174
175  private void loadTypedChildren(TypeRefComponent type) throws DefinitionException {
176    typeOfChildren = null;
177    StructureDefinition sd = context.fetchResource(StructureDefinition.class,
178        /*
179         * GF#13465 : this somehow needs to be revisited type.hasProfile() ?
180         * type.getProfile() :
181         */ type.getWorkingCode());
182    if (sd != null) {
183      DefinitionNavigator dn = new DefinitionNavigator(context, sd, 0, path, names, sd.getType());
184      typeChildren = dn.children();
185    } else
186      throw new DefinitionException("Unable to find definition for " + type.getWorkingCode()
187          + (type.hasProfile() ? "(" + type.getProfile() + ")" : ""));
188    typeOfChildren = type;
189  }
190
191  /**
192   * 
193   * @return
194   * @throws DefinitionException @
195   */
196  public List<DefinitionNavigator> childrenFromType(TypeRefComponent type) throws DefinitionException {
197    if (typeChildren == null || typeOfChildren != type) {
198      loadTypedChildren(type);
199    }
200    return typeChildren;
201  }
202
203}