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