001package org.hl7.fhir.dstu3.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.apache.commons.lang3.StringUtils;
038import org.hl7.fhir.dstu3.conformance.ProfileUtilities;
039import org.hl7.fhir.dstu3.context.IWorkerContext;
040import org.hl7.fhir.dstu3.fhirpath.TypeDetails;
041import org.hl7.fhir.dstu3.formats.FormatUtilities;
042import org.hl7.fhir.dstu3.model.ElementDefinition;
043import org.hl7.fhir.dstu3.model.ElementDefinition.PropertyRepresentation;
044import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
045import org.hl7.fhir.dstu3.model.StructureDefinition;
046import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
047import org.hl7.fhir.dstu3.utils.ToolingExtensions;
048import org.hl7.fhir.exceptions.DefinitionException;
049
050
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).getCode();
077                        for (int i = 1; i < definition.getType().size(); i++) {
078                                if (!tn.equals(definition.getType().get(i).getCode()))
079                                        throw new Error("logic error, gettype when types > 1");
080                        }
081                        return tn;
082                } else
083                        return definition.getType().get(0).getCode();
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 "+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("logic error, gettype when types > 1, name mismatch for "+elementName+" on at "+ed.getPath());
120    } else if (ed.getType().get(0).getCode() == null) {
121      return structure.getId();
122                } else
123      return ed.getType().get(0).getCode();
124        }
125
126  public boolean hasType(String elementName) {
127    if (definition.getType().size() == 0)
128      return false;
129    else if (definition.getType().size() > 1) {
130      String t = definition.getType().get(0).getCode();
131      boolean all = true;
132      for (TypeRefComponent tr : definition.getType()) {
133        if (!t.equals(tr.getCode()))
134          all = false;
135      }
136      if (all)
137        return true;
138      String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".")+1);
139      if (tail.endsWith("[x]") && elementName.startsWith(tail.substring(0, tail.length()-3))) {
140        String name = elementName.substring(tail.length()-3);
141        return true;        
142      } else
143        return false;
144    } else
145      return true;
146  }
147
148        public StructureDefinition getStructure() {
149                return structure;
150        }
151
152        /**
153         * Is the given name a primitive
154         * 
155         * @param E.g. "Observation.status"
156         */
157        public boolean isPrimitiveName(String name) {
158          String code = getType(name);
159      return isPrimitive(code);
160        }
161
162        /**
163         * Is the given type a primitive
164         * 
165         * @param E.g. "integer"
166         */
167        public boolean isPrimitive(String code) {
168    return org.hl7.fhir.dstu3.utils.TypesUtilities.isPrimitive(code);
169   // was this... but this can be very inefficient compared to hard coding the list
170//              StructureDefinition sd = context.fetchTypeDefinition(code);
171//    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
172        }
173
174        private String lowFirst(String t) {
175                return t.substring(0, 1).toLowerCase()+t.substring(1);
176        }
177
178        public boolean isResource() {
179          if (definition.getType().size() > 0)
180            return definition.getType().size() == 1 && ("Resource".equals(definition.getType().get(0).getCode()) || "DomainResource".equals(definition.getType().get(0).getCode()));
181          else
182            return !definition.getPath().contains(".") && structure.getKind() == StructureDefinitionKind.RESOURCE;
183        }
184
185        public boolean isList() {
186          return !"1".equals(definition.getMax());
187        }
188
189  public String getScopedPropertyName() {
190    return definition.getBase().getPath();
191  }
192
193  public String getNamespace() {
194    if (ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
195      return ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
196    if (ToolingExtensions.hasExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
197      return ToolingExtensions.readStringExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
198    return FormatUtilities.FHIR_NS;
199  }
200
201        public boolean IsLogicalAndHasPrimitiveValue(String name) {
202//              if (canBePrimitive!= null)
203//                      return canBePrimitive;
204                
205                canBePrimitive = false;
206        if (structure.getKind() != StructureDefinitionKind.LOGICAL)
207                return false;
208        if (!hasType(name))
209                return false;
210        StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name));
211        if (sd == null)
212          sd = context.fetchTypeDefinition(getType(name));
213    if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE)
214      return true;
215        if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL)
216                return false;
217        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
218                if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) {
219                        canBePrimitive = true;
220                        return true;
221                }
222        }
223        return false;
224        }
225
226  public boolean isChoice() {
227    if (definition.getType().size() <= 1)
228      return false;
229    String tn = definition.getType().get(0).getCode();
230    for (int i = 1; i < definition.getType().size(); i++) 
231      if (!definition.getType().get(i).getCode().equals(tn))
232        return true;
233    return false;
234  }
235
236
237  protected List<Property> getChildProperties(String elementName, String statedType) throws DefinitionException {
238    ElementDefinition ed = definition;
239    StructureDefinition sd = structure;
240    List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed);
241    if (children.isEmpty()  || isElementWithOnlyExtension(ed, children)) {
242      // ok, find the right definitions
243      String t = null;
244      if (ed.getType().size() == 1)
245        t = ed.getType().get(0).getCode();
246      else if (ed.getType().size() == 0)
247        throw new Error("types == 0, and no children found");
248      else {
249        t = ed.getType().get(0).getCode();
250        boolean all = true;
251        for (TypeRefComponent tr : ed.getType()) {
252          if (!tr.getCode().equals(t)) {
253            all = false;
254            break;
255          }
256        }
257        if (!all) {
258          // ok, it's polymorphic
259          if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
260            t = statedType;
261            if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"))
262              t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
263            boolean ok = false;
264            for (TypeRefComponent tr : ed.getType()) 
265              if (tr.getCode().equals(t)) 
266                ok = true;
267             if (!ok)
268               throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath());
269            
270          } else {
271            t = elementName.substring(tail(ed.getPath()).length() - 3);
272            if (isPrimitive(lowFirst(t)))
273              t = lowFirst(t);
274          }
275        }
276      }
277      if (!"xhtml".equals(t)) {
278        final String url;
279        if (StringUtils.isNotBlank(ed.getType().get(0).getProfile())) {
280         url = ed.getType().get(0).getProfile();
281        } else {
282          url = "http://hl7.org/fhir/StructureDefinition/" + t;
283        }
284        sd = context.fetchResource(StructureDefinition.class, url);
285        if (sd == null)
286          throw new DefinitionException("Unable to find type '"+t+"' for name '"+elementName+"' on property "+definition.getPath());
287        children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
288      }
289    }
290    List<Property> properties = new ArrayList<Property>();
291    for (ElementDefinition child : children) {
292      properties.add(new Property(context, child, sd));
293    }
294    return properties;
295  }
296
297  protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException {
298    ElementDefinition ed = definition;
299    StructureDefinition sd = structure;
300    List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed);
301    if (children.isEmpty()) {
302      // ok, find the right definitions
303      String t = null;
304      if (ed.getType().size() == 1)
305        t = ed.getType().get(0).getCode();
306      else if (ed.getType().size() == 0)
307        throw new Error("types == 0, and no children found");
308      else {
309        t = ed.getType().get(0).getCode();
310        boolean all = true;
311        for (TypeRefComponent tr : ed.getType()) {
312          if (!tr.getCode().equals(t)) {
313            all = false;
314            break;
315          }
316        }
317        if (!all) {
318          // ok, it's polymorphic
319          t = type.getType();
320        }
321      }
322      if (!"xhtml".equals(t)) {
323        sd = context.fetchResource(StructureDefinition.class, t);
324        if (sd == null)
325          throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath());
326        children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
327      }
328    }
329    List<Property> properties = new ArrayList<Property>();
330    for (ElementDefinition child : children) {
331      properties.add(new Property(context, child, sd));
332    }
333    return properties;
334  }
335
336  private String tail(String path) {
337    return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path;
338  }
339
340  public Property getChild(String elementName, String childName) throws DefinitionException {
341    List<Property> children = getChildProperties(elementName, null);
342    for (Property p : children) {
343      if (p.getName().equals(childName)) {
344        return p;
345      }
346    }
347    return null;
348  }
349
350  public Property getChild(String name, TypeDetails type) throws DefinitionException {
351    List<Property> children = getChildProperties(type);
352    for (Property p : children) {
353      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
354        return p;
355      }
356    }
357    return null;
358  }
359
360  public Property getChild(String name) throws DefinitionException {
361    List<Property> children = getChildProperties(name, null);
362    for (Property p : children) {
363      if (p.getName().equals(name)) {
364        return p;
365      }
366    }
367    return null;
368  }
369
370  public Property getChildSimpleName(String elementName, String name) throws DefinitionException {
371    List<Property> children = getChildProperties(elementName, null);
372    for (Property p : children) {
373      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
374        return p;
375      }
376    }
377    return null;
378  }
379
380  public IWorkerContext getContext() {
381    return context;
382  }
383
384  private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) {
385    boolean result = false;
386    if (!ed.getType().isEmpty()) {
387      result = true;
388      for (final ElementDefinition ele : children) {
389        if (!ele.getPath().contains("extension")) {
390          result = false;
391          break;
392        }
393      }
394    }
395    return result;
396  }
397
398}