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