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