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