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