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