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