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