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