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.utilities.MarkedToMoveToAdjunctPackage;
042import org.hl7.fhir.r4.model.StructureDefinition;
043
044@MarkedToMoveToAdjunctPackage
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,
068      List<String> names, String type) {
069    this.path = path;
070    this.context = context;
071    this.structure = structure;
072    this.index = index;
073    if (type == null)
074      for (String name : names)
075        this.names.add(name + "." + nameTail());
076    else {
077      this.names.addAll(names);
078      this.names.add(type);
079    }
080  }
081
082  /**
083   * When you walk a tree, and you walk into a typed structure, an element can
084   * simultaineously be covered by multiple types at once. Take, for example, the
085   * string label for an identifer value. It has the following paths:
086   * Patient.identifier.value.value Identifier.value.value String.value value If
087   * you started in a bundle, the list might be even longer and deeper
088   * 
089   * Any of these names might be relevant. This function returns the names in an
090   * ordered list in the order above
091   * 
092   * @return
093   */
094  public List<String> getNames() {
095    return names;
096  }
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,
125            null);
126
127        if (nameMap.containsKey(path)) {
128          DefinitionNavigator master = nameMap.get(path);
129          if (!master.current().hasSlicing())
130            throw new DefinitionException("Found slices with no slicing details at " + dn.current().getPath());
131          if (master.slices == null)
132            master.slices = new ArrayList<DefinitionNavigator>();
133          master.slices.add(dn);
134        } else {
135          nameMap.put(path, dn);
136          children.add(dn);
137        }
138      } else if (path.length() < prefix.length())
139        break;
140    }
141  }
142
143  public String path() {
144    return path;
145  }
146
147  private String tail(String p) {
148    if (p.contains("."))
149      return p.substring(p.lastIndexOf('.') + 1);
150    else
151      return p;
152  }
153
154  public String nameTail() {
155    return tail(path);
156  }
157
158  /**
159   * if you have a typed element, the tree might end at that point. And you may or
160   * may not want to walk into the tree of that type It depends what you are
161   * doing. So this is a choice. You can ask for the children, and then, if you
162   * get no children, you can see if there are children defined for the type, and
163   * then get them
164   * 
165   * you have to provide a type if there's more than one type for current() since
166   * this library doesn't know how to choose
167   * 
168   * @throws DefinitionException @
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,
180        /*
181         * GF#13465 : this somehow needs to be revisited type.hasProfile() ?
182         * type.getProfile() :
183         */ type.getWorkingCode());
184    if (sd != null) {
185      DefinitionNavigator dn = new DefinitionNavigator(context, sd, 0, path, names, sd.getType());
186      typeChildren = dn.children();
187    } else
188      throw new DefinitionException("Unable to find definition for " + type.getWorkingCode()
189          + (type.hasProfile() ? "(" + type.getProfile() + ")" : ""));
190    typeOfChildren = type;
191  }
192
193  /**
194   * 
195   * @return
196   * @throws DefinitionException @
197   */
198  public List<DefinitionNavigator> childrenFromType(TypeRefComponent type) throws DefinitionException {
199    if (typeChildren == null || typeOfChildren != type) {
200      loadTypedChildren(type);
201    }
202    return typeChildren;
203  }
204
205}