001package org.hl7.fhir.dstu3.elementmodel;
002
003/*-
004 * #%L
005 * org.hl7.fhir.dstu3
006 * %%
007 * Copyright (C) 2014 - 2019 Health Level 7
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023
024import static org.apache.commons.lang3.StringUtils.isNotBlank;
025
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.Comparator;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Set;
032
033import org.apache.commons.lang3.Validate;
034import org.hl7.fhir.dstu3.model.Base;
035import org.hl7.fhir.dstu3.model.ElementDefinition;
036import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
037import org.hl7.fhir.dstu3.model.StringType;
038import org.hl7.fhir.dstu3.model.StructureDefinition;
039import org.hl7.fhir.dstu3.model.Type;
040import org.hl7.fhir.exceptions.FHIRException;
041import org.hl7.fhir.utilities.Utilities;
042import org.hl7.fhir.utilities.xhtml.XhtmlNode;
043
044/**
045 * This class represents the underlying reference model of FHIR
046 * 
047 * A resource is nothing but a set of elements, where every element has a 
048 * name, maybe a stated type, maybe an id, and either a value or child elements 
049 * (one or the other, but not both or neither)
050 * 
051 * @author Grahame Grieve
052 *
053 */
054public class Element extends Base {
055
056
057  public enum SpecialElement {
058                CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, PARAMETER;
059
060    public static SpecialElement fromProperty(Property property) {
061      if (property.getStructure().getIdElement().getIdPart().equals("Parameters"))
062        return PARAMETER;
063      if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("resource"))
064        return BUNDLE_ENTRY;
065      if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("outcome"))
066        return BUNDLE_OUTCOME;
067      if (property.getName().equals("contained")) 
068        return CONTAINED;
069      throw new Error("Unknown resource containing a native resource: "+property.getDefinition().getId());
070    }
071        }
072
073        private List<String> comments;// not relevant for production, but useful in documentation
074        private String name;
075        private String type;
076        private String value;
077        private int index = -1;
078        private List<Element> children;
079        private Property property;
080  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
081        private int line;
082        private int col;
083        private SpecialElement special;
084        private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation
085
086        public Element(String name) {
087                super();
088                this.name = name;
089        }
090
091  public Element(Element other) {
092    super();
093    name = other.name;
094    type = other.type;
095    property = other.property;
096    elementProperty = other.elementProperty;
097    special = other.special;
098  }
099  
100  public Element(String name, Property property) {
101                super();
102                this.name = name;
103                this.property = property;
104        }
105
106        public Element(String name, Property property, String type, String value) {
107                super();
108                this.name = name;
109                this.property = property;
110                this.type = type;
111                this.value = value;
112        }
113
114        public void updateProperty(Property property, SpecialElement special, Property elementProperty) {
115                this.property = property;
116    this.elementProperty = elementProperty;
117                this.special = special;
118        }
119
120        public SpecialElement getSpecial() {
121                return special;
122        }
123
124        public String getName() {
125                return name;
126        }
127
128        public String getType() {
129                if (type == null)
130                        return property.getType(name);
131                else
132                  return type;
133        }
134
135        public String getValue() {
136                return value;
137        }
138
139        public boolean hasChildren() {
140                return !(children == null || children.isEmpty());
141        }
142
143        public List<Element> getChildren() {
144                if (children == null)
145                        children = new ArrayList<Element>();
146                return children;
147        }
148
149        public boolean hasComments() {
150                return !(comments == null || comments.isEmpty());
151        }
152
153        public List<String> getComments() {
154                if (comments == null)
155                        comments = new ArrayList<String>();
156                return comments;
157        }
158
159        public Property getProperty() {
160                return property;
161        }
162
163        public void setValue(String value) {
164                this.value = value;
165        }
166
167        public void setType(String type) {
168                this.type = type;
169
170        }
171
172        public boolean hasValue() {
173                return value != null;
174        }
175
176        public List<Element> getChildrenByName(String name) {
177                List<Element> res = new ArrayList<Element>();
178                if (hasChildren()) {
179                        for (Element child : children)
180                                if (name.equals(child.getName()))
181                                        res.add(child);
182                }
183                return res;
184        }
185
186        public void numberChildren() {
187                if (children == null)
188                        return;
189                
190                String last = "";
191                int index = 0;
192                for (Element child : children) {
193                        if (child.getProperty().isList()) {
194                          if (last.equals(child.getName())) {
195                                index++;
196                          } else {
197                                last = child.getName();
198                                index = 0;
199                          }
200                        child.index = index;
201                        } else {
202                                child.index = -1;
203                        }
204                        child.numberChildren();
205                }       
206        }
207
208        public int getIndex() {
209                return index;
210        }
211
212        public boolean hasIndex() {
213                return index > -1;
214        }
215
216        public void setIndex(int index) {
217                this.index = index;
218        }
219
220        public String getChildValue(String name) {
221                if (children == null)
222                        return null;
223                for (Element child : children) {
224                        if (name.equals(child.getName()))
225                                return child.getValue();
226                }
227        return null;
228        }
229
230  public void setChildValue(String name, String value) {
231    if (children == null)
232      children = new ArrayList<Element>();
233    for (Element child : children) {
234      if (name.equals(child.getName())) {
235        if (!child.isPrimitive())
236          throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")");
237        child.setValue(value);
238      }
239    }
240    try {
241      setProperty(name.hashCode(), name, new StringType(value));
242    } catch (FHIRException e) {
243      throw new Error(e);
244    }
245  }
246
247        public List<Element> getChildren(String name) {
248                List<Element> res = new ArrayList<Element>(); 
249                if (children != null)
250                for (Element child : children) {
251                        if (name.equals(child.getName()))
252                                res.add(child);
253                }
254                return res;
255        }
256
257  public boolean hasType() {
258    if (type == null)
259      return property.hasType(name);
260    else
261      return true;
262  }
263
264  @Override
265  public String fhirType() {
266    return getType();
267  }
268
269  @Override
270        public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
271        if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) {
272//              String tn = getType();
273//              throw new Error(tn+" not done yet");
274          Base[] b = new Base[1];
275          b[0] = new StringType(value);
276          return b;
277        }
278                
279        List<Base> result = new ArrayList<Base>();
280        if (children != null) {
281        for (Element child : children) {
282                if (child.getName().equals(name))
283                        result.add(child);
284                if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]"))
285                        result.add(child);
286        }
287        }
288        if (result.isEmpty() && checkValid) {
289//              throw new FHIRException("not determined yet");
290        }
291        return result.toArray(new Base[result.size()]);
292        }
293
294        @Override
295        protected void listChildren(List<org.hl7.fhir.dstu3.model.Property> childProps) {
296          if (children != null) {
297            for (Element c : children) {
298              childProps.add(new org.hl7.fhir.dstu3.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c));
299            }
300          }
301        }
302        
303  @Override
304  public Base setProperty(int hash, String name, Base value) throws FHIRException {
305    if (isPrimitive() && (hash == "value".hashCode())) {
306      this.value = castToString(value).asStringValue();
307      return this;
308    }
309    if ("xhtml".equals(getType()) && (hash == "value".hashCode())) {
310      this.xhtml = castToXhtml(value);
311      this.value =  castToXhtmlString(value);
312      return this;
313    }
314    
315    if (!value.isPrimitive() && !(value instanceof Element)) {
316      if (isDataType(value)) 
317        value = convertToElement(property.getChild(name), value);
318      else
319        throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type");
320    }
321    
322    if (children == null)
323      children = new ArrayList<Element>();
324    Element childForValue = null;
325    
326    // look through existing children
327    for (Element child : children) {
328      if (child.getName().equals(name)) {
329        if (!child.isList()) {
330          childForValue = child;
331          break;
332        } else {
333          Element ne = new Element(child);
334          children.add(ne);
335          numberChildren();
336          childForValue = ne;
337          break;
338        }
339      }
340    }
341
342    if (childForValue == null)
343      for (Property p : property.getChildProperties(this.name, type)) {
344        if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
345          Element ne = new Element(name, p);
346          children.add(ne);
347          childForValue = ne;
348          break;
349        }
350      }
351    
352    if (childForValue == null)
353      throw new Error("Cannot set property "+name+" on "+this.name);
354    else if (value.isPrimitive()) {
355      if (childForValue.property.getName().endsWith("[x]"))
356        childForValue.name = name+Utilities.capitalize(value.fhirType());
357      childForValue.setValue(value.primitiveValue());
358    } else {
359      Element ve = (Element) value;
360      childForValue.type = ve.getType();
361      if (childForValue.property.getName().endsWith("[x]"))
362        childForValue.name = name+Utilities.capitalize(childForValue.type);
363      else if (value.isResource()) {
364        if (childForValue.elementProperty == null)
365          childForValue.elementProperty = childForValue.property;
366        childForValue.property = ve.property;
367        childForValue.special = SpecialElement.BUNDLE_ENTRY;
368      }
369      if (ve.children != null) {
370        if (childForValue.children == null)
371          childForValue.children = new ArrayList<Element>();
372        else 
373          childForValue.children.clear();
374        childForValue.children.addAll(ve.children);
375      }
376    }
377    return childForValue;
378  }
379
380  private Base convertToElement(Property prop, Base v) throws FHIRException {
381    return new ObjectConverter(property.getContext()).convert(prop, (Type) v);
382  }
383
384  private boolean isDataType(Base v) {
385    return v instanceof Type &&  property.getContext().getTypeNames().contains(v.fhirType());
386  }
387
388  @Override
389  public Base makeProperty(int hash, String name) throws FHIRException {
390    if (isPrimitive() && (hash == "value".hashCode())) {
391      return new StringType(value);
392    }
393
394    if (children == null)
395      children = new ArrayList<Element>();
396    
397    // look through existing children
398    for (Element child : children) {
399      if (child.getName().equals(name)) {
400        if (!child.isList()) {
401          return child;
402        } else {
403          Element ne = new Element(child);
404          children.add(ne);
405          numberChildren();
406          return ne;
407        }
408      }
409    }
410
411    for (Property p : property.getChildProperties(this.name, type)) {
412      if (p.getName().equals(name)) {
413        Element ne = new Element(name, p);
414        children.add(ne);
415        return ne;
416      }
417    }
418      
419    throw new Error("Unrecognised name "+name+" on "+this.name); 
420  }
421  
422        private int maxToInt(String max) {
423    if (max.equals("*"))
424      return Integer.MAX_VALUE;
425    else
426      return Integer.parseInt(max);
427        }
428
429        @Override
430        public boolean isPrimitive() {
431                return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name));
432        }
433        
434  @Override
435  public boolean isResource() {
436    return property.isResource();
437  }
438  
439
440        @Override
441        public boolean hasPrimitiveValue() {
442                return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name);
443        }
444        
445
446        @Override
447        public String primitiveValue() {
448                if (isPrimitive())
449                  return value;
450                else {
451                        if (hasPrimitiveValue() && children != null) {
452                                for (Element c : children) {
453                                        if (c.getName().equals("value"))
454                                                return c.primitiveValue();
455                                }
456                        }
457                        return null;
458                }
459        }
460        
461        // for the validator
462  public int line() {
463    return line;
464  }
465
466  public int col() {
467    return col;
468  }
469
470        public Element markLocation(int line, int col) {
471                this.line = line;
472                this.col = col; 
473                return this;
474        }
475
476        public void markValidation(StructureDefinition profile, ElementDefinition definition) {
477        }
478        
479  public Element getNamedChild(String name) {
480          if (children == null)
481                return null;
482          Element result = null;
483          for (Element child : children) {
484                if (child.getName().equals(name)) {
485                        if (result == null)
486                                result = child;
487                        else 
488                                throw new Error("Attempt to read a single element when there is more than one present ("+name+")");
489                }
490          }
491          return result;
492        }
493
494  public void getNamedChildren(String name, List<Element> list) {
495        if (children != null)
496                for (Element child : children) 
497                        if (child.getName().equals(name))
498                                list.add(child);
499  }
500
501  public String getNamedChildValue(String name) {
502        Element child = getNamedChild(name);
503        return child == null ? null : child.value;
504  }
505
506  public void getNamedChildrenWithWildcard(String string, List<Element> values) {
507          Validate.isTrue(string.endsWith("[x]"));
508          
509          String start = string.substring(0, string.length() - 3);
510                if (children != null) {
511                        for (Element child : children) { 
512                                if (child.getName().startsWith(start)) {
513                                        values.add(child);
514                                }
515                        }
516                }
517  }
518
519  
520        public XhtmlNode getXhtml() {
521                return xhtml;
522        }
523
524        public Element setXhtml(XhtmlNode xhtml) {
525                this.xhtml = xhtml;
526                return this;
527        }
528
529        @Override
530        public boolean isEmpty() {
531                if (isNotBlank(value)) {
532                        return false;
533                }
534                for (Element next : getChildren()) {
535                        if (!next.isEmpty()) {
536                                return false;
537                        }
538                }
539                return true;
540        }
541
542  public Property getElementProperty() {
543    return elementProperty;
544  }
545
546  public boolean hasElementProperty() {
547    return elementProperty != null;
548  }
549
550  public boolean hasChild(String name) {
551    return getNamedChild(name) != null;
552  }
553
554  @Override
555  public String toString() {
556    return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
557  }
558
559  @Override
560  public String getIdBase() {
561    return getChildValue("id");
562  }
563
564  @Override
565  public void setIdBase(String value) {
566    setChildValue("id", value);
567  }
568
569
570  @Override
571  public boolean equalsDeep(Base other) {
572    if (!super.equalsDeep(other))
573      return false;
574    if (isPrimitive() && other.isPrimitive())
575      return primitiveValue().equals(other.primitiveValue());
576    if (isPrimitive() || other.isPrimitive())
577      return false;
578    Set<String> processed  = new HashSet<String>();
579    for (org.hl7.fhir.dstu3.model.Property p : children()) {
580      String name = p.getName();
581      processed.add(name);
582      org.hl7.fhir.dstu3.model.Property o = other.getChildByName(name);
583      if (!equalsDeep(p, o))
584        return false;
585    }
586    for (org.hl7.fhir.dstu3.model.Property p : children()) {
587      String name = p.getName();
588      if (!processed.contains(name)) {
589        org.hl7.fhir.dstu3.model.Property o = other.getChildByName(name);
590        if (!equalsDeep(p, o))
591          return false;
592      }
593    }
594    return true;
595  }
596
597  private boolean equalsDeep(org.hl7.fhir.dstu3.model.Property p, org.hl7.fhir.dstu3.model.Property o) {
598    if (o == null || p == null)
599      return false;
600    if (p.getValues().size() != o.getValues().size())
601      return false;
602    for (int i = 0; i < p.getValues().size(); i++)
603      if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true))
604        return false;
605    return true;
606  }
607
608  @Override
609  public boolean equalsShallow(Base other) {
610    if (!super.equalsShallow(other))
611      return false;
612    if (isPrimitive() && other.isPrimitive())
613      return primitiveValue().equals(other.primitiveValue());
614    if (isPrimitive() || other.isPrimitive())
615      return false;
616    return true; //?
617  }
618
619  public Type asType() throws FHIRException {
620    return new ObjectConverter(property.getContext()).convertToType(this);
621  }
622
623  @Override
624  public boolean isMetadataBased() {
625    return true;
626  }
627
628  public boolean isList() {
629    if (elementProperty != null)
630      return elementProperty.isList();
631    else
632      return property.isList();
633  }
634  
635  @Override
636  public String[] getTypesForProperty(int hash, String name) throws FHIRException {
637    Property p = property.getChildSimpleName(this.name, name);
638    if (p != null) {
639      Set<String> types = new HashSet<String>();
640      for (TypeRefComponent tr : p.getDefinition().getType()) {
641        types.add(tr.getCode());
642      }
643      return types.toArray(new String[]{});
644    }
645    return super.getTypesForProperty(hash, name);
646
647  }
648
649  public void sort() {
650    if (children != null) {
651      List<Element> remove = new ArrayList<Element>();
652      for (Element child : children) {
653        child.sort();
654        if (child.isEmpty())
655          remove.add(child);
656      }
657      children.removeAll(remove);
658      Collections.sort(children, new ElementSortComparator(this, this.property));
659    }
660  }
661
662  public class ElementSortComparator implements Comparator<Element> {
663    private List<ElementDefinition> children;
664    public ElementSortComparator(Element e, Property property) {
665      String tn = e.getType();
666      StructureDefinition sd = property.getContext().fetchTypeDefinition(tn);
667      if (sd != null && !sd.getAbstract())
668        children = sd.getSnapshot().getElement();
669      else
670        children = property.getStructure().getSnapshot().getElement();
671    }
672    
673    @Override
674    public int compare(Element e0, Element e1) {
675      int i0 = find(e0);
676      int i1 = find(e1);
677      return (i0 < i1) ? -1 : ((i0 == i1) ? 0 : 1);
678    }
679    private int find(Element e0) {
680      int i =  e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) :  children.indexOf(e0.property.getDefinition());
681      return i; 
682    }
683
684  }
685
686}