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