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