001package org.hl7.fhir.dstu3.elementmodel;
002
003import static org.apache.commons.lang3.StringUtils.isNotBlank;
004
005import java.io.IOException;
006import java.util.ArrayList;
007import java.util.Collections;
008import java.util.Comparator;
009import java.util.HashSet;
010import java.util.List;
011import java.util.Set;
012
013import org.apache.commons.lang3.Validate;
014import org.hl7.fhir.dstu3.elementmodel.Element.ElementSortComparator;
015import org.hl7.fhir.dstu3.model.Base;
016import org.hl7.fhir.dstu3.model.ElementDefinition;
017import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
018import org.hl7.fhir.dstu3.model.StringType;
019import org.hl7.fhir.dstu3.model.StructureDefinition;
020import org.hl7.fhir.dstu3.model.Type;
021import org.hl7.fhir.exceptions.FHIRException;
022import org.hl7.fhir.utilities.Utilities;
023import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
024import org.hl7.fhir.utilities.xhtml.XhtmlNode;
025
026/**
027 * This class represents the underlying reference model of FHIR
028 * 
029 * A resource is nothing but a set of elements, where every element has a 
030 * name, maybe a stated type, maybe an id, and either a value or child elements 
031 * (one or the other, but not both or neither)
032 * 
033 * @author Grahame Grieve
034 *
035 */
036public class Element extends Base {
037
038
039  public enum SpecialElement {
040                CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, PARAMETER;
041
042    public static SpecialElement fromProperty(Property property) {
043      if (property.getStructure().getIdElement().getIdPart().equals("Parameters"))
044        return PARAMETER;
045      if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("resource"))
046        return BUNDLE_ENTRY;
047      if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("outcome"))
048        return BUNDLE_OUTCOME;
049      if (property.getName().equals("contained")) 
050        return CONTAINED;
051      throw new Error("Unknown resource containing a native resource: "+property.getDefinition().getId());
052    }
053        }
054
055        private List<String> comments;// not relevant for production, but useful in documentation
056        private String name;
057        private String type;
058        private String value;
059        private int index = -1;
060        private List<Element> children;
061        private Property property;
062  private Property elementProperty; // this is used when special is set to true - it tracks the underlying element property which is used in a few places
063        private int line;
064        private int col;
065        private SpecialElement special;
066        private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation
067
068        public Element(String name) {
069                super();
070                this.name = name;
071        }
072
073  public Element(Element other) {
074    super();
075    name = other.name;
076    type = other.type;
077    property = other.property;
078    elementProperty = other.elementProperty;
079    special = other.special;
080  }
081  
082  public Element(String name, Property property) {
083                super();
084                this.name = name;
085                this.property = property;
086        }
087
088        public Element(String name, Property property, String type, String value) {
089                super();
090                this.name = name;
091                this.property = property;
092                this.type = type;
093                this.value = value;
094        }
095
096        public void updateProperty(Property property, SpecialElement special, Property elementProperty) {
097                this.property = property;
098    this.elementProperty = elementProperty;
099                this.special = special;
100        }
101
102        public SpecialElement getSpecial() {
103                return special;
104        }
105
106        public String getName() {
107                return name;
108        }
109
110        public String getType() {
111                if (type == null)
112                        return property.getType(name);
113                else
114                  return type;
115        }
116
117        public String getValue() {
118                return value;
119        }
120
121        public boolean hasChildren() {
122                return !(children == null || children.isEmpty());
123        }
124
125        public List<Element> getChildren() {
126                if (children == null)
127                        children = new ArrayList<Element>();
128                return children;
129        }
130
131        public boolean hasComments() {
132                return !(comments == null || comments.isEmpty());
133        }
134
135        public List<String> getComments() {
136                if (comments == null)
137                        comments = new ArrayList<String>();
138                return comments;
139        }
140
141        public Property getProperty() {
142                return property;
143        }
144
145        public void setValue(String value) {
146                this.value = value;
147        }
148
149        public void setType(String type) {
150                this.type = type;
151
152        }
153
154        public boolean hasValue() {
155                return value != null;
156        }
157
158        public List<Element> getChildrenByName(String name) {
159                List<Element> res = new ArrayList<Element>();
160                if (hasChildren()) {
161                        for (Element child : children)
162                                if (name.equals(child.getName()))
163                                        res.add(child);
164                }
165                return res;
166        }
167
168        public void numberChildren() {
169                if (children == null)
170                        return;
171                
172                String last = "";
173                int index = 0;
174                for (Element child : children) {
175                        if (child.getProperty().isList()) {
176                          if (last.equals(child.getName())) {
177                                index++;
178                          } else {
179                                last = child.getName();
180                                index = 0;
181                          }
182                        child.index = index;
183                        } else {
184                                child.index = -1;
185                        }
186                        child.numberChildren();
187                }       
188        }
189
190        public int getIndex() {
191                return index;
192        }
193
194        public boolean hasIndex() {
195                return index > -1;
196        }
197
198        public void setIndex(int index) {
199                this.index = index;
200        }
201
202        public String getChildValue(String name) {
203                if (children == null)
204                        return null;
205                for (Element child : children) {
206                        if (name.equals(child.getName()))
207                                return child.getValue();
208                }
209        return null;
210        }
211
212  public void setChildValue(String name, String value) {
213    if (children == null)
214      children = new ArrayList<Element>();
215    for (Element child : children) {
216      if (name.equals(child.getName())) {
217        if (!child.isPrimitive())
218          throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")");
219        child.setValue(value);
220      }
221    }
222    try {
223      setProperty(name.hashCode(), name, new StringType(value));
224    } catch (FHIRException e) {
225      throw new Error(e);
226    }
227  }
228
229        public List<Element> getChildren(String name) {
230                List<Element> res = new ArrayList<Element>(); 
231                if (children != null)
232                for (Element child : children) {
233                        if (name.equals(child.getName()))
234                                res.add(child);
235                }
236                return res;
237        }
238
239  public boolean hasType() {
240    if (type == null)
241      return property.hasType(name);
242    else
243      return true;
244  }
245
246  @Override
247  public String fhirType() {
248    return getType();
249  }
250
251  @Override
252        public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
253        if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) {
254//              String tn = getType();
255//              throw new Error(tn+" not done yet");
256          Base[] b = new Base[1];
257          b[0] = new StringType(value);
258          return b;
259        }
260                
261        List<Base> result = new ArrayList<Base>();
262        if (children != null) {
263        for (Element child : children) {
264                if (child.getName().equals(name))
265                        result.add(child);
266                if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]"))
267                        result.add(child);
268        }
269        }
270        if (result.isEmpty() && checkValid) {
271//              throw new FHIRException("not determined yet");
272        }
273        return result.toArray(new Base[result.size()]);
274        }
275
276        @Override
277        protected void listChildren(List<org.hl7.fhir.dstu3.model.Property> childProps) {
278          if (children != null) {
279            for (Element c : children) {
280              childProps.add(new org.hl7.fhir.dstu3.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c));
281            }
282          }
283        }
284        
285  @Override
286  public Base setProperty(int hash, String name, Base value) throws FHIRException {
287    if (isPrimitive() && (hash == "value".hashCode())) {
288      this.value = castToString(value).asStringValue();
289      return this;
290    }
291    if ("xhtml".equals(getType()) && (hash == "value".hashCode())) {
292      this.xhtml = castToXhtml(value);
293      this.value =  castToXhtmlString(value);
294      return this;
295    }
296    
297    if (!value.isPrimitive() && !(value instanceof Element)) {
298      if (isDataType(value)) 
299        value = convertToElement(property.getChild(name), value);
300      else
301        throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type");
302    }
303    
304    if (children == null)
305      children = new ArrayList<Element>();
306    Element childForValue = null;
307    
308    // look through existing children
309    for (Element child : children) {
310      if (child.getName().equals(name)) {
311        if (!child.isList()) {
312          childForValue = child;
313          break;
314        } else {
315          Element ne = new Element(child);
316          children.add(ne);
317          numberChildren();
318          childForValue = ne;
319          break;
320        }
321      }
322    }
323
324    if (childForValue == null)
325      for (Property p : property.getChildProperties(this.name, type)) {
326        if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
327          Element ne = new Element(name, p);
328          children.add(ne);
329          childForValue = ne;
330          break;
331        }
332      }
333    
334    if (childForValue == null)
335      throw new Error("Cannot set property "+name+" on "+this.name);
336    else if (value.isPrimitive()) {
337      if (childForValue.property.getName().endsWith("[x]"))
338        childForValue.name = name+Utilities.capitalize(value.fhirType());
339      childForValue.setValue(value.primitiveValue());
340    } else {
341      Element ve = (Element) value;
342      childForValue.type = ve.getType();
343      if (childForValue.property.getName().endsWith("[x]"))
344        childForValue.name = name+Utilities.capitalize(childForValue.type);
345      else if (value.isResource()) {
346        if (childForValue.elementProperty == null)
347          childForValue.elementProperty = childForValue.property;
348        childForValue.property = ve.property;
349        childForValue.special = SpecialElement.BUNDLE_ENTRY;
350      }
351      if (ve.children != null) {
352        if (childForValue.children == null)
353          childForValue.children = new ArrayList<Element>();
354        else 
355          childForValue.children.clear();
356        childForValue.children.addAll(ve.children);
357      }
358    }
359    return childForValue;
360  }
361
362  private Base convertToElement(Property prop, Base v) throws FHIRException {
363    return new ObjectConverter(property.getContext()).convert(prop, (Type) v);
364  }
365
366  private boolean isDataType(Base v) {
367    return v instanceof Type &&  property.getContext().getTypeNames().contains(v.fhirType());
368  }
369
370  @Override
371  public Base makeProperty(int hash, String name) throws FHIRException {
372    if (isPrimitive() && (hash == "value".hashCode())) {
373      return new StringType(value);
374    }
375
376    if (children == null)
377      children = new ArrayList<Element>();
378    
379    // look through existing children
380    for (Element child : children) {
381      if (child.getName().equals(name)) {
382        if (!child.isList()) {
383          return child;
384        } else {
385          Element ne = new Element(child);
386          children.add(ne);
387          numberChildren();
388          return ne;
389        }
390      }
391    }
392
393    for (Property p : property.getChildProperties(this.name, type)) {
394      if (p.getName().equals(name)) {
395        Element ne = new Element(name, p);
396        children.add(ne);
397        return ne;
398      }
399    }
400      
401    throw new Error("Unrecognised name "+name+" on "+this.name); 
402  }
403  
404        private int maxToInt(String max) {
405    if (max.equals("*"))
406      return Integer.MAX_VALUE;
407    else
408      return Integer.parseInt(max);
409        }
410
411        @Override
412        public boolean isPrimitive() {
413                return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name));
414        }
415        
416  @Override
417  public boolean isResource() {
418    return property.isResource();
419  }
420  
421
422        @Override
423        public boolean hasPrimitiveValue() {
424                return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name);
425        }
426        
427
428        @Override
429        public String primitiveValue() {
430                if (isPrimitive())
431                  return value;
432                else {
433                        if (hasPrimitiveValue() && children != null) {
434                                for (Element c : children) {
435                                        if (c.getName().equals("value"))
436                                                return c.primitiveValue();
437                                }
438                        }
439                        return null;
440                }
441        }
442        
443        // for the validator
444  public int line() {
445    return line;
446  }
447
448  public int col() {
449    return col;
450  }
451
452        public Element markLocation(int line, int col) {
453                this.line = line;
454                this.col = col; 
455                return this;
456        }
457
458        public void markValidation(StructureDefinition profile, ElementDefinition definition) {
459        }
460        
461  public Element getNamedChild(String name) {
462          if (children == null)
463                return null;
464          Element result = null;
465          for (Element child : children) {
466                if (child.getName().equals(name)) {
467                        if (result == null)
468                                result = child;
469                        else 
470                                throw new Error("Attempt to read a single element when there is more than one present ("+name+")");
471                }
472          }
473          return result;
474        }
475
476  public void getNamedChildren(String name, List<Element> list) {
477        if (children != null)
478                for (Element child : children) 
479                        if (child.getName().equals(name))
480                                list.add(child);
481  }
482
483  public String getNamedChildValue(String name) {
484        Element child = getNamedChild(name);
485        return child == null ? null : child.value;
486  }
487
488  public void getNamedChildrenWithWildcard(String string, List<Element> values) {
489          Validate.isTrue(string.endsWith("[x]"));
490          
491          String start = string.substring(0, string.length() - 3);
492                if (children != null) {
493                        for (Element child : children) { 
494                                if (child.getName().startsWith(start)) {
495                                        values.add(child);
496                                }
497                        }
498                }
499  }
500
501  
502        public XhtmlNode getXhtml() {
503                return xhtml;
504        }
505
506        public Element setXhtml(XhtmlNode xhtml) {
507                this.xhtml = xhtml;
508                return this;
509        }
510
511        @Override
512        public boolean isEmpty() {
513                if (isNotBlank(value)) {
514                        return false;
515                }
516                for (Element next : getChildren()) {
517                        if (!next.isEmpty()) {
518                                return false;
519                        }
520                }
521                return true;
522        }
523
524  public Property getElementProperty() {
525    return elementProperty;
526  }
527
528  public boolean hasElementProperty() {
529    return elementProperty != null;
530  }
531
532  public boolean hasChild(String name) {
533    return getNamedChild(name) != null;
534  }
535
536  @Override
537  public String toString() {
538    return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
539  }
540
541  @Override
542  public String getIdBase() {
543    return getChildValue("id");
544  }
545
546  @Override
547  public void setIdBase(String value) {
548    setChildValue("id", value);
549  }
550
551
552//  @Override
553//  public boolean equalsDeep(Base other) {
554//    if (!super.equalsDeep(other))
555//      return false;
556//    
557//  }
558//
559//  @Override
560//  public boolean equalsShallow(Base other) {
561//    if (!super.equalsShallow(other))
562//      return false;
563//  }
564
565  public Type asType() throws FHIRException {
566    return new ObjectConverter(property.getContext()).convertToType(this);
567  }
568
569  @Override
570  public boolean isMetadataBased() {
571    return true;
572  }
573
574  public boolean isList() {
575    if (elementProperty != null)
576      return elementProperty.isList();
577    else
578      return property.isList();
579  }
580  
581  @Override
582  public String[] getTypesForProperty(int hash, String name) throws FHIRException {
583    Property p = property.getChildSimpleName(this.name, name);
584    if (p != null) {
585      Set<String> types = new HashSet<String>();
586      for (TypeRefComponent tr : p.getDefinition().getType()) {
587        types.add(tr.getCode());
588      }
589      return types.toArray(new String[]{});
590    }
591    return super.getTypesForProperty(hash, name);
592
593  }
594
595  public void sort() {
596    if (children != null) {
597      List<Element> remove = new ArrayList<Element>();
598      for (Element child : children) {
599        child.sort();
600        if (child.isEmpty())
601          remove.add(child);
602      }
603      children.removeAll(remove);
604      Collections.sort(children, new ElementSortComparator(this, this.property));
605    }
606  }
607
608  public class ElementSortComparator implements Comparator<Element> {
609    private List<ElementDefinition> children;
610    public ElementSortComparator(Element e, Property property) {
611      String tn = e.getType();
612      StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+tn);
613      if (sd != null && !sd.getAbstract())
614        children = sd.getSnapshot().getElement();
615      else
616        children = property.getStructure().getSnapshot().getElement();
617    }
618    
619    @Override
620    public int compare(Element e0, Element e1) {
621      int i0 = find(e0);
622      int i1 = find(e1);
623      return (i0 < i1) ? -1 : ((i0 == i1) ? 0 : 1);
624    }
625    
626    private int find(Element e0) {
627      int i =  e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) :  children.indexOf(e0.property.getDefinition());
628      return i; 
629    }
630
631  }
632
633}