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