001package org.hl7.fhir.validation.instance.utils;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.List;
006import java.util.Map;
007
008import org.hl7.fhir.r5.context.IWorkerContext;
009import org.hl7.fhir.r5.elementmodel.Element;
010import org.hl7.fhir.r5.model.ElementDefinition;
011import org.hl7.fhir.r5.model.StructureDefinition;
012import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
013import org.hl7.fhir.utilities.Utilities;
014
015public class NodeStack {
016
017  protected IWorkerContext context;
018  private ElementDefinition definition;
019  private Element element;
020  private ElementDefinition extension;
021  private String literalPath; // xpath format
022  private List<String> logicalPaths; // dotted format, various entry points
023  private NodeStack parent;
024  private ElementDefinition type;
025  private String workingLang;
026  private Map<String, Element> ids;
027  private boolean resetPoint = false;
028  private boolean contained = false; 
029
030  public NodeStack(IWorkerContext context) {
031    this.context = context;
032  }
033
034  public NodeStack(IWorkerContext context, String initialPath, Element element, String validationLanguage) {
035    this.context = context;
036    ids = new HashMap<>();
037    this.element = element;
038    literalPath = (initialPath == null ? "" : initialPath+".") + element.getPath();
039    workingLang = validationLanguage;
040    if (!element.getName().equals(element.fhirType())) {
041      logicalPaths = new ArrayList<>();
042      logicalPaths.add(element.fhirType());
043    }
044  }
045
046  public NodeStack(IWorkerContext context, Element element, String refPath, String validationLanguage) {
047    this.context = context;
048    ids = new HashMap<>();
049    this.element = element;
050    literalPath = refPath + "->" + element.getName();
051    workingLang = validationLanguage;
052  }
053
054  public String addToLiteralPath(String... path) {
055    StringBuilder b = new StringBuilder();
056    b.append(getLiteralPath());
057    for (String p : path) {
058      if (p.startsWith(":")) {
059        b.append("[");
060        b.append(p.substring(1));
061        b.append("]");
062      } else {
063        b.append(".");
064        b.append(p);
065      }
066    }
067    return b.toString();
068  }
069
070  private ElementDefinition getDefinition() {
071    return definition;
072  }
073
074  public Element getElement() {
075    return element;
076  }
077
078  public String getLiteralPath() {
079    return literalPath == null ? "" : literalPath;
080  }
081
082  public List<String> getLogicalPaths() {
083    return logicalPaths == null ? new ArrayList<String>() : logicalPaths;
084  }
085
086  private ElementDefinition getType() {
087    return type;
088  }
089
090  public NodeStack pushTarget(Element element, int count, ElementDefinition definition, ElementDefinition type) {
091    return pushInternal(element, count, definition, type, "->");
092  }
093
094  public NodeStack push(Element element, int count, ElementDefinition definition, ElementDefinition type) {
095    return pushInternal(element, count, definition, type, ".");
096  }
097
098  private NodeStack pushInternal(Element element, int count, ElementDefinition definition, ElementDefinition type, String sep) {
099    NodeStack res = new NodeStack(context);
100    res.ids = ids;
101    res.parent = this;
102    res.workingLang = this.workingLang;
103    res.element = element;
104    res.definition = definition;
105    res.contained = contained;
106    res.literalPath = getLiteralPath() + sep + element.getName();
107    if (count > -1)
108      res.literalPath = res.literalPath + "[" + Integer.toString(count) + "]";
109    else if (element.getSpecial() == null && element.getProperty().isList())
110      res.literalPath = res.literalPath + "[0]";
111    else if (element.getProperty().isChoice()) {
112      String n = res.literalPath.substring(res.literalPath.lastIndexOf(".") + 1);
113      String en = element.getProperty().getName();
114      en = en.substring(0, en.length() - 3);
115      String t = n.substring(en.length());
116      if (isPrimitiveType(Utilities.uncapitalize(t)))
117        t = Utilities.uncapitalize(t);
118      res.literalPath = res.literalPath.substring(0, res.literalPath.lastIndexOf(".")) + "." + en + ".ofType(" + t + ")";
119    }
120    res.logicalPaths = new ArrayList<String>();
121    if (type != null) {
122      // type will be null if we on a stitching point of a contained resource, or if....
123      res.type = type;
124      String tn = res.type.getPath();
125      String t = tail(definition.getPath());
126      if ("Resource".equals(tn)) {
127        tn = element.fhirType();
128      }
129      for (String lp : getLogicalPaths()) {
130        res.logicalPaths.add(lp + "." + t);
131        if (t.endsWith("[x]")) {
132          res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + ".ofType("+type.getPath()+")");
133          res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + type.getPath());
134        }
135      }
136      res.logicalPaths.add(tn);
137    } else if (definition != null) {
138      for (String lp : getLogicalPaths()) {
139        res.logicalPaths.add(lp + "." + element.getName());
140      }
141      res.logicalPaths.add(definition.typeSummary());
142    } else
143      res.logicalPaths.addAll(getLogicalPaths());
144    return res;
145  }
146
147  private void setType(ElementDefinition type) {
148    this.type = type;
149  }
150  
151  public NodeStack resetIds() {
152    ids = new HashMap<>();
153    resetPoint  = true;
154    return this;
155  }
156  public Map<String, Element> getIds() {
157    return ids;
158  }
159  private String tail(String path) {
160    return path.substring(path.lastIndexOf(".") + 1);
161  }
162
163  public boolean isPrimitiveType(String code) {
164    StructureDefinition sd = context.fetchTypeDefinition(code);
165    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
166  }
167
168  public String getWorkingLang() {
169    return workingLang;
170  }
171
172  public void setWorkingLang(String workingLang) {
173    this.workingLang = workingLang;
174  }
175
176  public NodeStack getParent() {
177    return parent;
178  }
179
180  public void qualifyPath(String qualifier) {
181    literalPath = literalPath + qualifier;
182    
183  }
184
185  public boolean isResetPoint() {
186    return resetPoint;
187  }
188
189  @Override
190  public String toString() {
191    return literalPath;
192  }
193
194  public int depth() {
195    if (parent == null) {
196      return 0;
197    } else {
198      return parent.depth()+1;
199    }
200  }
201
202  public boolean isContained() {
203    return contained;
204  }
205
206  public void setContained(boolean contained) {
207    this.contained = contained;
208  }
209
210
211}