001package org.hl7.fhir.r5.profilemodel; 002 003import java.util.ArrayList; 004import java.util.HashMap; 005import java.util.List; 006import java.util.Map; 007 008import org.apache.xmlbeans.impl.xb.xsdschema.All; 009import org.hl7.fhir.exceptions.DefinitionException; 010import org.hl7.fhir.r5.context.ContextUtilities; 011import org.hl7.fhir.r5.model.Base; 012import org.hl7.fhir.r5.model.ElementDefinition; 013import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 014import org.hl7.fhir.r5.model.StructureDefinition; 015import org.hl7.fhir.r5.profilemodel.PEDefinition.PEDefinitionElementMode; 016import org.hl7.fhir.utilities.Utilities; 017 018public abstract class PEDefinition { 019 020 public enum PEDefinitionElementMode { 021 Resource, Element, DataType, Extension 022 } 023 024 protected PEBuilder builder; 025 protected String name; 026 protected String path; 027 protected StructureDefinition profile; 028 protected ElementDefinition definition; 029 protected List<PEType> types; 030 protected Map<String, List<PEDefinition>> children = new HashMap<>(); 031 private boolean recursing; 032 private boolean mustHaveValue; 033 private boolean inFixedValue; 034 private boolean isSlicer; 035 036// /** 037// * Don't create one of these directly - always use the public methods on ProfiledElementBuilder 038// * 039// * @param builder 040// * @param baseElement 041// * @param profiledElement 042// * @param data 043// */ 044// protected PEDefinition(PEBuilder builder, String name, 045// ElementDefinition definition, Base data) { 046// super(); 047// this.builder = builder; 048// this.name = name; 049// this.definition = definition; 050//// this.data = data; 051// } 052 053 protected PEDefinition(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, String ppath) { 054 this.builder = builder; 055 this.name = name; 056 this.profile = profile; 057 this.definition = definition; 058 this.path = path == null ? name : ppath+"."+name; 059 } 060 061 062 /** 063 * @return The name of the element or slice in the profile (always unique amongst children) 064 */ 065 public String name() { 066 return name; 067 } 068 069 /** 070 * @return The path of the element or slice in the profile (name.name.name...) 071 */ 072 public String path() { 073 return path; 074 } 075 076 /** 077 * @return The name of the element in the resource (may be different to the slice name) 078 */ 079 public String schemaName() { 080 return definition.getName(); 081 } 082 083 /** 084 * @return a list of types. There is usually at least one type; it might be Element, Type, BackboneElement or BackboneType 085 * 086 * The following elements don't have types (true primitives): Element.id. Extension.url, PrimitiveType.value 087 */ 088 public List<PEType> types() { 089 if (types == null) { 090 List<PEType> ltypes = new ArrayList<>(); 091 listTypes(ltypes); 092 types = ltypes; 093 } 094 return types; 095 } 096 097 protected abstract void listTypes(List<PEType> types); 098 099 /** 100 * @return The minimum number of repeats allowed 101 */ 102 public int min() { 103 return mustHaveValue ? 1 : definition.getMin(); 104 } 105 106 /** 107 * @return the maximum number of repeats allowed 108 */ 109 public int max() { 110 return definition.getMax() == null || "*".equals(definition.getMax()) ? Integer.MAX_VALUE : Integer.parseInt(definition.getMax()); 111 } 112 113 /** 114 * @return the definition of the element in the profile (fully populated) 115 * 116 * Note that the profile definition might be the same as a base definition, when the tree runs off the end of what's profiled 117 */ 118 public ElementDefinition definition() { 119 return definition; 120 } 121 122 /** 123 * @return the definition of the element in the base specification 124 * 125 * Note that the profile definition might be the same as a base definition, when the tree runs off the end of what's profiled 126 */ 127 public ElementDefinition baseDefinition() { 128 String type = definition.getBase().getPath(); 129 if (type.contains(".")) { 130 type= type.substring(0, type.indexOf(".")); 131 } 132 StructureDefinition sd = builder.getContext().fetchTypeDefinition(type); 133 return sd.getSnapshot().getElementByPath(definition.getBase().getPath()); 134 } 135 136 /** 137 * @return the short documentation of the definition (shown in the profile table view) 138 */ 139 public String shortDocumentation() { 140 return definition.getShort(); 141 } 142 143 /** 144 * @return the full definition of the element (markdown syntax) 145 */ 146 public String documentation() { 147 return definition.getDefinition(); 148 } 149 150// /** 151// * @return if the profiled definition has a value set, get the expansion 152// */ 153// public ValueSet expansion() { 154// throw new NotImplementedException("Not done yet"); 155// } 156// 157 /** 158 * @param typeUrl - the url of one of the types listed in types() 159 * @return - the list of children for the nominated type 160 * 161 * Warning: profiles and resources can be recursive; you can't iterate this tree until you get 162 * to the leaves because you will never get to a child that doesn't have children (extensions have extensions etc) 163 * 164 */ 165 public List<PEDefinition> children(String typeUrl) { 166 return children(typeUrl, false); 167 } 168 169 public List<PEDefinition> children(String typeUrl, boolean allFixed) { 170 if (children.containsKey(typeUrl+"$"+allFixed)) { 171 return children.get(typeUrl+"$"+allFixed); 172 } 173 List<PEDefinition> res = new ArrayList<>(); 174 makeChildren(typeUrl, res, allFixed); 175 children.put(typeUrl+"$"+allFixed, res); 176 return res; 177 } 178 179 public List<PEDefinition> children() { 180 if (types().size() == 1) { 181 return children(types.get(0).getUrl(), false); 182 } else { 183 throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")"); 184 } 185 } 186 187 public List<PEDefinition> children(boolean allFixed) { 188 if (types().size() == 1) { 189 return children(types.get(0).getUrl(), allFixed); 190 } else { 191 throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")"); 192 } 193 } 194 195 /** 196 * @return True if the element has a fixed value. This will always be false if fixedProps = false when the builder is created 197 */ 198 public boolean fixedValue() { 199 return definition.hasFixed() || definition.hasPattern(); 200 } 201 202 protected abstract void makeChildren(String typeUrl, List<PEDefinition> children, boolean allFixed); 203 204 @Override 205 public String toString() { 206 return name+"("+schemaName()+"):"+types().toString()+" ["+min()+":"+(max() == Integer.MAX_VALUE ? "*" : max() )+"] \""+shortDocumentation()+"\""; 207 } 208 209 /** 210 * @return true if the builder observes that this element is recursing (extensions have extensions) 211 * 212 * Note that this is unreliable and may be withdrawn if it can't be fixed 213 */ 214 public boolean isRecursing() { 215 return recursing; 216 } 217 218 protected void setRecursing(boolean recursing) { 219 this.recursing = recursing; 220 } 221 222 protected boolean isMustHaveValue() { 223 return mustHaveValue; 224 } 225 226 protected void setMustHaveValue(boolean mustHaveValue) { 227 this.mustHaveValue = mustHaveValue; 228 } 229 230 /** 231 * @return true if this property is inside an element that has an assigned fixed value 232 */ 233 public boolean isInFixedValue() { 234 return inFixedValue; 235 } 236 237 238 protected void setInFixedValue(boolean inFixedValue) { 239 this.inFixedValue = inFixedValue; 240 } 241 242 243 /** 244 * This is public to support unit testing - there's no reason to use it otherwise 245 * 246 * @return used in the instance processor to differentiate slices 247 */ 248 public abstract String fhirpath(); 249 250 251 public boolean isList() { 252 return "*".equals(definition.getBase().getMax()); 253 } 254 255 256 public boolean repeats() { 257 return max() > 1; 258 } 259 260 public PEDefinitionElementMode mode() { 261 if (builder.isResource(definition.getBase().getPath())) { 262 return PEDefinitionElementMode.Resource; 263 } 264 for (TypeRefComponent tr : definition.getType()) { 265 if ("Extension".equals(tr.getWorkingCode())) { 266 return PEDefinitionElementMode.Extension; 267 } 268 if (!Utilities.existsInList(tr.getWorkingCode(), "Element", "BackboneElement")) { 269 return PEDefinitionElementMode.DataType; 270 } 271 } 272 return PEDefinitionElementMode.Element; 273 } 274 275 /** 276 * @return true if this element is profiled one way or another 277 */ 278 public boolean isProfiled() { 279 return !profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition"); 280 } 281 282 283 public boolean isSlicer() { 284 return isSlicer; 285 } 286 287 288 public void setSlicer(boolean isSlicer) { 289 this.isSlicer = isSlicer; 290 } 291 292 293 public boolean isBaseList() { 294 return !"1".equals(definition.getBase().getMax()); 295 } 296 297 298 public StructureDefinition getProfile() { 299 return profile; 300 } 301 302 303 public boolean isKeyElement() { 304 boolean selfKey = definition.getMustSupport() || definition.getMustHaveValue() || min() > 0 || definition.hasCondition(); 305 if (isProfiled() && !selfKey) { 306 if (types() != null && types().size() > 0) { 307 for (PEDefinition child : children()) { 308 if (child.isKeyElement()) { 309 return true; 310 } 311 } 312 } 313 } 314 return selfKey; 315 } 316 317} 318 319