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