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