001package org.hl7.fhir.r5.elementmodel;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033import java.util.ArrayList;
034import java.util.Collections;
035import java.util.Comparator;
036import java.util.HashMap;
037import java.util.HashSet;
038import java.util.List;
039import java.util.Map;
040import java.util.Set;
041
042import org.apache.commons.lang3.Validate;
043import org.hl7.fhir.exceptions.FHIRException;
044import org.hl7.fhir.r5.conformance.ProfileUtilities;
045import org.hl7.fhir.r5.model.Base;
046import org.hl7.fhir.r5.model.DataType;
047import org.hl7.fhir.r5.model.ElementDefinition;
048import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
049import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
050import org.hl7.fhir.r5.model.ICoding;
051import org.hl7.fhir.r5.model.StringType;
052import org.hl7.fhir.r5.model.StructureDefinition;
053import org.hl7.fhir.r5.model.TypeConvertor;
054import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
055import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
056import org.hl7.fhir.utilities.ElementDecoration;
057import org.hl7.fhir.utilities.ElementDecoration.DecorationType;
058import org.hl7.fhir.utilities.Utilities;
059import org.hl7.fhir.utilities.validation.ValidationMessage;
060import org.hl7.fhir.utilities.xhtml.XhtmlNode;
061
062/**
063 * This class represents the underlying reference model of FHIR
064 * 
065 * A resource is nothing but a set of elements, where every element has a 
066 * name, maybe a stated type, maybe an id, and either a value or child elements 
067 * (one or the other, but not both or neither)
068 * 
069 * @author Grahame Grieve
070 *
071 */
072public class Element extends Base {
073
074
075  public enum SpecialElement {
076                CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, PARAMETER;
077
078    public static SpecialElement fromProperty(Property property) {
079      if (property.getStructure().getType().equals("Parameters"))
080        return PARAMETER;
081      if (property.getStructure().getType().equals("Bundle") && property.getName().equals("resource"))
082        return BUNDLE_ENTRY;
083      if (property.getStructure().getType().equals("Bundle") && property.getName().equals("outcome"))
084        return BUNDLE_OUTCOME;
085      if (property.getName().equals("contained")) 
086        return CONTAINED;
087      throw new FHIRException("Unknown resource containing a native resource: "+property.getDefinition().getId());
088    }
089
090    public String toHuman() {
091      switch (this) {
092      case BUNDLE_ENTRY: return "entry";
093      case BUNDLE_OUTCOME: return "outcome";
094      case CONTAINED: return "contained";
095      case PARAMETER: return "parameter";
096      default: return "??";        
097      }
098    }
099        }
100
101        private List<String> comments;// not relevant for production, but useful in documentation
102        private String name;
103        private String type;
104        private String value;
105        private int index = -1;
106        private List<Element> children;
107        private Property property;
108  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
109        private int line;
110        private int col;
111        private SpecialElement special;
112        private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation
113        private String explicitType; // for xsi:type attribute
114        private Element parentForValidator;
115        private boolean hasParentForValidator;
116        private String path;
117        private List<ValidationMessage> messages;
118        private boolean prohibited;
119        private boolean required;
120  private Map<String, List<Element>> childMap;
121  private int descendentCount;
122  private int instanceId;
123
124        public Element(String name) {
125                super();
126                this.name = name;
127        }
128
129  public Element(Element other) {
130    super();
131    name = other.name;
132    type = other.type;
133    property = other.property;
134    elementProperty = other.elementProperty;
135    special = other.special;
136  }
137  
138  public Element(String name, Property property) {
139                super();
140                this.name = name;
141                this.property = property;
142        }
143
144        public Element(String name, Property property, String type, String value) {
145                super();
146                this.name = name;
147                this.property = property;
148                this.type = type;
149                this.value = value;
150        }
151
152        public void updateProperty(Property property, SpecialElement special, Property elementProperty) {
153                this.property = property;
154    this.elementProperty = elementProperty;
155                this.special = special;
156        }
157
158        public SpecialElement getSpecial() {
159                return special;
160        }
161
162        public String getName() {
163                return name;
164        }
165
166        public String getType() {
167                if (type == null)
168                        return property.getType(name);
169                else
170                  return type;
171        }
172
173        public String getValue() {
174                return value;
175        }
176
177        public boolean hasChildren() {
178                return !(children == null || children.isEmpty());
179        }
180
181        public List<Element> getChildren() {
182                if (children == null)
183                        children = new ArrayList<Element>();
184                return children;
185        }
186
187        public boolean hasComments() {
188                return !(comments == null || comments.isEmpty());
189        }
190
191        public List<String> getComments() {
192                if (comments == null)
193                        comments = new ArrayList<String>();
194                return comments;
195        }
196
197        public Property getProperty() {
198                return property;
199        }
200
201        public void setValue(String value) {
202                this.value = value;
203        }
204
205        public void setType(String type) {
206                this.type = type;
207
208        }
209
210        public boolean hasValue() {
211                return value != null;
212        }
213
214        public List<Element> getChildrenByName(String name) {
215                List<Element> res = new ArrayList<Element>();
216                if (children.size() > 20) {
217      populateChildMap();
218      List<Element> l = childMap.get(name);
219      if (l != null) {
220        res.addAll(l);
221      }
222    } else {
223                if (hasChildren()) {
224                        for (Element child : children)
225                                if (name.equals(child.getName()))
226                                        res.add(child);
227                }
228    }
229                return res;
230        }
231
232        public void numberChildren() {
233                if (children == null)
234                        return;
235                
236                String last = "";
237                int index = 0;
238                for (Element child : children) {
239                        if (child.getProperty().isList()) {
240                          if (last.equals(child.getName())) {
241                                index++;
242                          } else {
243                                last = child.getName();
244                                index = 0;
245                          }
246                        child.index = index;
247                        } else {
248                                child.index = -1;
249                        }
250                        child.numberChildren();
251                }       
252        }
253
254        public int getIndex() {
255                return index;
256        }
257
258        public boolean hasIndex() {
259                return index > -1;
260        }
261
262        public void setIndex(int index) {
263                this.index = index;
264        }
265
266        public String getChildValue(String name) {
267                if (children == null)
268                        return null;
269                for (Element child : children) {
270                        if (name.equals(child.getName()))
271                                return child.getValue();
272                }
273        return null;
274        }
275
276  public void setChildValue(String name, String value) {
277    if (children == null)
278      children = new ArrayList<Element>();
279    for (Element child : children) {
280      if (name.equals(child.getName())) {
281        if (!child.isPrimitive())
282          throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")");
283        child.setValue(value);
284      }
285    }
286    childMap = null;
287    try {
288      setProperty(name.hashCode(), name, new StringType(value));
289    } catch (FHIRException e) {
290      throw new Error(e);
291    }
292  }
293
294  public List<Element> getChildren(String name) {
295    List<Element> res = new ArrayList<Element>(); 
296    if (children.size() > 20) {
297      populateChildMap();
298      List<Element> l = childMap.get(name);
299      if (l != null) {
300        res.addAll(l);
301      }
302    } else {
303      if (children != null)
304        for (Element child : children) {
305          if (name.equals(child.getName()))
306            res.add(child);
307        }
308    }
309                return res;
310        }
311
312  public boolean hasType() {
313    if (type == null)
314      return property.hasType(name);
315    else
316      return true;
317  }
318
319  @Override
320  public String fhirType() {
321    return getType();
322  }
323
324  @Override
325        public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
326        if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) {
327//              String tn = getType();
328//              throw new Error(tn+" not done yet");
329          Base[] b = new Base[1];
330          b[0] = new StringType(value);
331          return b;
332        }
333                
334        List<Base> result = new ArrayList<Base>();
335        if (children != null) {
336          if (children.size() > 20) {
337            populateChildMap();
338        List<Element> l = childMap.get(name);
339        if (l != null) {
340          result.addAll(l);
341        }
342          } else {
343        for (Element child : children) {
344                if (child.getName().equals(name)) {
345                            result.add(child);
346                }
347                if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]")) {
348                        result.add(child);
349                }
350        }
351          }
352        }
353        if (result.isEmpty() && checkValid) {
354//              throw new FHIRException("not determined yet");
355        }
356        return result.toArray(new Base[result.size()]);
357        }
358
359  private void populateChildMap() {
360    if (childMap == null) {
361      childMap = new HashMap<>();
362      for (Element child : children) {
363        String n;
364        if (child.getProperty().getName().endsWith("[x]")) {
365          n = child.getProperty().getName();
366          n = n.substring(0, n.length()-3);
367        } else {
368          n = child.getName();
369        }
370        List<Element> l = childMap.get(n);
371        if (l == null) {
372          l = new ArrayList<Element>();
373          childMap.put(n,l);
374        }
375        l.add(child);
376      }               
377    }
378  }
379
380        @Override
381        protected void listChildren(List<org.hl7.fhir.r5.model.Property> childProps) {
382          if (children != null) {
383            Map<String, org.hl7.fhir.r5.model.Property> map = new HashMap<String, org.hl7.fhir.r5.model.Property>();
384            for (Element c : children) {
385              org.hl7.fhir.r5.model.Property p = map.get(c.getName());
386              if (p == null) {
387              p = new org.hl7.fhir.r5.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c);
388          childProps.add(p);
389          map.put(c.getName(), p);
390              
391              } else
392                p.getValues().add(c);
393            }
394          }
395        }
396        
397  @Override
398  public Base setProperty(int hash, String name, Base value) throws FHIRException {
399    if ("xhtml".equals(getType()) && (hash == "value".hashCode())) {
400      this.xhtml = TypeConvertor.castToXhtml(value);
401      this.value =  TypeConvertor.castToXhtmlString(value);
402      return this;
403    }
404    if (isPrimitive() && (hash == "value".hashCode())) {
405      this.value = TypeConvertor.castToString(value).asStringValue();
406      return this;
407    }
408    
409    if (!value.isPrimitive() && !(value instanceof Element)) {
410      if (isDataType(value)) 
411        value = convertToElement(property.getChild(name), value);
412      else
413        throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type");
414    }
415    
416    childMap = null;
417    if (children == null)
418      children = new ArrayList<Element>();
419    Element childForValue = null;
420    
421    // look through existing children
422    for (Element child : children) {
423      if (child.getName().equals(name)) {
424        if (!child.isList()) {
425          childForValue = child;
426          break;
427        } else {
428          Element ne = new Element(child);
429          children.add(ne);
430          numberChildren();
431          childForValue = ne;
432          break;
433        }
434      }
435    }
436
437    int i = 0;
438    if (childForValue == null)
439      for (Property p : property.getChildProperties(this.name, type)) {
440        int t = -1;
441        for (int c =0; c < children.size(); c++) {
442          Element e = children.get(c);
443          if (p.getName().equals(e.getName()))
444            t = c;
445        }
446        if (t >= i)
447          i = t+1;
448        if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
449          Element ne = new Element(name, p);
450          children.add(i, ne);
451          childForValue = ne;
452          break;
453        }
454      }
455    
456    if (childForValue == null)
457      throw new Error("Cannot set property "+name+" on "+this.name);
458    else if (value.isPrimitive()) {
459      if (childForValue.property.getName().endsWith("[x]"))
460        childForValue.name = name+Utilities.capitalize(value.fhirType());
461      childForValue.setValue(value.primitiveValue());
462    } else {
463      Element ve = (Element) value;
464      childForValue.type = ve.getType();
465      if (childForValue.property.getName().endsWith("[x]"))
466        childForValue.name = name+Utilities.capitalize(childForValue.type);
467      else if (value.isResource()) {
468        if (childForValue.elementProperty == null)
469          childForValue.elementProperty = childForValue.property;
470        childForValue.property = ve.property;
471        childForValue.special = SpecialElement.BUNDLE_ENTRY;
472      }
473      if (ve.children != null) {
474        if (childForValue.children == null)
475          childForValue.children = new ArrayList<Element>();
476        else 
477          childForValue.children.clear();
478        childForValue.children.addAll(ve.children);
479      }
480    }
481    return childForValue;
482  }
483
484  private Base convertToElement(Property prop, Base v) throws FHIRException {
485    return new ObjectConverter(property.getContext()).convert(prop, (DataType) v);
486  }
487
488  private boolean isDataType(Base v) {
489    return v instanceof DataType &&  property.getContext().getTypeNames().contains(v.fhirType());
490  }
491
492  @Override
493  public Base makeProperty(int hash, String name) throws FHIRException {
494    if (isPrimitive() && (hash == "value".hashCode())) {
495      return new StringType(value);
496    }
497
498    if (children == null)
499      children = new ArrayList<Element>();
500    
501    // look through existing children
502    for (Element child : children) {
503      if (child.getName().equals(name)) {
504        if (!child.isList()) {
505          return child;
506        } else {
507          Element ne = new Element(child);
508          children.add(ne);
509          numberChildren();
510          return ne;
511        }
512      }
513    }
514
515    for (Property p : property.getChildProperties(this.name, type)) {
516      if (p.getName().equals(name)) {
517        Element ne = new Element(name, p);
518        children.add(ne);
519        return ne;
520      }
521    }
522      
523    throw new Error("Unrecognised name "+name+" on "+this.name); 
524  }
525  
526        private int maxToInt(String max) {
527    if (max.equals("*"))
528      return Integer.MAX_VALUE;
529    else
530      return Integer.parseInt(max);
531        }
532
533        @Override
534        public boolean isPrimitive() {
535                return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name));
536        }
537        
538  @Override
539  public boolean isBooleanPrimitive() {
540    return isPrimitive() && ("boolean".equals(type) || "boolean".equals(property.getType(name)));
541  }
542 
543  @Override
544  public boolean isResource() {
545    return property.isResource();
546  }
547  
548
549        @Override
550        public boolean hasPrimitiveValue() {
551                return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name);
552        }
553        
554
555        @Override
556        public String primitiveValue() {
557                if (isPrimitive())
558                  return value;
559                else {
560                        if (hasPrimitiveValue() && children != null) {
561                                for (Element c : children) {
562                                        if (c.getName().equals("value"))
563                                                return c.primitiveValue();
564                                }
565                        }
566                        return null;
567                }
568        }
569        
570        // for the validator
571  public int line() {
572    return line;
573  }
574
575  public int col() {
576    return col;
577  }
578
579        public Element markLocation(int line, int col) {
580                this.line = line;
581                this.col = col; 
582                return this;
583        }
584
585        public void clearDecorations() {
586          clearUserData("fhir.decorations");
587          for (Element e : children) {
588            e.clearDecorations();         
589          }
590    childMap = null;
591        }
592        
593        public void markValidation(StructureDefinition profile, ElementDefinition definition) {
594          @SuppressWarnings("unchecked")
595    List<ElementDecoration> decorations = (List<ElementDecoration>) getUserData("fhir.decorations");
596          if (decorations == null) {
597            decorations = new ArrayList<>();
598            setUserData("fhir.decorations", decorations);
599          }
600          decorations.add(new ElementDecoration(DecorationType.TYPE, profile.getUserString("path"), definition.getPath()));
601          if (definition.getId() != null && tail(definition.getId()).contains(":")) {
602            String[] details = tail(definition.getId()).split(":");
603            decorations.add(new ElementDecoration(DecorationType.SLICE, null, details[1]));
604          }
605        }
606        
607  private String tail(String id) {
608    return id.contains(".") ? id.substring(id.lastIndexOf(".")+1) : id;
609  }
610
611  public Element getNamedChild(String name) {
612    if (children == null)
613      return null;
614    if (children.size() > 20) {
615      populateChildMap();
616      List<Element> l = childMap.get(name);
617      if (l == null) {
618        // try the other way (in case of complicated naming rules)
619      } else if (l.size() > 1) {
620        throw new Error("Attempt to read a single element when there is more than one present ("+name+")");
621      } else {
622        return l.get(0);
623      }
624    } else {
625      
626    }
627    Element result = null;
628    
629    for (Element child : children) {
630      if (child.getName() != null && name != null && child.getProperty() != null && child.getProperty().getDefinition() != null && child.fhirType() != null) {
631        if (child.getName().equals(name) || (child.getName().length() >  child.fhirType().length() && child.getName().substring(0, child.getName().length() - child.fhirType().length()).equals(name) && child.getProperty().getDefinition().isChoice())) {
632          if (result == null)
633            result = child;
634          else 
635            throw new Error("Attempt to read a single element when there is more than one present ("+name+")");
636        }
637      }
638    }
639          return result;
640        }
641
642  public void getNamedChildren(String name, List<Element> list) {
643        if (children != null)
644          if (children.size() > 20) {
645        populateChildMap();
646        List<Element> l = childMap.get(name);
647        if (l != null) {
648          list.addAll(l);
649        }
650      } else {
651                  for (Element child : children) 
652                          if (child.getName().equals(name))
653                                  list.add(child);
654      }
655  }
656
657  public String getNamedChildValue(String name) {
658        Element child = getNamedChild(name);
659        return child == null ? null : child.value;
660  }
661
662  public void getNamedChildrenWithWildcard(String string, List<Element> values) {
663          Validate.isTrue(string.endsWith("[x]"));
664          
665          String start = string.substring(0, string.length() - 3);
666                if (children != null) {
667                        for (Element child : children) { 
668                                if (child.getName().startsWith(start)) {
669                                        values.add(child);
670                                }
671                        }
672                }
673  }
674
675  
676        public XhtmlNode getXhtml() {
677                return xhtml;
678        }
679
680        public Element setXhtml(XhtmlNode xhtml) {
681                this.xhtml = xhtml;
682                return this;
683        }
684
685        @Override
686        public boolean isEmpty() {
687        // GG: this used to also test !"".equals(value). 
688    // the condition where "" is empty and there are no children is an error, and so this really only manifested as an issue in corner cases technical testing of the validator / FHIRPath.
689          // it should not cause any problems in real life.
690                if (value != null) {   
691                        return false;
692                }
693                for (Element next : getChildren()) {
694                        if (!next.isEmpty()) {
695                                return false;
696                        }
697                }
698                return true;
699        }
700
701  public Property getElementProperty() {
702    return elementProperty;
703  }
704
705  public boolean hasElementProperty() {
706    return elementProperty != null;
707  }
708
709  public boolean hasChild(String name) {
710    return getNamedChild(name) != null;
711  }
712
713  public boolean hasChildren(String name) {
714    if (children != null)
715      for (Element child : children) 
716        if (child.getName().equals(name))
717          return true;
718    return false;
719  }
720
721  @Override
722  public String toString() {
723    if (name.equals(fhirType()) && isResource()) {
724      return fhirType()+"/"+getIdBase() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
725      
726    } else if (isResource()) {
727      return name+"="+fhirType()+"/"+getIdBase()+ "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
728    } else {
729      return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
730    }
731  }
732
733  @Override
734  public String getIdBase() {
735    return getChildValue("id");
736  }
737
738  @Override
739  public void setIdBase(String value) {
740    setChildValue("id", value);
741  }
742
743
744  @Override
745  public boolean equalsDeep(Base other) {
746    if (!super.equalsDeep(other))
747      return false;
748    if (isPrimitive() && other.isPrimitive())
749      return primitiveValue().equals(other.primitiveValue());
750    if (isPrimitive() || other.isPrimitive())
751      return false;
752    Set<String> processed  = new HashSet<String>();
753    for (org.hl7.fhir.r5.model.Property p : children()) {
754      String name = p.getName();
755      processed.add(name);
756      org.hl7.fhir.r5.model.Property o = other.getChildByName(name);
757      if (!equalsDeep(p, o))
758        return false;
759    }
760    for (org.hl7.fhir.r5.model.Property p : children()) {
761      String name = p.getName();
762      if (!processed.contains(name)) {
763        org.hl7.fhir.r5.model.Property o = other.getChildByName(name);
764        if (!equalsDeep(p, o))
765          return false;
766      }
767    }
768    return true;
769  }
770
771  private boolean equalsDeep(org.hl7.fhir.r5.model.Property p, org.hl7.fhir.r5.model.Property o) {
772    if (o == null || p == null)
773      return false;
774    if (p.getValues().size() != o.getValues().size())
775      return false;
776    for (int i = 0; i < p.getValues().size(); i++)
777      if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true))
778        return false;
779    return true;
780  }
781
782  @Override
783  public boolean equalsShallow(Base other) {
784    if (!super.equalsShallow(other))
785      return false;
786    if (isPrimitive() && other.isPrimitive())
787      return primitiveValue().equals(other.primitiveValue());
788    if (isPrimitive() || other.isPrimitive())
789      return false;
790    return true; //?
791  }
792
793  public DataType asType() throws FHIRException {
794    return new ObjectConverter(property.getContext()).convertToType(this);
795  }
796
797  @Override
798  public boolean isMetadataBased() {
799    return true;
800  }
801
802  public boolean isList() {
803    if (elementProperty != null)
804      return elementProperty.isList();
805    else
806      return property.isList();
807  }
808  
809  public boolean isBaseList() {
810    if (elementProperty != null)
811      return elementProperty.isBaseList();
812    else
813      return property.isBaseList();
814  }
815  
816  @Override
817  public String[] getTypesForProperty(int hash, String name) throws FHIRException {
818    Property p = property.getChildSimpleName(this.name, name);
819    if (p != null) {
820      Set<String> types = new HashSet<String>();
821      for (TypeRefComponent tr : p.getDefinition().getType()) {
822        types.add(tr.getCode());
823      }
824      return types.toArray(new String[]{});
825    }
826    return super.getTypesForProperty(hash, name);
827
828  }
829
830  public void sort() {
831    if (children != null) {
832      List<Element> remove = new ArrayList<Element>();
833      for (Element child : children) {
834        child.sort();
835        if (child.isEmpty())
836          remove.add(child);
837      }
838      children.removeAll(remove);
839      Collections.sort(children, new ElementSortComparator(this, this.property));
840      childMap = null;
841    }
842  }
843
844  public class ElementSortComparator implements Comparator<Element> {
845    private List<ElementDefinition> children;
846    public ElementSortComparator(Element e, Property property) {
847      String tn = e.getType();
848      StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, property.getContext().getOverrideVersionNs()));
849      if (sd != null && !sd.getAbstract())
850        children = sd.getSnapshot().getElement();
851      else
852        children = property.getStructure().getSnapshot().getElement();
853    }
854    
855    @Override
856    public int compare(Element e0, Element e1) {
857      int i0 = find(e0);
858      int i1 = find(e1);
859      return Integer.compare(i0, i1);
860    }
861    private int find(Element e0) {
862      int i =  e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) :  children.indexOf(e0.property.getDefinition());
863      return i; 
864    }
865
866  }
867
868  public class ICodingImpl implements ICoding {
869    private String system;
870    private String version;
871    private String code;
872    private String display;
873    private boolean doesSystem;
874    private boolean doesVersion;
875    private boolean doesCode;
876    private boolean doesDisplay;
877    public ICodingImpl(boolean doesCode, boolean doesSystem, boolean doesVersion, boolean doesDisplay) {
878      super();
879      this.doesCode = doesCode;
880      this.doesSystem = doesSystem;
881      this.doesVersion = doesVersion;
882      this.doesDisplay = doesDisplay;
883    }
884    public String getSystem() {
885      return system;
886    }
887    public String getVersion() {
888      return version;
889    }
890    public String getCode() {
891      return code;
892    }
893    public String getDisplay() {
894      return display;
895    }
896    public boolean hasSystem() {
897      return !Utilities.noString(system); 
898    }
899    public boolean hasVersion() {
900      return !Utilities.noString(version);
901    }
902    public boolean hasCode() {
903      return !Utilities.noString(code);
904    }
905    public boolean hasDisplay() {
906      return !Utilities.noString(display);
907    }
908    public boolean supportsSystem() {
909      return doesSystem;
910    }
911    public boolean supportsVersion() {
912      return doesVersion;
913    }
914    public boolean supportsCode() {
915      return doesCode;
916    }
917    public boolean supportsDisplay() {
918      return doesDisplay;
919    }    
920  }
921
922  public ICoding getAsICoding() throws FHIRException {
923    if ("code".equals(fhirType())) {
924      if (property.getDefinition().getBinding().getStrength() != BindingStrength.REQUIRED)
925        return null;
926      ICodingImpl c = new ICodingImpl(true, true, false, false);
927      c.code = primitiveValue();
928      ValueSetExpansionOutcome vse = property.getContext().expandVS(property.getDefinition().getBinding(), true, false);
929      if (vse.getValueset() == null)
930        return null;
931      for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
932        if (cc.getCode().equals(c.code)) {
933          c.system = cc.getSystem();
934          if (cc.hasVersion()) {
935            c.doesVersion = true;
936            c.version = cc.getVersion();
937          }
938          if (cc.hasDisplay()) {
939            c.doesDisplay = true;
940            c.display = cc.getDisplay();
941          }
942        }
943      }
944      if (c.system == null)
945        return null;
946      return c;   
947    } else if ("Coding".equals(fhirType())) {
948      ICodingImpl c = new ICodingImpl(true, true, true, true);
949      c.system = getNamedChildValue("system");
950      c.code = getNamedChildValue("code");
951      c.display = getNamedChildValue("display");
952      c.version = getNamedChildValue("version");
953      return c;
954    } else if ("Quantity".equals(fhirType())) {
955      ICodingImpl c = new ICodingImpl(true, true, false, false);
956      c.system = getNamedChildValue("system");
957      c.code = getNamedChildValue("code");
958      return c;
959    } else 
960      return null;
961  }
962
963  public String getExplicitType() {
964    return explicitType;
965  }
966
967  public void setExplicitType(String explicitType) {
968    this.explicitType = explicitType;
969  }
970
971  public boolean hasDescendant(Element element) {
972    if (children != null) {
973      for (Element child : children) {
974        if (element == child || child.hasDescendant(element)) {
975          return true;        
976        }
977      }
978    }
979    return false;
980  }
981
982  public Element getExtension(String url) {
983    if (children != null) {
984      for (Element child : children) {
985        if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
986          String u = child.getChildValue("url");
987          if (url.equals(u)) {
988            return child;
989          }
990        }
991      }
992    }
993    return null;
994  }
995
996  public Base getExtensionValue(String url) {
997    if (children != null) {
998      for (Element child : children) {
999        if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
1000          String u = child.getChildValue("url");
1001          if (url.equals(u)) {
1002            return child.getNamedChild("value");
1003          }
1004        }
1005      }
1006    }
1007    return null;
1008  }
1009
1010  public boolean hasExtension(String url) {
1011    if (children != null) {
1012      for (Element child : children) {
1013        if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
1014          String u = child.getChildValue("url");
1015          if (url.equals(u)) {
1016            return true;
1017          }
1018        }
1019      }
1020    }
1021    return false;
1022  }
1023
1024  /**
1025   * this is set by the instance validator. There's no reason to maintain this when working with an element tree, and so it should be ignored outside the validator
1026   */
1027  public Element getParentForValidator() {
1028    if (!hasParentForValidator) {
1029      throw new Error("Parent not set");
1030    }
1031    return parentForValidator;
1032  }
1033
1034  public void setParentForValidator(Element parentForValidator) {
1035    this.parentForValidator = parentForValidator;
1036    this.hasParentForValidator = true;
1037  }
1038  
1039  public boolean hasParentForValidator() {
1040    return hasParentForValidator;
1041  }
1042
1043  public void clear() {
1044    comments = null;
1045    children.clear();
1046    childMap = null;
1047    property = null;
1048    elementProperty = null;
1049    xhtml = null;
1050    path = null;
1051  }
1052
1053  public String getPath() {
1054    return path;
1055  }
1056
1057  public void setPath(String path) {
1058    this.path = path;
1059  }  
1060  
1061  public void addMessage(ValidationMessage vm) {
1062    if (messages == null) {
1063      messages = new ArrayList<>();
1064    }
1065    messages.add(vm);
1066  }
1067
1068  public boolean hasMessages() {
1069    return messages != null && !messages.isEmpty();
1070  }
1071
1072  public List<ValidationMessage> getMessages() {
1073    return messages;
1074  }
1075
1076  public void removeChild(String name) {
1077    children.removeIf(n -> name.equals(n.getName()));
1078    childMap = null;
1079  }
1080
1081  public boolean isProhibited() {
1082    return prohibited;
1083  }
1084
1085  public void setProhibited(boolean prohibited) {
1086    this.prohibited = prohibited;
1087  }
1088
1089  public boolean isRequired() {
1090    return required;
1091  }
1092
1093  public void setRequired(boolean required) {
1094    this.required = required;
1095  }
1096
1097  public int getDescendentCount() {
1098    return descendentCount;
1099  }
1100
1101  public void setDescendentCount(int descendentCount) {
1102    this.descendentCount = descendentCount;
1103  }
1104
1105  public int countDescendents() {
1106    if (descendentCount > 0) {
1107      return descendentCount;
1108    } else if (children != null) {
1109      descendentCount = children.size();
1110      for (Element e : children) {
1111        descendentCount = descendentCount + e.countDescendents();
1112      }
1113    } else {
1114      descendentCount = 0;
1115    }
1116    return descendentCount;
1117  }
1118
1119  public int getInstanceId() {
1120    return instanceId;
1121  }
1122
1123  public void setInstanceId(int instanceId) {
1124    this.instanceId = instanceId;
1125  }
1126  
1127  
1128}