001package org.hl7.fhir.r4.elementmodel;
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 */
031
032import java.util.ArrayList;
033import java.util.List;
034
035import org.hl7.fhir.exceptions.DefinitionException;
036import org.hl7.fhir.exceptions.FHIRException;
037import org.hl7.fhir.r4.conformance.ProfileUtilities;
038import org.hl7.fhir.r4.context.IWorkerContext;
039import org.hl7.fhir.r4.formats.FormatUtilities;
040import org.hl7.fhir.r4.model.ElementDefinition;
041import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation;
042import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
043import org.hl7.fhir.r4.model.StructureDefinition;
044import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
045import org.hl7.fhir.r4.model.TypeDetails;
046import org.hl7.fhir.r4.utils.ToolingExtensions;
047import org.hl7.fhir.r4.utils.TypesUtilities;
048import org.hl7.fhir.utilities.Utilities;
049
050public class Property {
051
052  private IWorkerContext context;
053  private ElementDefinition definition;
054  private StructureDefinition structure;
055  private Boolean canBePrimitive;
056
057  public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure) {
058    this.context = context;
059    this.definition = definition;
060    this.structure = structure;
061  }
062
063  public String getName() {
064    return definition.getPath().substring(definition.getPath().lastIndexOf(".") + 1);
065  }
066
067  public ElementDefinition getDefinition() {
068    return definition;
069  }
070
071  public String getType() {
072    if (definition.getType().size() == 0)
073      return null;
074    else if (definition.getType().size() > 1) {
075      String tn = definition.getType().get(0).getWorkingCode();
076      for (int i = 1; i < definition.getType().size(); i++) {
077        if (!tn.equals(definition.getType().get(i).getWorkingCode()))
078          throw new Error("logic error, gettype when types > 1");
079      }
080      return tn;
081    } else
082      return definition.getType().get(0).getWorkingCode();
083  }
084
085  public String getType(String elementName) {
086    if (!definition.getPath().contains("."))
087      return definition.getPath();
088    ElementDefinition ed = definition;
089    if (definition.hasContentReference()) {
090      if (!definition.getContentReference().startsWith("#"))
091        throw new Error("not handled yet");
092      boolean found = false;
093      for (ElementDefinition d : structure.getSnapshot().getElement()) {
094        if (d.hasId() && d.getId().equals(definition.getContentReference().substring(1))) {
095          found = true;
096          ed = d;
097        }
098      }
099      if (!found)
100        throw new Error("Unable to resolve " + definition.getContentReference() + " at " + definition.getPath() + " on "
101            + structure.getUrl());
102    }
103    if (ed.getType().size() == 0)
104      return null;
105    else if (ed.getType().size() > 1) {
106      String t = ed.getType().get(0).getCode();
107      boolean all = true;
108      for (TypeRefComponent tr : ed.getType()) {
109        if (!t.equals(tr.getCode()))
110          all = false;
111      }
112      if (all)
113        return t;
114      String tail = ed.getPath().substring(ed.getPath().lastIndexOf(".") + 1);
115      if (tail.endsWith("[x]") && elementName != null && elementName.startsWith(tail.substring(0, tail.length() - 3))) {
116        String name = elementName.substring(tail.length() - 3);
117        return isPrimitive(lowFirst(name)) ? lowFirst(name) : name;
118      } else
119        throw new Error(
120            "logic error, gettype when types > 1, name mismatch for " + elementName + " on at " + ed.getPath());
121    } else if (ed.getType().get(0).getCode() == null) {
122      if (Utilities.existsInList(ed.getId(), "Element.id", "Extension.url"))
123        return "string";
124      else
125        return structure.getId();
126    } else
127      return ed.getType().get(0).getWorkingCode();
128  }
129
130  public boolean hasType(String elementName) {
131    if (definition.getType().size() == 0)
132      return false;
133    else if (definition.getType().size() > 1) {
134      String t = definition.getType().get(0).getCode();
135      boolean all = true;
136      for (TypeRefComponent tr : definition.getType()) {
137        if (!t.equals(tr.getCode()))
138          all = false;
139      }
140      if (all)
141        return true;
142      String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".") + 1);
143      if (tail.endsWith("[x]") && elementName.startsWith(tail.substring(0, tail.length() - 3))) {
144        String name = elementName.substring(tail.length() - 3);
145        return true;
146      } else
147        return false;
148    } else
149      return true;
150  }
151
152  public StructureDefinition getStructure() {
153    return structure;
154  }
155
156  /**
157   * Is the given name a primitive
158   * 
159   * @param E.g. "Observation.status"
160   */
161  public boolean isPrimitiveName(String name) {
162    String code = getType(name);
163    return isPrimitive(code);
164  }
165
166  /**
167   * Is the given type a primitive
168   * 
169   * @param E.g. "integer"
170   */
171  public boolean isPrimitive(String code) {
172    return TypesUtilities.isPrimitive(code);
173    // was this... but this can be very inefficient compared to hard coding the list
174//              StructureDefinition sd = context.fetchTypeDefinition(code);
175//      return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
176  }
177
178  private String lowFirst(String t) {
179    return t.substring(0, 1).toLowerCase() + t.substring(1);
180  }
181
182  public boolean isResource() {
183    if (definition.getType().size() > 0)
184      return definition.getType().size() == 1 && ("Resource".equals(definition.getType().get(0).getCode())
185          || "DomainResource".equals(definition.getType().get(0).getCode()));
186    else
187      return !definition.getPath().contains(".") && structure.getKind() == StructureDefinitionKind.RESOURCE;
188  }
189
190  public boolean isList() {
191    return !"1".equals(definition.getMax());
192  }
193
194  public String getScopedPropertyName() {
195    return definition.getBase().getPath();
196  }
197
198  public String getNamespace() {
199    if (ToolingExtensions.hasExtension(definition,
200        "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
201      return ToolingExtensions.readStringExtension(definition,
202          "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
203    if (ToolingExtensions.hasExtension(structure,
204        "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
205      return ToolingExtensions.readStringExtension(structure,
206          "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
207    return FormatUtilities.FHIR_NS;
208  }
209
210  private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) {
211    boolean result = false;
212    if (!ed.getType().isEmpty()) {
213      result = true;
214      for (final ElementDefinition ele : children) {
215        if (!ele.getPath().contains("extension")) {
216          result = false;
217          break;
218        }
219      }
220    }
221    return result;
222  }
223
224  public boolean IsLogicalAndHasPrimitiveValue(String name) {
225//              if (canBePrimitive!= null)
226//                      return canBePrimitive;
227
228    canBePrimitive = false;
229    if (structure.getKind() != StructureDefinitionKind.LOGICAL)
230      return false;
231    if (!hasType(name))
232      return false;
233    StructureDefinition sd = context.fetchResource(StructureDefinition.class,
234        structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/") + 1) + getType(name));
235    if (sd == null)
236      sd = context.fetchResource(StructureDefinition.class,
237          ProfileUtilities.sdNs(getType(name), context.getOverrideVersionNs()));
238    if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE)
239      return true;
240    if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL)
241      return false;
242    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
243      if (ed.getPath().equals(sd.getId() + ".value") && ed.getType().size() == 1
244          && isPrimitive(ed.getType().get(0).getCode())) {
245        canBePrimitive = true;
246        return true;
247      }
248    }
249    return false;
250  }
251
252  public boolean isChoice() {
253    if (definition.getType().size() <= 1)
254      return false;
255    String tn = definition.getType().get(0).getCode();
256    for (int i = 1; i < definition.getType().size(); i++)
257      if (!definition.getType().get(i).getCode().equals(tn))
258        return true;
259    return false;
260  }
261
262  protected List<Property> getChildProperties(String elementName, String statedType) throws FHIRException {
263    ElementDefinition ed = definition;
264    StructureDefinition sd = structure;
265    List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed);
266    String url = null;
267    if (children.isEmpty() || isElementWithOnlyExtension(ed, children)) {
268      // ok, find the right definitions
269      String t = null;
270      if (ed.getType().size() == 1)
271        t = ed.getType().get(0).getWorkingCode();
272      else if (ed.getType().size() == 0)
273        throw new Error("types == 0, and no children found on " + getDefinition().getPath());
274      else {
275        t = ed.getType().get(0).getWorkingCode();
276        boolean all = true;
277        for (TypeRefComponent tr : ed.getType()) {
278          if (!tr.getWorkingCode().equals(t)) {
279            all = false;
280            break;
281          }
282        }
283        if (!all) {
284          // ok, it's polymorphic
285          if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
286            t = statedType;
287            if (t == null && ToolingExtensions.hasExtension(ed,
288                "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"))
289              t = ToolingExtensions.readStringExtension(ed,
290                  "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
291            boolean ok = false;
292            for (TypeRefComponent tr : ed.getType()) {
293              if (tr.getWorkingCode().equals(t))
294                ok = true;
295              if (Utilities.isAbsoluteUrl(tr.getWorkingCode())) {
296                StructureDefinition sdt = context.fetchResource(StructureDefinition.class, tr.getWorkingCode());
297                if (sdt != null && sdt.getType().equals(t)) {
298                  url = tr.getWorkingCode();
299                  ok = true;
300                }
301              }
302              if (ok)
303                break;
304            }
305            if (!ok)
306              throw new DefinitionException("Type '" + t + "' is not an acceptable type for '" + elementName
307                  + "' on property " + definition.getPath());
308
309          } else {
310            t = elementName.substring(tail(ed.getPath()).length() - 3);
311            if (isPrimitive(lowFirst(t)))
312              t = lowFirst(t);
313          }
314        }
315      }
316      if (!"xhtml".equals(t)) {
317        for (TypeRefComponent aType : ed.getType()) {
318          if (aType.getWorkingCode().equals(t)) {
319            if (aType.hasProfile()) {
320              assert aType.getProfile().size() == 1;
321              url = aType.getProfile().get(0).getValue();
322            } else {
323              url = ProfileUtilities.sdNs(t, context.getOverrideVersionNs());
324            }
325            break;
326          }
327        }
328        if (url == null)
329          throw new FHIRException(
330              "Unable to find type " + t + " for element " + elementName + " with path " + ed.getPath());
331        sd = context.fetchResource(StructureDefinition.class, url);
332        if (sd == null)
333          throw new DefinitionException(
334              "Unable to find type '" + t + "' for name '" + elementName + "' on property " + definition.getPath());
335        children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
336      }
337    }
338    List<Property> properties = new ArrayList<Property>();
339    for (ElementDefinition child : children) {
340      properties.add(new Property(context, child, sd));
341    }
342    return properties;
343  }
344
345  protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException {
346    ElementDefinition ed = definition;
347    StructureDefinition sd = structure;
348    List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed);
349    if (children.isEmpty()) {
350      // ok, find the right definitions
351      String t = null;
352      if (ed.getType().size() == 1)
353        t = ed.getType().get(0).getCode();
354      else if (ed.getType().size() == 0)
355        throw new Error("types == 0, and no children found");
356      else {
357        t = ed.getType().get(0).getCode();
358        boolean all = true;
359        for (TypeRefComponent tr : ed.getType()) {
360          if (!tr.getCode().equals(t)) {
361            all = false;
362            break;
363          }
364        }
365        if (!all) {
366          // ok, it's polymorphic
367          t = type.getType();
368        }
369      }
370      if (!"xhtml".equals(t)) {
371        sd = context.fetchResource(StructureDefinition.class, t);
372        if (sd == null)
373          throw new DefinitionException(
374              "Unable to find class '" + t + "' for name '" + ed.getPath() + "' on property " + definition.getPath());
375        children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
376      }
377    }
378    List<Property> properties = new ArrayList<Property>();
379    for (ElementDefinition child : children) {
380      properties.add(new Property(context, child, sd));
381    }
382    return properties;
383  }
384
385  private String tail(String path) {
386    return path.contains(".") ? path.substring(path.lastIndexOf(".") + 1) : path;
387  }
388
389  public Property getChild(String elementName, String childName) throws FHIRException {
390    List<Property> children = getChildProperties(elementName, null);
391    for (Property p : children) {
392      if (p.getName().equals(childName)) {
393        return p;
394      }
395    }
396    return null;
397  }
398
399  public Property getChild(String name, TypeDetails type) throws DefinitionException {
400    List<Property> children = getChildProperties(type);
401    for (Property p : children) {
402      if (p.getName().equals(name) || p.getName().equals(name + "[x]")) {
403        return p;
404      }
405    }
406    return null;
407  }
408
409  public Property getChild(String name) throws FHIRException {
410    List<Property> children = getChildProperties(name, null);
411    for (Property p : children) {
412      if (p.getName().equals(name)) {
413        return p;
414      }
415    }
416    return null;
417  }
418
419  public Property getChildSimpleName(String elementName, String name) throws FHIRException {
420    List<Property> children = getChildProperties(elementName, null);
421    for (Property p : children) {
422      if (p.getName().equals(name) || p.getName().equals(name + "[x]")) {
423        return p;
424      }
425    }
426    return null;
427  }
428
429  public IWorkerContext getContext() {
430    return context;
431  }
432
433  @Override
434  public String toString() {
435    return definition.getPath();
436  }
437
438}