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