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