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