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 bull 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) + type.getPath()); 133 } 134 res.logicalPaths.add(tn); 135 } else if (definition != null) { 136 for (String lp : getLogicalPaths()) { 137 res.logicalPaths.add(lp + "." + element.getName()); 138 } 139 res.logicalPaths.add(definition.typeSummary()); 140 } else 141 res.logicalPaths.addAll(getLogicalPaths()); 142 return res; 143 } 144 145 private void setType(ElementDefinition type) { 146 this.type = type; 147 } 148 149 public NodeStack resetIds() { 150 ids = new HashMap<>(); 151 resetPoint = true; 152 return this; 153 } 154 public Map<String, Element> getIds() { 155 return ids; 156 } 157 private String tail(String path) { 158 return path.substring(path.lastIndexOf(".") + 1); 159 } 160 161 public boolean isPrimitiveType(String code) { 162 StructureDefinition sd = context.fetchTypeDefinition(code); 163 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 164 } 165 166 public String getWorkingLang() { 167 return workingLang; 168 } 169 170 public void setWorkingLang(String workingLang) { 171 this.workingLang = workingLang; 172 } 173 174 public NodeStack getParent() { 175 return parent; 176 } 177 178 public void qualifyPath(String qualifier) { 179 literalPath = literalPath + qualifier; 180 181 } 182 183 public boolean isResetPoint() { 184 return resetPoint; 185 } 186 187 @Override 188 public String toString() { 189 return literalPath; 190 } 191 192 public int depth() { 193 if (parent == null) { 194 return 0; 195 } else { 196 return parent.depth()+1; 197 } 198 } 199 200 public boolean isContained() { 201 return contained; 202 } 203 204 public void setContained(boolean contained) { 205 this.contained = contained; 206 } 207 208 209}