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