
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.hl7.fhir.exceptions.DefinitionException; 037import org.hl7.fhir.r5.model.DataType; 038import org.hl7.fhir.r5.model.ElementDefinition; 039import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 040import org.hl7.fhir.r5.model.StructureDefinition; 041import org.hl7.fhir.r5.model.ValueSet; 042import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 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 = ppath == 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 (element = "+path+", 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 (element = "+path+", 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 hasFixedValue() { 239 return definition.hasFixed() || definition.hasPattern(); 240 } 241 242 public DataType getFixedValue() { 243 return definition.hasFixed() ? definition.getFixed() : definition.getPattern(); 244 } 245 246 247 protected abstract void makeChildren(String typeUrl, List<PEDefinition> children, boolean allFixed); 248 249 @Override 250 public String toString() { 251 return name+"("+schemaName()+"):"+types().toString()+" ["+min()+":"+(max() == Integer.MAX_VALUE ? "*" : max() )+"] \""+shortDocumentation()+"\""; 252 } 253 254 /** 255 * @return true if the builder observes that this element is recursing (extensions have extensions) 256 * 257 * Note that this is unreliable and may be withdrawn if it can't be fixed 258 */ 259 public boolean isRecursing() { 260 return recursing; 261 } 262 263 protected void setRecursing(boolean recursing) { 264 this.recursing = recursing; 265 } 266 267 protected boolean isMustHaveValue() { 268 return mustHaveValue; 269 } 270 271 protected void setMustHaveValue(boolean mustHaveValue) { 272 this.mustHaveValue = mustHaveValue; 273 } 274 275 /** 276 * @return true if this property is inside an element that has an assigned fixed value 277 */ 278 public boolean isInFixedValue() { 279 return inFixedValue; 280 } 281 282 283 protected void setInFixedValue(boolean inFixedValue) { 284 this.inFixedValue = inFixedValue; 285 } 286 287 288 /** 289 * This is public to support unit testing - there's no reason to use it otherwise 290 * 291 * @return used in the instance processor to differentiate slices 292 */ 293 public abstract String fhirpath(); 294 295 296 public boolean isList() { 297 return "*".equals(definition.getMax()) || (Utilities.parseInt(definition.getMax(), 2) > 1); 298 } 299 300 301 public boolean repeats() { 302 return max() > 1; 303 } 304 305 public PEDefinitionElementMode mode() { 306 if (builder.isResource(definition.getBase().getPath())) { 307 return PEDefinitionElementMode.Resource; 308 } 309 for (TypeRefComponent tr : definition.getType()) { 310 if ("Extension".equals(tr.getWorkingCode())) { 311 return PEDefinitionElementMode.Extension; 312 } 313 if (!Utilities.existsInList(tr.getWorkingCode(), "Element", "BackboneElement")) { 314 return PEDefinitionElementMode.DataType; 315 } 316 } 317 return PEDefinitionElementMode.Element; 318 } 319 320 /** 321 * @return true if this element is profiled one way or another 322 */ 323 public boolean isProfiled() { 324 return !profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition"); 325 } 326 327 328 public boolean isSlicer() { 329 return isSlicer; 330 } 331 332 333 public void setSlicer(boolean isSlicer) { 334 this.isSlicer = isSlicer; 335 } 336 337 338 public boolean isBaseList() { 339 return !"1".equals(definition.getBase().getMax()); 340 } 341 342 343 public StructureDefinition getProfile() { 344 return profile; 345 } 346 347 348 public boolean isKeyElement() { 349 boolean selfKey = definition.getMustSupport() || definition.getMustHaveValue() || min() > 0 || definition.hasCondition(); 350 if (isProfiled() && !selfKey) { 351 if (types() != null && types().size() > 0) { 352 for (PEDefinition child : children()) { 353 if (child.isKeyElement()) { 354 return true; 355 } 356 } 357 } 358 } 359 return selfKey; 360 } 361 362 363 public boolean isPrimitive() { 364 return types().size() == 1 && builder.getContext().isPrimitiveType(types.get(0).getName()); 365 } 366 367 368 public boolean isBasePrimitive() { 369 ElementDefinition ed = baseDefinition(); 370 return ed != null && ed.getType().size() == 1 && builder.getContext().isPrimitiveType(ed.getType().get(0).getWorkingCode()); 371 } 372 373 374 // extensions do something different here 375 public List<PEDefinition> directChildren(boolean allFixed) { 376 return children(allFixed); 377 } 378 379 380 public List<PEDefinition> getSlices() { 381 return slices; 382 } 383 384 385 public void setSlices(List<PEDefinition> slices) { 386 this.slices = slices; 387 } 388 389 390 public boolean isExtension() { 391 return false; 392 } 393 394 public String getExtensionUrl() { 395 return null; 396 } 397 398 public ValueSet valueSet() { 399 if (definition.getBinding().hasValueSet()) { 400 return builder.getContext().fetchResource(ValueSet.class, definition.getBinding().getValueSet()); 401 } 402 return null; 403 } 404 405 406 public PEBuilder getBuilder() { 407 return builder; 408 } 409 410 public String typeSummary() { 411 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 412 for (PEType t : types()) { 413 b.append(t.getName()); 414 } 415 return b.toString(); 416 } 417 418 419 public boolean isSlice() { 420 return definition.hasSliceName(); 421 } 422 423} 424 425