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
032
033
034import java.util.ArrayList;
035import java.util.List;
036
037import org.hl7.fhir.exceptions.DefinitionException;
038import org.hl7.fhir.exceptions.FHIRException;
039import org.hl7.fhir.r4.conformance.ProfileUtilities;
040import org.hl7.fhir.r4.context.IWorkerContext;
041import org.hl7.fhir.r4.formats.FormatUtilities;
042import org.hl7.fhir.r4.model.ElementDefinition;
043import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation;
044import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
045import org.hl7.fhir.r4.model.StructureDefinition;
046import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
047import org.hl7.fhir.r4.model.TypeDetails;
048import org.hl7.fhir.r4.utils.ToolingExtensions;
049import org.hl7.fhir.r4.utils.TypesUtilities;
050import org.hl7.fhir.utilities.Utilities;
051
052public class Property {
053
054        private IWorkerContext context;
055        private ElementDefinition definition;
056        private StructureDefinition structure;
057        private Boolean canBePrimitive; 
058
059        public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure) {
060                this.context = context;
061                this.definition = definition;
062                this.structure = structure;
063        }
064
065        public String getName() {
066                return definition.getPath().substring(definition.getPath().lastIndexOf(".")+1);
067        }
068
069        public ElementDefinition getDefinition() {
070                return definition;
071        }
072
073        public String getType() {
074                if (definition.getType().size() == 0)
075                        return null;
076                else if (definition.getType().size() > 1) {
077                        String tn = definition.getType().get(0).getWorkingCode();
078                        for (int i = 1; i < definition.getType().size(); i++) {
079                                if (!tn.equals(definition.getType().get(i).getWorkingCode()))
080                                        throw new Error("logic error, gettype when types > 1");
081                        }
082                        return tn;
083                } else
084                        return definition.getType().get(0).getWorkingCode();
085        }
086
087        public String getType(String elementName) {
088    if (!definition.getPath().contains("."))
089      return definition.getPath();
090    ElementDefinition ed = definition;
091    if (definition.hasContentReference()) {
092      if (!definition.getContentReference().startsWith("#"))
093        throw new Error("not handled yet");
094      boolean found = false;
095      for (ElementDefinition d : structure.getSnapshot().getElement()) {
096        if (d.hasId() && d.getId().equals(definition.getContentReference().substring(1))) {
097          found = true;
098          ed = d;
099        }
100      }
101      if (!found)
102        throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+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("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()) || "DomainResource".equals(definition.getType().get(0).getCode()));
185          else
186            return !definition.getPath().contains(".") && structure.getKind() == StructureDefinitionKind.RESOURCE;
187        }
188
189        public boolean isList() {
190          return !"1".equals(definition.getMax());
191        }
192
193  public String getScopedPropertyName() {
194    return definition.getBase().getPath();
195  }
196
197  public String getNamespace() {
198    if (ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
199      return ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
200    if (ToolingExtensions.hasExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
201      return ToolingExtensions.readStringExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
202    return FormatUtilities.FHIR_NS;
203  }
204
205  private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) {
206    boolean result = false;
207    if (!ed.getType().isEmpty()) {
208      result = true;
209      for (final ElementDefinition ele : children) {
210        if (!ele.getPath().contains("extension")) {
211          result = false;
212          break;
213        }
214      }
215    }
216    return result;
217  }
218  
219        public boolean IsLogicalAndHasPrimitiveValue(String name) {
220//              if (canBePrimitive!= null)
221//                      return canBePrimitive;
222                
223                canBePrimitive = false;
224        if (structure.getKind() != StructureDefinitionKind.LOGICAL)
225                return false;
226        if (!hasType(name))
227                return false;
228        StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name));
229        if (sd == null)
230          sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(getType(name), context.getOverrideVersionNs()));
231    if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE)
232      return true;
233        if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL)
234                return false;
235        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
236                if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) {
237                        canBePrimitive = true;
238                        return true;
239                }
240        }
241        return false;
242        }
243
244  public boolean isChoice() {
245    if (definition.getType().size() <= 1)
246      return false;
247    String tn = definition.getType().get(0).getCode();
248    for (int i = 1; i < definition.getType().size(); i++) 
249      if (!definition.getType().get(i).getCode().equals(tn))
250        return true;
251    return false;
252  }
253
254
255  protected List<Property> getChildProperties(String elementName, String statedType) throws FHIRException {
256    ElementDefinition ed = definition;
257    StructureDefinition sd = structure;
258    List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed);
259    String url = null;
260    if (children.isEmpty() || isElementWithOnlyExtension(ed, children)) {
261      // ok, find the right definitions
262      String t = null;
263      if (ed.getType().size() == 1)
264        t = ed.getType().get(0).getWorkingCode();
265      else if (ed.getType().size() == 0)
266        throw new Error("types == 0, and no children found on "+getDefinition().getPath());
267      else {
268        t = ed.getType().get(0).getWorkingCode();
269        boolean all = true;
270        for (TypeRefComponent tr : ed.getType()) {
271          if (!tr.getWorkingCode().equals(t)) {
272            all = false;
273            break;
274          }
275        }
276        if (!all) {
277          // ok, it's polymorphic
278          if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
279            t = statedType;
280            if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"))
281              t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
282            boolean ok = false;
283            for (TypeRefComponent tr : ed.getType()) { 
284              if (tr.getWorkingCode().equals(t)) 
285                ok = true;
286              if (Utilities.isAbsoluteUrl(tr.getWorkingCode())) {
287                StructureDefinition sdt = context.fetchResource(StructureDefinition.class, tr.getWorkingCode());
288                if (sdt != null && sdt.getType().equals(t)) {
289                  url = tr.getWorkingCode();
290                  ok = true;
291                }
292              }
293              if (ok)
294                break;
295            }
296             if (!ok)
297               throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath());
298            
299          } else {
300            t = elementName.substring(tail(ed.getPath()).length() - 3);
301            if (isPrimitive(lowFirst(t)))
302              t = lowFirst(t);
303          }
304        }
305      }
306      if (!"xhtml".equals(t)) {
307        for (TypeRefComponent aType: ed.getType()) {
308          if (aType.getWorkingCode().equals(t)) {
309            if (aType.hasProfile()) {
310              assert aType.getProfile().size() == 1; 
311              url = aType.getProfile().get(0).getValue();
312            } else {
313              url = ProfileUtilities.sdNs(t, context.getOverrideVersionNs());
314            }
315            break;
316          }
317        }
318        if (url==null)
319          throw new FHIRException("Unable to find type " + t + " for element " + elementName + " with path " + ed.getPath());
320        sd = context.fetchResource(StructureDefinition.class, url);        
321        if (sd == null)
322          throw new DefinitionException("Unable to find type '"+t+"' for name '"+elementName+"' on property "+definition.getPath());
323        children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
324      }
325    }
326    List<Property> properties = new ArrayList<Property>();
327    for (ElementDefinition child : children) {
328      properties.add(new Property(context, child, sd));
329    }
330    return properties;
331  }
332
333  protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException {
334    ElementDefinition ed = definition;
335    StructureDefinition sd = structure;
336    List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed);
337    if (children.isEmpty()) {
338      // ok, find the right definitions
339      String t = null;
340      if (ed.getType().size() == 1)
341        t = ed.getType().get(0).getCode();
342      else if (ed.getType().size() == 0)
343        throw new Error("types == 0, and no children found");
344      else {
345        t = ed.getType().get(0).getCode();
346        boolean all = true;
347        for (TypeRefComponent tr : ed.getType()) {
348          if (!tr.getCode().equals(t)) {
349            all = false;
350            break;
351          }
352        }
353        if (!all) {
354          // ok, it's polymorphic
355          t = type.getType();
356        }
357      }
358      if (!"xhtml".equals(t)) {
359        sd = context.fetchResource(StructureDefinition.class, t);
360        if (sd == null)
361          throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath());
362        children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
363      }
364    }
365    List<Property> properties = new ArrayList<Property>();
366    for (ElementDefinition child : children) {
367      properties.add(new Property(context, child, sd));
368    }
369    return properties;
370  }
371
372  private String tail(String path) {
373    return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path;
374  }
375
376  public Property getChild(String elementName, String childName) throws FHIRException {
377    List<Property> children = getChildProperties(elementName, null);
378    for (Property p : children) {
379      if (p.getName().equals(childName)) {
380        return p;
381      }
382    }
383    return null;
384  }
385
386  public Property getChild(String name, TypeDetails type) throws DefinitionException {
387    List<Property> children = getChildProperties(type);
388    for (Property p : children) {
389      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
390        return p;
391      }
392    }
393    return null;
394  }
395
396  public Property getChild(String name) throws FHIRException {
397    List<Property> children = getChildProperties(name, null);
398    for (Property p : children) {
399      if (p.getName().equals(name)) {
400        return p;
401      }
402    }
403    return null;
404  }
405
406  public Property getChildSimpleName(String elementName, String name) throws FHIRException {
407    List<Property> children = getChildProperties(elementName, null);
408    for (Property p : children) {
409      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
410        return p;
411      }
412    }
413    return null;
414  }
415
416  public IWorkerContext getContext() {
417    return context;
418  }
419
420  @Override
421  public String toString() {
422    return definition.getPath();
423  }
424
425
426}