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