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.*;
036
037import org.apache.commons.lang3.Validate;
038import org.hl7.fhir.exceptions.FHIRException;
039import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
040import org.hl7.fhir.r5.context.ContextUtilities;
041import org.hl7.fhir.r5.extensions.ExtensionsUtils;
042import org.hl7.fhir.r5.model.Base;
043import org.hl7.fhir.r5.model.DataType;
044import org.hl7.fhir.r5.model.ElementDefinition;
045import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
046import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
047import org.hl7.fhir.r5.model.ICoding;
048import org.hl7.fhir.r5.model.StringType;
049import org.hl7.fhir.r5.model.StructureDefinition;
050import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
051import org.hl7.fhir.r5.model.TypeConvertor;
052import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
053import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
054import org.hl7.fhir.r5.utils.ToolingExtensions;
055import org.hl7.fhir.utilities.ElementDecoration;
056import org.hl7.fhir.utilities.ElementDecoration.DecorationType;
057import org.hl7.fhir.utilities.SourceLocation;
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  private static final HashSet<String> extensionList = new HashSet<>(Arrays.asList("extension", "modifierExtension"));
074
075  public enum SpecialElement {
076                CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, BUNDLE_ISSUES, PARAMETER, LOGICAL;
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.getStructure().getType().equals("Bundle") && property.getName().equals("issues"))
086        return BUNDLE_ISSUES;
087      if (property.getName().equals("contained")) 
088        return CONTAINED;
089      if (property.getStructure().getKind() == StructureDefinitionKind.LOGICAL)
090        return LOGICAL;
091      throw new FHIRException("Unknown resource containing a native resource: "+property.getDefinition().getId());
092    }
093
094    public String toHuman() {
095      switch (this) {
096      case BUNDLE_ENTRY: return "entry";
097      case BUNDLE_OUTCOME: return "outcome";
098      case BUNDLE_ISSUES: return "issues";
099      case CONTAINED: return "contained";
100      case PARAMETER: return "parameter";
101      case LOGICAL: return "logical";
102      default: return "??";        
103      }
104    }
105        }
106
107        private List<String> comments;// not relevant for production, but useful in documentation
108        private String name;
109        private String type;
110        private String value;
111        private int index = -1;
112        private List<Element> children;
113        private Property property;
114  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
115        private int line;
116        private int col;
117        private SpecialElement special;
118        private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation
119        private String explicitType; // for xsi:type attribute
120        private Element parentForValidator;
121        private boolean hasParentForValidator;
122        private String path;
123        private List<ValidationMessage> messages;
124        private boolean prohibited;
125        private boolean required;
126  private Map<String, List<Element>> childMap;
127  private int descendentCount;
128  private int instanceId;
129  private boolean isNull;
130  private Base source;
131  private boolean ignorePropertyOrder;
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                if (property.isResource()) {
152                  children = new ArrayList<>();
153                }
154        }
155
156        public Element(String name, Property property, String type, String value) {
157                super();
158                this.name = name;
159                this.property = property;
160                this.type = type;
161                this.value = value;
162        }
163
164        public void updateProperty(Property property, SpecialElement special, Property elementProperty) {
165                this.property = property;
166    this.elementProperty = elementProperty;
167                this.special = special;
168        }
169
170        public SpecialElement getSpecial() {
171                return special;
172        }
173
174        public String getName() {
175                return name;
176        }
177
178        public String getType() {
179                if (type == null)
180                        return property.getType(name);
181                else
182                  return type;
183        }
184
185        public String getValue() {
186                return value;
187        }
188
189        public boolean hasChildren() {
190                return !(children == null || children.isEmpty());
191        }
192
193        public List<Element> getChildren() {
194                if (children == null)
195                        children = new ArrayList<Element>();
196                return children;
197        }
198
199        public boolean hasComments() {
200                return !(comments == null || comments.isEmpty());
201        }
202
203        public List<String> getComments() {
204                if (comments == null)
205                        comments = new ArrayList<String>();
206                return comments;
207        }
208
209        public Property getProperty() {
210                return property;
211        }
212
213        public void setValue(String value) {
214                this.value = value;
215        }
216
217        public Element setType(String type) {
218                this.type = type;
219                return this;
220
221        }
222
223        public boolean isNull() {
224    return isNull;
225  }
226
227  public void setNull(boolean isNull) {
228    this.isNull = isNull;
229  }
230
231  public boolean hasValue() {
232                return value != null;
233        }
234
235        public List<Element> getChildrenByName(String name) {
236                List<Element> res = new ArrayList<Element>();
237                if (children.size() > 20) {
238      populateChildMap();
239      List<Element> l = childMap.get(name);
240      if (l != null) {
241        res.addAll(l);
242      }
243    } else {
244                if (hasChildren()) {
245                        for (Element child : children)
246                                if (name.equals(child.getName()))
247                                        res.add(child);
248                }
249    }
250                return res;
251        }
252
253        public void numberChildren() {
254                if (children == null)
255                        return;
256                
257                String last = "";
258                int index = 0;
259                for (Element child : children) {
260                        if (child.getProperty().isList()) {
261                          if (last.equals(child.getName())) {
262                                index++;
263                          } else {
264                                last = child.getName();
265                                index = 0;
266                          }
267                        child.index = index;
268                        } else {
269                                child.index = -1;
270                        }
271                        child.numberChildren();
272                }       
273        }
274
275        public int getIndex() {
276                return index;
277        }
278
279        public boolean hasIndex() {
280                return index > -1;
281        }
282
283        public void setIndex(int index) {
284                this.index = index;
285        }
286
287        public String getChildValue(String name) {
288                if (children == null)
289                        return null;
290                for (Element child : children) {
291                        if (name.equals(child.getName()))
292                                return child.getValue();
293                }
294                for (Element child : children) {
295      if (name.equals(child.getNameBase()))
296        return child.getValue();
297    }
298        return null;
299        }
300
301  private String getNameBase() {
302    if (property.isChoice()) {
303      return property.getName().replace("[x]", "");
304    } else  {
305      return getName();
306    }
307  }
308
309  public void setChildValue(String name, String value) {
310    if (children == null)
311      children = new ArrayList<Element>();
312    for (Element child : children) {
313      if (name.equals(child.getName())) {
314        if (!child.isPrimitive())
315          throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")");
316        child.setValue(value);
317      }
318    }
319    childMap = null;
320    try {
321      setProperty(name.hashCode(), name, new StringType(value));
322    } catch (FHIRException e) {
323      throw new Error(e);
324    }
325  }
326
327  public List<Element> getChildren(String name) {
328    List<Element> res = new ArrayList<Element>(); 
329    if (children.size() > 20) {
330      populateChildMap();
331      List<Element> l = childMap.get(name);
332      if (l != null) {
333        res.addAll(l);
334      }
335    } else {
336      if (children != null)
337        for (Element child : children) {
338          if (name.equals(child.getName()))
339            res.add(child);
340        }
341    }
342                return res;
343        }
344
345  public boolean hasType() {
346    if (type == null)
347      return property.hasType(name);
348    else
349      return true;
350  }
351
352  @Override
353  public String fhirType() {
354    return getType();
355  }
356
357  @Override
358        public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
359        if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) {
360//              String tn = getType();
361//              throw new Error(tn+" not done yet");
362          Base[] b = new Base[1];
363          b[0] = new StringType(value);
364          return b;
365        }
366                
367        List<Base> result = new ArrayList<Base>();
368        if (children != null) {
369          if (children.size() > 20) {
370            populateChildMap();
371        List<Element> l = childMap.get(name);
372        if (l != null) {
373          result.addAll(l);
374        }
375          } else {
376        for (Element child : children) {
377                if (child.getName().equals(name)) {
378                            result.add(child);
379                }
380                if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]")) {
381                        result.add(child);
382                }
383        }
384          }
385        }
386        if (result.isEmpty() && checkValid) {
387//              throw new FHIRException("not determined yet");
388        }
389        return result.toArray(new Base[result.size()]);
390        }
391
392  private void populateChildMap() {
393    if (childMap == null) {
394      childMap = new HashMap<>();
395      for (Element child : children) {
396        String n;
397        if (child.getProperty().getName().endsWith("[x]")) {
398          n = child.getProperty().getName();
399          n = n.substring(0, n.length()-3);
400        } else {
401          n = child.getName();
402        }
403        List<Element> l = childMap.get(n);
404        if (l == null) {
405          l = new ArrayList<Element>();
406          childMap.put(n,l);
407        }
408        l.add(child);
409      }               
410    }
411  }
412
413        @Override
414        protected void listChildren(List<org.hl7.fhir.r5.model.Property> childProps) {
415          if (children != null) {
416            Map<String, org.hl7.fhir.r5.model.Property> map = new HashMap<String, org.hl7.fhir.r5.model.Property>();
417            for (Element c : children) {
418              org.hl7.fhir.r5.model.Property p = map.get(c.getName());
419              if (p == null) {
420              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);
421          childProps.add(p);
422          map.put(c.getName(), p);
423              
424              } else
425                p.getValues().add(c);
426            }
427          }
428        }
429        
430  @Override
431  public Base setProperty(int hash, String name, Base value) throws FHIRException {
432    if ("xhtml".equals(getType()) && (hash == "value".hashCode())) {
433      this.xhtml = TypeConvertor.castToXhtml(value);
434      this.value =  TypeConvertor.castToXhtmlString(value);
435      return this;
436    }
437    if (isPrimitive() && (hash == "value".hashCode())) {
438      this.value = TypeConvertor.castToString(value).asStringValue();
439      return this;
440    }
441    
442    if (!value.isPrimitive() && !(value instanceof Element)) {
443      if (isDataType(value)) 
444        value = convertToElement(property.getChild(name), value);
445      else
446        throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type");
447    }
448    
449    childMap = null;
450    if (children == null)
451      children = new ArrayList<Element>();
452    Element childForValue = null;
453    
454    // look through existing children
455    for (Element child : children) {
456      if (child.getName().equals(name)) {
457        if (!child.isList()) {
458          childForValue = child;
459          break;
460        } else {
461          Element ne = new Element(child);
462          children.add(ne);
463          numberChildren();
464          childForValue = ne;
465          break;
466        }
467      }
468    }
469
470    int i = 0;
471    if (childForValue == null)
472      for (Property p : property.getChildProperties(this.name, type)) {
473        int t = -1;
474        for (int c =0; c < children.size(); c++) {
475          Element e = children.get(c);
476          if (p.getName().equals(e.getName()))
477            t = c;
478        }
479        if (t >= i)
480          i = t+1;
481        if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
482          Element ne = new Element(name, p);
483          children.add(i, ne);
484          childForValue = ne;
485          break;
486        }
487      }
488    
489    if (childForValue == null)
490      throw new Error("Cannot set property "+name+" on "+this.name);
491    else if (value.isPrimitive()) {
492      if (childForValue.property.getName().endsWith("[x]"))
493        childForValue.name = name+Utilities.capitalize(value.fhirType());
494      childForValue.setValue(value.primitiveValue());
495    } else {
496      Element ve = (Element) value;
497      childForValue.type = ve.getType();
498      if (childForValue.property.getName().endsWith("[x]"))
499        childForValue.name = name+Utilities.capitalize(childForValue.type);
500      else if (value.isResource()) {
501        if (childForValue.elementProperty == null)
502          childForValue.elementProperty = childForValue.property;
503        childForValue.property = ve.property;
504        childForValue.special = SpecialElement.BUNDLE_ENTRY;
505      }
506      if (ve.children != null) {
507        if (childForValue.children == null)
508          childForValue.children = new ArrayList<Element>();
509        else 
510          childForValue.children.clear();
511        childForValue.children.addAll(ve.children);
512      }
513    }
514    return childForValue;
515  }
516
517  private Base convertToElement(Property prop, Base v) throws FHIRException {
518    return new ObjectConverter(property.getContext()).convert(prop, (DataType) v);
519  }
520
521  private boolean isDataType(Base v) {
522    return v instanceof DataType && new ContextUtilities(property.getContext()).getTypeNames().contains(v.fhirType());
523  }
524
525  @Override
526  public Base makeProperty(int hash, String name) throws FHIRException {
527    if (isPrimitive() && (hash == "value".hashCode())) {
528      return new StringType(value);
529    } else {
530      return makeElement(name);
531    }
532  }
533
534  public Element makeElement(String name) throws FHIRException {
535    if (children == null)
536      children = new ArrayList<Element>();
537    
538    // look through existing children
539    for (Element child : children) {
540      if (child.getName().equals(name)) {
541        if (!child.isList()) {
542          return child;
543        } else {
544          Element ne = new Element(child);
545          children.add(ne);
546          numberChildren();
547          return ne;
548        }
549      }
550    }
551
552    for (Property p : property.getChildProperties(this.name, type)) {
553      if (p.getName().equals(name)) {
554        Element ne = new Element(name, p);
555        children.add(ne);
556        return ne;
557      } else if (p.getDefinition().isChoice() && name.startsWith(p.getName().replace("[x]", ""))) {
558        String type = name.substring(p.getName().length()-3);
559        if (new ContextUtilities(property.getContext()).isPrimitiveDatatype(Utilities.uncapitalize(type))) {
560          type = Utilities.uncapitalize(type);
561        }
562        Element ne = new Element(name, p);
563        ne.setType(type);
564        children.add(ne);
565        return ne;
566        
567      }
568    }
569      
570    throw new Error("Unrecognised name "+name+" on "+this.name); 
571  }
572
573  public Element forceElement(String name) throws FHIRException {
574    if (children == null)
575      children = new ArrayList<Element>();
576    
577    // look through existing children
578    for (Element child : children) {
579      if (child.getName().equals(name)) {
580        return child;
581      }
582    }
583
584    for (Property p : property.getChildProperties(this.name, type)) {
585      if (p.getName().equals(name)) {
586        Element ne = new Element(name, p);
587        children.add(ne);
588        return ne;
589      }
590    }
591      
592    throw new Error("Unrecognised name "+name+" on "+this.name); 
593  }
594
595
596        private int maxToInt(String max) {
597    if (max.equals("*"))
598      return Integer.MAX_VALUE;
599    else
600      return Integer.parseInt(max);
601        }
602
603        @Override
604        public boolean isPrimitive() {
605                return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name));
606        }
607        
608  @Override
609  public boolean isBooleanPrimitive() {
610    return isPrimitive() && ("boolean".equals(type) || "boolean".equals(property.getType(name)));
611  }
612 
613  @Override
614  public boolean isResource() {
615    return property.isResource();
616  }
617  
618
619        @Override
620        public boolean hasPrimitiveValue() {
621                return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name);
622        }
623        
624
625        @Override
626        public String primitiveValue() {
627                if (isPrimitive() || value != null)
628                  return value;
629                else {
630                        if (hasPrimitiveValue() && children != null) {
631                                for (Element c : children) {
632                                        if (c.getName().equals("value"))
633                                                return c.primitiveValue();
634                                }
635                        }
636                        return null;
637                }
638        }
639        
640        // for the validator
641  public int line() {
642    return line;
643  }
644
645  public int col() {
646    return col;
647  }
648
649        public Element markLocation(int line, int col) {
650                this.line = line;
651                this.col = col; 
652                return this;
653        }
654
655  public Element markLocation(SourceLocation loc) {
656    this.line = loc.getLine();
657    this.col = loc.getColumn(); 
658    return this;
659  }
660
661  public Element markLocation(Element src) {
662    this.line = src.line();
663    this.col = src.col(); 
664    return this;
665  }
666
667        public void clearDecorations() {
668          clearUserData("fhir.decorations");
669          for (Element e : children) {
670            e.clearDecorations();         
671          }
672    childMap = null;
673        }
674        
675        public void markValidation(StructureDefinition profile, ElementDefinition definition) {
676          @SuppressWarnings("unchecked")
677    List<ElementDecoration> decorations = (List<ElementDecoration>) getUserData("fhir.decorations");
678          if (decorations == null) {
679            decorations = new ArrayList<>();
680            setUserData("fhir.decorations", decorations);
681          }
682          decorations.add(new ElementDecoration(DecorationType.TYPE, profile.getWebPath(), definition.getPath()));
683          if (definition.getId() != null && tail(definition.getId()).contains(":")) {
684            String[] details = tail(definition.getId()).split(":");
685            decorations.add(new ElementDecoration(DecorationType.SLICE, null, details[1]));
686          }
687        }
688        
689  private String tail(String id) {
690    return id.contains(".") ? id.substring(id.lastIndexOf(".")+1) : id;
691  }
692
693  public Element getNamedChild(String name) {
694    if (children == null)
695      return null;
696    if (children.size() > 20) {
697      populateChildMap();
698      List<Element> l = childMap.get(name);
699      if (l == null) {
700        // try the other way (in case of complicated naming rules)
701      } else if (l.size() > 1) {
702        throw new Error("Attempt to read a single element when there is more than one present ("+name+")");
703      } else {
704        return l.get(0);
705      }
706    } else {
707      
708    }
709    Element result = null;
710    
711    for (Element child : children) {
712      if (child.getName() != null && name != null && child.getProperty() != null && child.getProperty().getDefinition() != null && child.fhirType() != null) {
713        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())) {
714          if (result == null)
715            result = child;
716          else 
717            throw new Error("Attempt to read a single element when there is more than one present ("+name+")");
718        }
719      }
720    }
721          return result;
722        }
723
724  public void getNamedChildren(String name, List<Element> list) {
725        if (children != null)
726          if (children.size() > 20) {
727        populateChildMap();
728        List<Element> l = childMap.get(name);
729        if (l != null) {
730          list.addAll(l);
731        }
732      } else {
733                  for (Element child : children) 
734                          if (child.getName().equals(name))
735                                  list.add(child);
736      }
737  }
738
739  public String getNamedChildValue(String name) {
740        Element child = getNamedChild(name);
741        return child == null ? null : child.value;
742  }
743
744  public void getNamedChildrenWithWildcard(String string, List<Element> values) {
745          Validate.isTrue(string.endsWith("[x]"));
746          
747          String start = string.substring(0, string.length() - 3);
748                if (children != null) {
749                        for (Element child : children) { 
750                                if (child.getName().startsWith(start)) {
751                                        values.add(child);
752                                }
753                        }
754                }
755  }
756
757  
758        public XhtmlNode getXhtml() {
759                return xhtml;
760        }
761
762        public Element setXhtml(XhtmlNode xhtml) {
763                this.xhtml = xhtml;
764                return this;
765        }
766
767        @Override
768        public boolean isEmpty() {
769        // GG: this used to also test !"".equals(value). 
770    // 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.
771          // it should not cause any problems in real life.
772                if (value != null) {   
773                        return false;
774                }
775                for (Element next : getChildren()) {
776                        if (!next.isEmpty()) {
777                                return false;
778                        }
779                }
780                return true;
781        }
782
783  public Property getElementProperty() {
784    return elementProperty;
785  }
786
787  public boolean hasElementProperty() {
788    return elementProperty != null;
789  }
790
791  public boolean hasChild(String name) {
792    return getNamedChild(name) != null;
793  }
794
795  public boolean hasChildren(String name) {
796    if (children != null)
797      for (Element child : children) 
798        if (child.getName().equals(name))
799          return true;
800    return false;
801  }
802
803  @Override
804  public String toString() {
805    if (name.equals(fhirType()) && isResource()) {
806      return fhirType()+"/"+getIdBase() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
807      
808    } else if (isResource()) {
809      return name+"="+fhirType()+"/"+getIdBase()+ "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
810    } else {
811      return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
812    }
813  }
814
815  @Override
816  public String getIdBase() {
817    return getChildValue("id");
818  }
819
820  @Override
821  public void setIdBase(String value) {
822    setChildValue("id", value);
823  }
824
825
826  @Override
827  public boolean equalsDeep(Base other) {
828    if (!super.equalsDeep(other))
829      return false;
830    if (isPrimitive() && other.isPrimitive())
831      return primitiveValue().equals(other.primitiveValue());
832    if (isPrimitive() || other.isPrimitive())
833      return false;
834    Set<String> processed  = new HashSet<String>();
835    for (org.hl7.fhir.r5.model.Property p : children()) {
836      String name = p.getName();
837      processed.add(name);
838      org.hl7.fhir.r5.model.Property o = other.getChildByName(name);
839      if (!equalsDeep(p, o))
840        return false;
841    }
842    for (org.hl7.fhir.r5.model.Property p : children()) {
843      String name = p.getName();
844      if (!processed.contains(name)) {
845        org.hl7.fhir.r5.model.Property o = other.getChildByName(name);
846        if (!equalsDeep(p, o))
847          return false;
848      }
849    }
850    return true;
851  }
852
853  private boolean equalsDeep(org.hl7.fhir.r5.model.Property p, org.hl7.fhir.r5.model.Property o) {
854    if (o == null || p == null)
855      return false;
856    if (p.getValues().size() != o.getValues().size())
857      return false;
858    for (int i = 0; i < p.getValues().size(); i++)
859      if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true))
860        return false;
861    return true;
862  }
863
864  @Override
865  public boolean equalsShallow(Base other) {
866    if (!super.equalsShallow(other))
867      return false;
868    if (isPrimitive() && other.isPrimitive())
869      return primitiveValue().equals(other.primitiveValue());
870    if (isPrimitive() || other.isPrimitive())
871      return false;
872    return true; //?
873  }
874
875  public DataType asType() throws FHIRException {
876    return new ObjectConverter(property.getContext()).convertToType(this);
877  }
878
879  @Override
880  public boolean isMetadataBased() {
881    return true;
882  }
883
884  public boolean isList() {
885    if (elementProperty != null)
886      return elementProperty.isList();
887    else
888      return property.isList();
889  }
890  
891  public boolean isBaseList() {
892    if (elementProperty != null)
893      return elementProperty.isBaseList();
894    else
895      return property.isBaseList();
896  }
897  
898  @Override
899  public String[] getTypesForProperty(int hash, String name) throws FHIRException {
900    Property p = property.getChildSimpleName(this.name, name);
901    if (p != null) {
902      Set<String> types = new HashSet<String>();
903      for (TypeRefComponent tr : p.getDefinition().getType()) {
904        types.add(tr.getCode());
905      }
906      return types.toArray(new String[]{});
907    }
908    return super.getTypesForProperty(hash, name);
909
910  }
911
912  public void sort() {
913    if (children != null) {
914      List<Element> remove = new ArrayList<Element>();
915      for (Element child : children) {
916        child.sort();
917        if (child.isEmpty())
918          remove.add(child);
919      }
920      children.removeAll(remove);
921      Collections.sort(children, new ElementSortComparator(this, this.property));
922      childMap = null;
923    }
924  }
925
926  public class ElementSortComparator implements Comparator<Element> {
927    private List<ElementDefinition> children;
928    public ElementSortComparator(Element e, Property property) {
929      String tn = e.getType();
930      StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, null));
931      if (sd != null && !sd.getAbstract())
932        children = sd.getSnapshot().getElement();
933      else
934        children = property.getStructure().getSnapshot().getElement();
935    }
936    
937    @Override
938    public int compare(Element e0, Element e1) {
939      int i0 = find(e0);
940      int i1 = find(e1);
941      return Integer.compare(i0, i1);
942    }
943    private int find(Element e0) {
944      int i =  e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) :  children.indexOf(e0.property.getDefinition());
945      return i; 
946    }
947
948  }
949
950  public class ICodingImpl implements ICoding {
951    private String system;
952    private String version;
953    private String code;
954    private String display;
955    private boolean doesSystem;
956    private boolean doesVersion;
957    private boolean doesCode;
958    private boolean doesDisplay;
959    public ICodingImpl(boolean doesCode, boolean doesSystem, boolean doesVersion, boolean doesDisplay) {
960      super();
961      this.doesCode = doesCode;
962      this.doesSystem = doesSystem;
963      this.doesVersion = doesVersion;
964      this.doesDisplay = doesDisplay;
965    }
966    public String getSystem() {
967      return system;
968    }
969    public String getVersion() {
970      return version;
971    }
972    public String getCode() {
973      return code;
974    }
975    public String getDisplay() {
976      return display;
977    }
978    public boolean hasSystem() {
979      return !Utilities.noString(system); 
980    }
981    public boolean hasVersion() {
982      return !Utilities.noString(version);
983    }
984    public boolean hasCode() {
985      return !Utilities.noString(code);
986    }
987    public boolean hasDisplay() {
988      return !Utilities.noString(display);
989    }
990    public boolean supportsSystem() {
991      return doesSystem;
992    }
993    public boolean supportsVersion() {
994      return doesVersion;
995    }
996    public boolean supportsCode() {
997      return doesCode;
998    }
999    public boolean supportsDisplay() {
1000      return doesDisplay;
1001    }    
1002  }
1003
1004  public ICoding getAsICoding() throws FHIRException {
1005    if ("code".equals(fhirType())) {
1006      if (property.getDefinition().getBinding().getStrength() != BindingStrength.REQUIRED)
1007        return null;
1008      ICodingImpl c = new ICodingImpl(true, true, false, false);
1009      c.code = primitiveValue();
1010      ValueSetExpansionOutcome vse = property.getContext().expandVS(property.getStructure(), property.getDefinition().getBinding(), true, false);
1011      if (vse.getValueset() == null)
1012        return null;
1013      for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
1014        if (cc.getCode().equals(c.code)) {
1015          c.system = cc.getSystem();
1016          if (cc.hasVersion()) {
1017            c.doesVersion = true;
1018            c.version = cc.getVersion();
1019          }
1020          if (cc.hasDisplay()) {
1021            c.doesDisplay = true;
1022            c.display = cc.getDisplay();
1023          }
1024        }
1025      }
1026      if (c.system == null)
1027        return null;
1028      return c;   
1029    } else if ("Coding".equals(fhirType())) {
1030      ICodingImpl c = new ICodingImpl(true, true, true, true);
1031      c.system = getNamedChildValue("system");
1032      c.code = getNamedChildValue("code");
1033      c.display = getNamedChildValue("display");
1034      c.version = getNamedChildValue("version");
1035      return c;
1036    } else if ("Quantity".equals(fhirType())) {
1037      ICodingImpl c = new ICodingImpl(true, true, false, false);
1038      c.system = getNamedChildValue("system");
1039      c.code = getNamedChildValue("code");
1040      return c;
1041    } else 
1042      return null;
1043  }
1044
1045  public String getExplicitType() {
1046    return explicitType;
1047  }
1048
1049  public void setExplicitType(String explicitType) {
1050    this.explicitType = explicitType;
1051  }
1052
1053  public boolean hasDescendant(Element element) {
1054    if (children != null) {
1055      for (Element child : children) {
1056        if (element == child || child.hasDescendant(element)) {
1057          return true;        
1058        }
1059      }
1060    }
1061    return false;
1062  }
1063
1064  public Element getExtension(String url) {
1065    if (children != null) {
1066      for (Element child : children) {
1067        if (extensionList.contains(child.getName())) {
1068          String u = child.getChildValue("url");
1069          if (url.equals(u)) {
1070            return child;
1071          }
1072        }
1073      }
1074    }
1075    return null;
1076  }
1077
1078  public Base getExtensionValue(String url) {
1079    if (children != null) {
1080      for (Element child : children) {
1081        if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
1082          String u = child.getChildValue("url");
1083          if (url.equals(u)) {
1084            return child.getNamedChild("value");
1085          }
1086        }
1087      }
1088    }
1089    return null;
1090  }
1091
1092  public boolean hasExtension(String url) {
1093    if (children != null) {
1094      for (Element child : children) {
1095        if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
1096          String u = child.getChildValue("url");
1097          if (url.equals(u)) {
1098            return true;
1099          }
1100        }
1101      }
1102    }
1103    return false;
1104  }
1105
1106  /**
1107   * 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
1108   */
1109  public Element getParentForValidator() {
1110    if (!hasParentForValidator) {
1111      throw new Error("Parent not set");
1112    }
1113    return parentForValidator;
1114  }
1115
1116  public void setParentForValidator(Element parentForValidator) {
1117    this.parentForValidator = parentForValidator;
1118    this.hasParentForValidator = true;
1119  }
1120  
1121  public boolean hasParentForValidator() {
1122    return hasParentForValidator;
1123  }
1124
1125  public void clear() {
1126    comments = null;
1127    children.clear();
1128    childMap = null;
1129    property = null;
1130    elementProperty = null;
1131    xhtml = null;
1132    path = null;
1133  }
1134
1135  public String getPath() {
1136    return path;
1137  }
1138
1139  public void setPath(String path) {
1140    this.path = path;
1141  }  
1142  
1143  public void addMessage(ValidationMessage vm) {
1144    if (messages == null) {
1145      messages = new ArrayList<>();
1146    }
1147    messages.add(vm);
1148  }
1149
1150  public boolean hasMessages() {
1151    return messages != null && !messages.isEmpty();
1152  }
1153
1154  public List<ValidationMessage> getMessages() {
1155    return messages;
1156  }
1157
1158  public void removeChild(String name) {
1159    children.removeIf(n -> name.equals(n.getName()));
1160    childMap = null;
1161  }
1162
1163  public boolean isProhibited() {
1164    return prohibited;
1165  }
1166
1167  public void setProhibited(boolean prohibited) {
1168    this.prohibited = prohibited;
1169  }
1170
1171  public boolean isRequired() {
1172    return required;
1173  }
1174
1175  public void setRequired(boolean required) {
1176    this.required = required;
1177  }
1178
1179  public int getDescendentCount() {
1180    return descendentCount;
1181  }
1182
1183  public void setDescendentCount(int descendentCount) {
1184    this.descendentCount = descendentCount;
1185  }
1186
1187  public int countDescendents() {
1188    if (descendentCount > 0) {
1189      return descendentCount;
1190    } else if (children != null) {
1191      descendentCount = children.size();
1192      for (Element e : children) {
1193        descendentCount = descendentCount + e.countDescendents();
1194      }
1195    } else {
1196      descendentCount = 0;
1197    }
1198    return descendentCount;
1199  }
1200
1201  public int getInstanceId() {
1202    return instanceId;
1203  }
1204
1205  public void setInstanceId(int instanceId) {
1206    this.instanceId = instanceId;
1207  }
1208
1209
1210  @Override
1211  public boolean hasValidationInfo() {
1212    return hasSource() ? source.hasValidationInfo() : super.hasValidationInfo();
1213  }
1214
1215  @Override
1216  public List<ValidationInfo> getValidationInfo() {
1217    return hasSource() ? source.getValidationInfo() : super.getValidationInfo();
1218  }
1219
1220  @Override
1221  public ValidationInfo addDefinition(StructureDefinition source, ElementDefinition defn, ValidationMode mode) {
1222    if (this.source != null) {
1223      return this.source.addDefinition(source, defn, mode);
1224    } else {
1225      return super.addDefinition(source, defn, mode);
1226    }
1227  }
1228
1229  public boolean hasSource() {
1230    return source != null;
1231  }
1232
1233  
1234  public Base getSource() {
1235    return source;
1236  }
1237
1238  public void setSource(Base source) {
1239    this.source = source;
1240  }
1241
1242  public void printToOutput() {
1243    printToOutput(System.out, "");
1244    
1245  }
1246
1247  public void printToOutput(PrintStream stream) {
1248    printToOutput(stream, "");
1249    
1250  }
1251
1252  private void printToOutput(PrintStream out, String indent) {
1253    String s = indent+name +(index == -1 ? "" : "["+index+"]") +(special != null ? "$"+special.toHuman(): "")+ (type!= null || explicitType != null ? " : "+type+(explicitType != null ? "/'"+explicitType+"'" : "") : "");
1254    if (isNull) {
1255      s = s + " = (null)";
1256    } else if (value != null) {
1257      s = s + " = '"+value+"'";      
1258    } else if (xhtml != null) {
1259      s = s + " = (xhtml)";
1260    }
1261    if (property != null) {
1262      s = s +" {"+property.summary();
1263      if (elementProperty != null) {
1264        s = s +" -> "+elementProperty.summary();
1265      }
1266      s = s + "}";
1267    }
1268    if (line > 0) {
1269      s = s + " (l"+line+":c"+col+")";
1270    }
1271    out.println(s);
1272    if (children != null) {
1273      for (Element child : children) {
1274        child.printToOutput(out, indent+"  ");
1275      }
1276    }
1277    
1278  }
1279
1280  private String msgCounts() {
1281    int e = 0;
1282    int w = 0;
1283    int h = 0;
1284    for (ValidationMessage msg : messages) {
1285      switch (msg.getLevel()) {
1286      case ERROR:
1287        e++;
1288        break;
1289      case FATAL:
1290        e++;
1291        break;
1292      case INFORMATION:
1293        h++;
1294        break;
1295      case NULL:
1296        break;
1297      case WARNING:
1298        w++;
1299        break;
1300      default:
1301        break;      
1302      }
1303    }
1304    return "e:"+e+",w:"+w+",h:"+h;
1305  }
1306
1307  public void populatePaths(String path) {
1308    if (path == null) {
1309      path = fhirType();
1310    }
1311    setPath(path);
1312    if (children != null) {
1313      for (Element n : children) {
1314        n.populatePaths(path+"."+n.getName());
1315      }
1316    }
1317    
1318  }
1319
1320  public String fhirTypeRoot() {
1321    if (fhirType().contains("/")) {
1322      return fhirType().substring(fhirType().lastIndexOf("/")+1);
1323    } else {
1324      return fhirType();
1325    }
1326  }
1327
1328  public void setElement(String string, Element map) {
1329    throw new Error("Not done yet");    
1330  }
1331
1332  public Element addElement(String name) {
1333    if (children == null)
1334      children = new ArrayList<Element>();
1335
1336    for (Property p : property.getChildProperties(this.name, type)) {
1337      if (p.getName().equals(name)) {
1338        if (!p.isList()) {
1339          throw new Error(name+" on "+this.name+" is not a list, so can't add an element"); 
1340        }
1341        Element ne = new Element(name, p);
1342        children.add(ne);
1343        return ne;
1344      }
1345    }
1346
1347    throw new Error("Unrecognised property '"+name+"' on "+this.name); 
1348  }
1349
1350  @Override
1351  public Base copy() {
1352    Element element = new Element(this);
1353    this.copyValues(element);
1354    return element;
1355  }
1356
1357  @Override
1358  public void copyValues(Base dst) {
1359    super.copyValues(dst);
1360    
1361    Element dest = (Element) dst;
1362    if (comments != null) {
1363      dest.comments = new ArrayList<>();
1364      dest.comments.addAll(comments);
1365    } else {
1366      dest.comments = null;
1367    }
1368    dest.value = value;
1369    if (children != null) {
1370      dest.children = new ArrayList<>();
1371      for (Element child : children) {
1372        dest.children.add((Element) child.copy());
1373      }
1374    } else {
1375      dest.children = null;
1376    }
1377    dest.line = line;
1378    dest.col = col;
1379    dest.xhtml = xhtml;
1380    dest.explicitType = explicitType;
1381    dest.hasParentForValidator = false;
1382    dest.path = path;
1383    dest.messages = null;
1384    dest.prohibited = prohibited;
1385    dest.required = required;
1386    dest.childMap = null;
1387    dest.descendentCount = descendentCount;
1388    dest.instanceId = instanceId;
1389    dest.isNull = isNull;
1390    dest.source = source;
1391  }
1392  
1393  public Base setProperty(String name, Base value) throws FHIRException {
1394    setChildValue(name, value.primitiveValue());
1395    return this;
1396  }
1397
1398  public boolean isIgnorePropertyOrder() {
1399    return ignorePropertyOrder;
1400  }
1401
1402  public void setIgnorePropertyOrder(boolean ignorePropertyOrder) {
1403    this.ignorePropertyOrder = ignorePropertyOrder;
1404    if (children != null) {
1405      for (Element e : children) {
1406        e.setIgnorePropertyOrder(ignorePropertyOrder);
1407      }
1408    }
1409  }
1410  
1411
1412  private String webPath;
1413  public boolean hasWebPath() {
1414    return webPath != null;
1415  }
1416  public String getWebPath() {
1417    return webPath;
1418  }
1419  public void setWebPath(String webPath) {
1420    this.webPath = webPath;
1421  }
1422
1423  public String getTranslation(String lang) {
1424    for (Element e : getChildren()) {
1425      if (e.fhirType().equals("Extension")) {
1426        String url = e.getNamedChildValue("url");
1427        if (ToolingExtensions.EXT_TRANSLATION.equals(url)) {
1428          String l = null;
1429          String v = null;
1430          for (Element g : e.getChildren()) {
1431            if (g.fhirType().equals("Extension")) {
1432              String u = g.getNamedChildValue("url");
1433              if ("lang".equals(u)) {
1434                l = g.getNamedChildValue("value");
1435              } else if ("value".equals(u)) {
1436                v = g.getNamedChildValue("value");
1437              }
1438            }
1439          }
1440          if (LanguageUtils.langsMatch(lang, l)) {
1441            return v;
1442          }
1443        }
1444      }
1445    }
1446    return null;
1447  }
1448
1449  public String getBasePath() {
1450    if (property.getStructure().hasExtension(ToolingExtensions.EXT_RESOURCE_IMPLEMENTS)) {
1451      StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ExtensionsUtils.getExtensionString(property.getStructure(), ToolingExtensions.EXT_RESOURCE_IMPLEMENTS));
1452      if (sd != null) {
1453        ElementDefinition ed = sd.getSnapshot().getElementByPath(property.getDefinition().getPath().replace(property.getStructure().getType(), sd.getType()));
1454        if (ed != null) {
1455          return ed.getBase().getPath();
1456        }
1457      }
1458    }
1459    return property.getDefinition().getBase().getPath();
1460  }
1461
1462  public void setTranslation(String lang, String translation) {
1463    for (Element e : getChildren()) {
1464      if (e.fhirType().equals("Extension")) {
1465        String url = e.getNamedChildValue("url");
1466        if (ToolingExtensions.EXT_TRANSLATION.equals(url)) {
1467          String l = null;
1468          Element v = null;
1469          for (Element g : e.getChildren()) {
1470            if (g.fhirType().equals("Extension")) {
1471              String u = g.getNamedChildValue("url");
1472              if ("lang".equals(u)) {
1473                l = g.getNamedChildValue("value");
1474              } else if ("value".equals(u)) {
1475                v = g.getNamedChild("value");
1476              }
1477            }
1478          }
1479          if (LanguageUtils.langsMatch(lang, l)) {
1480            if (v == null) {
1481              Element ext = e.addElement("extension");
1482              ext.addElement("url").setValue("value");
1483              ext.addElement("valueString").setValue(translation);
1484            } else {
1485              v.setValue(translation);
1486            }
1487          }
1488        }
1489      }
1490    }
1491    Element t = addElement("extension");
1492    t.addElement("url").setValue(ToolingExtensions.EXT_TRANSLATION);
1493
1494    Element ext = t.addElement("extension");
1495    ext.addElement("url").setValue("lang");
1496    ext.addElement("valueCode").setValue(lang);
1497   
1498    ext = t.addElement("extension");
1499    ext.addElement("url").setValue("value");
1500    ext.addElement("valueString").setValue(translation);
1501  }
1502}