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