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