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