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