001package org.hl7.fhir.r5.model;
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
031import java.io.Serializable;
032import java.util.ArrayList;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036
037import org.hl7.fhir.exceptions.FHIRException;
038import org.hl7.fhir.instance.model.api.IBase;
039import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
040import org.hl7.fhir.utilities.FhirPublication;
041import org.hl7.fhir.utilities.Utilities;
042import org.hl7.fhir.utilities.validation.ValidationMessage;
043import org.hl7.fhir.utilities.xhtml.XhtmlNode;
044
045import ca.uhn.fhir.model.api.IElement;
046
047public abstract class Base implements Serializable, IBase, IElement {
048
049  public enum ValidationReason {
050    Validation, MatchingSlice, Expression    
051  }
052  
053  public enum ProfileSource {
054    BaseDefinition, ConfigProfile, MetaProfile, ProfileDependency, FromExpression,  GlobalProfile
055  }
056  
057  public static class ValidationMode {
058    private ValidationReason reason;
059    private ProfileSource source;
060    public ValidationMode(ValidationReason reason, ProfileSource source) {
061      super();
062      this.reason = reason;
063      this.source = source;
064    }
065    public ValidationReason getReason() {
066      return reason;
067    }
068    public ProfileSource getSource() {
069      return source;
070    }
071    public ValidationMode withSource(ProfileSource source) {
072      ValidationMode res = new ValidationMode(reason, source);
073      return res;
074    }
075    public ValidationMode withReason(ValidationReason reason) {
076      ValidationMode res = new ValidationMode(reason, source);
077      return res;
078    }
079  }
080  
081  public class ValidationInfo {
082    private StructureDefinition structure;
083    private ElementDefinition definition;
084    private ValidationReason reason;
085    private ProfileSource source;
086    private boolean valid;
087    
088    public ValidationInfo(StructureDefinition structure, ElementDefinition definition, ValidationMode mode) {
089      super();
090      this.structure = structure;
091      this.definition = definition;
092      this.reason = mode.reason;
093      this.source = mode.source;
094    }
095    
096    public StructureDefinition getStructure() {
097      return structure;
098    }
099    
100    public ElementDefinition getDefinition() {
101      return definition;
102    }
103    
104    public ValidationReason getReason() {
105      return reason;
106    }
107
108    public ProfileSource getSource() {
109      return source;
110    }
111
112    public boolean isValid() {
113      return valid;
114    }
115    public void setValid(boolean valid) {
116      this.valid = valid;
117    }
118
119  }
120
121  private static ThreadLocal<Boolean> copyUserData = new ThreadLocal<>();
122  
123  public static boolean isCopyUserData() {
124    Boolean res = copyUserData.get();
125    return res != null && res;
126  }
127  
128  public static void setCopyUserData(boolean value) {
129    copyUserData.set(value);
130  }
131  
132  /**
133   * User appended data items - allow users to add extra information to the class
134   */
135  private transient Map<String, Object> userData; 
136
137  /**
138   * Post Validation Definition information
139   */
140  private transient List<ValidationInfo> validationInfo;
141
142  /**
143   * Round tracking xml comments for testing convenience
144   */
145  private List<String> formatCommentsPre; 
146   
147  /**
148   * Round tracking xml comments for testing convenience
149   */
150  private List<String> formatCommentsPost;
151
152  private List<ValidationMessage> validationMessages; 
153   
154  
155  public Object getUserData(String name) {
156    if (userData == null)
157      return null;
158    return userData.get(name);
159  }
160  
161  public void setUserData(String name, Object value) {
162    if (userData == null)
163      userData = new HashMap<String, Object>();
164    userData.put(name, value);
165  }
166
167  public void clearUserData(String name) {
168    if (userData != null)
169      userData.remove(name);
170  }
171 
172  
173  public void setUserDataINN(String name, Object value) {
174    if (value == null)
175      return;
176    
177    if (userData == null)
178      userData = new HashMap<String, Object>();
179    userData.put(name, value);
180  }
181
182  public boolean hasUserData(String name) {
183    if (userData == null)
184      return false;
185    else
186      return userData.containsKey(name) && (userData.get(name) != null);
187  }
188
189        public String getUserString(String name) {
190    Object ud = getUserData(name);
191    if (ud == null)
192      return null;
193    if (ud instanceof String)
194      return (String) ud;
195    return ud.toString();
196  }
197
198  public int getUserInt(String name) {
199    if (!hasUserData(name))
200      return 0;
201    return (Integer) getUserData(name);
202  }
203
204  public void copyUserData(Base other) {
205    if (other.userData != null) {
206      if (userData == null) {
207        userData = new HashMap<>();
208      }
209      userData.putAll(other.userData);
210    }
211  }      
212
213  public boolean hasFormatComment() {
214    return hasFormatCommentPre() || hasFormatCommentPost();
215  }
216  
217  public boolean hasFormatCommentPre() {
218    return formatCommentsPre != null && !formatCommentsPre.isEmpty();
219  }
220  
221  public boolean hasFormatCommentPost() {
222    return formatCommentsPost != null && !formatCommentsPost.isEmpty();
223  }
224  
225  public List<String> getFormatCommentsPre() {
226    if (formatCommentsPre == null)
227      formatCommentsPre = new ArrayList<String>();
228    return formatCommentsPre;
229  }
230  
231  public List<String> getFormatCommentsPost() {
232    if (formatCommentsPost == null)
233      formatCommentsPost = new ArrayList<String>();
234    return formatCommentsPost;
235  }  
236  
237
238  public void copyFormatComments(Base other) {
239    if (other.hasFormatComment()) {
240      formatCommentsPre = new ArrayList<>();
241      formatCommentsPre.addAll(other.formatCommentsPre);      
242    } else {
243      formatCommentsPre = null;
244    }
245  }
246  
247
248  public void addFormatCommentsPre(List<String> comments) {
249    if (comments != null && !comments.isEmpty()) {
250      getFormatCommentsPre().addAll(comments); 
251    }    
252  }
253
254  public void addFormatCommentsPost(List<String> comments) {
255    if (comments != null && !comments.isEmpty()) {
256      getFormatCommentsPost().addAll(comments); 
257    }    
258  }
259
260        // these 3 allow evaluation engines to get access to primitive values
261  
262  /**
263   * @return true if the data type is a primitive type and might have a primitive value 
264   *   (which will be accessed as a string, irrespective of the stated value)
265   */
266        public boolean isPrimitive() {
267                return false;
268        }
269        
270        /**
271         * @return true if the type is boolean, and the primitive value can only be 'true' or 'false'
272         */
273  public boolean isBooleanPrimitive() {
274    return false;
275  }
276
277  /**
278   * @return true if the type is primitive, and there's value (e.g. no Data-Absent-Reason extension etc)
279   */
280  public boolean hasPrimitiveValue() {
281    return primitiveValue() != null;
282  }
283  
284  /**
285   * @return true if the type is primitive, and there could be a value (irrespective of whether it's present e.g. no Data-Absent-Reason extension etc)
286   */
287  public boolean canHavePrimitiveValue() {
288    return false;
289  }
290  
291        /**
292         * @return the primitive value if there is one, as a string irrespective of the actual type (e.g. dates converted to their FHIR string representation)
293         *    return null if the value is not a primitive or there is no value (might be extensions instead)
294         */
295        public String primitiveValue() {
296                return null;
297        }
298        
299  /**
300   * @return true if the type is date|dateTime|instant, and the primitive value is a date/time of some precision
301   */
302  public boolean isDateTime() {
303    return false;
304  }
305
306  /**
307   * @return the date/time value if there is one, or null
308   */
309  public BaseDateTimeType dateTimeValue() {
310    return null;
311  }
312
313  /**
314   * @return the FHIR type name of the instance (not the java class name)
315   */
316        public abstract String fhirType() ;
317
318        /**
319         * Note that this is potentially misleading on ElementDefinition that has a 'type' 
320         * property - don't mistakenly use this thinking it's going to look at ElementDefinition.type
321   *
322         * @param name - fhir type name
323         * @return- true if it 'has' this type (including by specialization)
324         */
325        public boolean hasType(String... name) {
326                String t = fhirType();
327                for (String n : name) {
328                  if (n.equalsIgnoreCase(t))
329                        return true;
330                  if (n.contains(".")) {
331                    String[] p = n.split("\\.");
332                    if (p.length == 2 && Utilities.existsInList(p[0], "FHIR", "CDA") && p[1].equalsIgnoreCase(t))
333                return true;
334                  }
335                }
336                return false;
337        }
338        
339        protected void listChildren(List<Property> result) {
340          // nothing
341        }
342        
343        public Base setProperty(String name, Base value) throws FHIRException {
344          throw new FHIRException("Attempt to set unknown property "+name);
345        }
346        
347        public Base addChild(String name) throws FHIRException {
348    throw new FHIRException("Attempt to add child with unknown name "+name);
349  }
350
351        public void removeChild(String name, Base value) throws FHIRException {
352    throw new FHIRException("Attempt to remove child with unknown name "+name);
353        }
354  /**
355   * Supports iterating the children elements in some generic processor or browser
356   * All defined children will be listed, even if they have no value on this instance
357   * 
358   * Note that the actual content of primitive or xhtml elements is not iterated explicitly.
359   * To find these, the processing code must recognise the element as a primitive, typecast
360   * the value to a {@link DataType}, and examine the value
361   *  
362   * @return a list of all the children defined for this element
363   */
364  public List<Property> children() {
365        List<Property> result = new ArrayList<Property>();
366        listChildren(result);
367        return result;
368  }
369
370  public Property getChildByName(String name) {
371    List<Property> children = new ArrayList<Property>();
372    listChildren(children);
373    for (Property c : children)
374      if (c.getName().equals(name) || c.getName().equals(name+"[x]")) {
375        return c;
376      }
377      return null;
378    }
379  
380  public List<Base> listChildrenByName(String name) throws FHIRException {
381    List<Base> result = new ArrayList<Base>();
382        for (Base b : listChildrenByName(name, true))
383                if (b != null)
384                  result.add(b);
385    return result;
386  }
387
388  public Base getChildValueByName(String name) {
389    Property p = getChildByName(name);
390    if (p != null && p.hasValues()) {
391      if (p.getValues().size() > 1) {
392        throw new Error("Too manye values for "+name+" found");
393      } else {
394        return p.getValues().get(0);        
395      }
396    }
397    return null;
398  }
399  
400  public Base[] listChildrenByName(String name, boolean checkValid) throws FHIRException {
401        if (name.equals("*")) {
402                List<Property> children = new ArrayList<Property>();
403                listChildren(children);
404                List<Base> result = new ArrayList<Base>();
405                for (Property c : children)
406                                result.addAll(c.getValues());
407                return result.toArray(new Base[result.size()]);
408        }
409        else
410        return getProperty(name.hashCode(), name, checkValid);
411  }
412
413        public boolean isEmpty() {
414          return true; // userData does not count
415  }
416
417        public boolean equalsDeep(Base other) {
418          return other != null;
419  }  
420  
421        public boolean equalsShallow(Base other) {
422          return other != null;
423  }  
424  
425  public static boolean compareDeep(String s1, String s2, boolean allowNull) {
426    if (allowNull) {
427      boolean noLeft = s1 == null || Utilities.noString(s1);
428      boolean noRight = s2 == null || Utilities.noString(s2);
429      if (noLeft && noRight) {
430        return true;
431      }
432    }
433    if (s1 == null || s2 == null)
434      return false;
435    return s1.equals(s2);   
436  }
437  
438        public static boolean compareDeep(List<? extends Base> e1, List<? extends Base> e2, boolean allowNull) {
439                if (noList(e1) && noList(e2) && allowNull)
440                        return true;
441                if (noList(e1) || noList(e2))
442                        return false;
443                if (e1.size() != e2.size())
444                        return false;
445                for (int i = 0; i < e1.size(); i++) {
446                        if (!compareDeep(e1.get(i), e2.get(i), allowNull))
447                                return false;
448                }
449                return true;
450        }
451        
452        private static boolean noList(List<? extends Base> list) {
453    return list == null || list.isEmpty() || (list.size() == 1 && list.get(0).isEmpty());
454  }
455
456        public static boolean compareDeep(Base e1, Base e2, boolean allowNull) {
457                if (allowNull) {
458                        boolean noLeft = e1 == null || e1.isEmpty();
459                        boolean noRight = e2 == null || e2.isEmpty();
460                        if (noLeft && noRight) {
461                        return true;
462                        }
463                }
464                if (e1 == null || e2 == null)
465                        return false;
466                if (e2.isMetadataBased() && !e1.isMetadataBased()) // respect existing order for debugging consistency; outcome must be the same either way
467                        return e2.equalsDeep(e1);
468                else
469                return e1.equalsDeep(e2);
470        }
471        
472        public static boolean compareDeep(XhtmlNode div1, XhtmlNode div2, boolean allowNull) {
473                if (div1 == null && div2 == null && allowNull)
474                        return true;
475                if (div1 == null || div2 == null)
476                        return false;
477                return div1.equalsDeep(div2);
478  }
479
480
481        public static boolean compareValues(List<? extends PrimitiveType> e1, List<? extends PrimitiveType> e2, boolean allowNull) {
482                if (e1 == null && e2 == null && allowNull)
483                        return true;
484                if (e1 == null || e2 == null)
485                        return false;
486                if (e1.size() != e2.size())
487                        return false;
488                for (int i = 0; i < e1.size(); i++) {
489                        if (!compareValues(e1.get(i), e2.get(i), allowNull))
490                                return false;
491                }
492                return true;
493        }
494
495        public static boolean compareValues(PrimitiveType e1, PrimitiveType e2, boolean allowNull) {
496                boolean noLeft = e1 == null || e1.isEmpty();
497                boolean noRight = e2 == null || e2.isEmpty();
498      if (noLeft && noRight && allowNull) {
499                        return true;
500      }
501                if (noLeft != noRight)
502                        return false;
503                return e1.equalsShallow(e2);
504  }
505        
506        protected boolean isMetadataBased() {
507        return false;
508        }
509
510        public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
511                if (checkValid)
512                        throw new FHIRException("Attempt to read invalid property '"+name+"' on type "+fhirType());
513        return null; 
514        }
515
516        public Base setProperty(int hash, String name, Base value) throws FHIRException {
517                throw new FHIRException("Attempt to write to invalid property '"+name+"' on type "+fhirType());
518        }
519
520        public Base makeProperty(int hash, String name) throws FHIRException {
521                throw new FHIRException("Attempt to make an invalid property '"+name+"' on type "+fhirType());
522        }
523
524        public String[] getTypesForProperty(int hash, String name) throws FHIRException {
525    throw new FHIRException("Attempt to get types for an invalid property '"+name+"' on type "+fhirType());
526        }
527        
528        public static boolean equals(String v1, String v2) {
529        if (v1 == null && v2 == null)
530                return true;
531        else if (v1 == null || v2 == null)
532        return false;
533        else
534                return v1.equals(v2);
535        }
536
537  public boolean isResource() {
538    return false;
539  }
540        
541
542  public abstract String getIdBase();
543  public abstract void setIdBase(String value);
544
545  public Property getNamedProperty(String _name) throws FHIRException {
546    return getNamedProperty(_name.hashCode(), _name, false);
547  }
548  public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
549    if (_checkValid)
550      throw new FHIRException("Attempt to read invalid property '"+_name+"' on type "+fhirType());
551    return null; 
552  }
553
554  public abstract Base copy();
555  
556  public void copyValues(Base dst) {  
557    if (isCopyUserData() && userData != null) {
558      dst.userData = new HashMap<>();
559      dst.userData.putAll(userData);
560    }
561  }
562
563  /**
564   * return XHTML if this is an XHTML node, else null
565   * 
566   * @return
567   */
568  public XhtmlNode getXhtml() {
569    return null;
570  }
571
572
573  public boolean hasValidationInfo() {
574    return validationInfo != null;
575  }
576
577  /**
578   * A list of definitions that the validator matched this element to.
579   * Note that the element doesn't have to conform to these definitions - check whether they're valid 
580   * Some of the definitions will be noted because of slice matching
581   * 
582   * @return
583   */
584  public List<ValidationInfo> getValidationInfo() {
585    return validationInfo;
586  }
587
588  public ValidationInfo addDefinition(StructureDefinition structure, ElementDefinition defn, ValidationMode mode) {
589    if (validationInfo == null) {
590      validationInfo = new ArrayList<>();
591    }
592    for (ValidationInfo t : validationInfo) {
593      if (t.structure == structure && t.definition == defn && t.reason == mode.reason && t.source == mode.source) {
594        return t;
595      }
596    }
597    ValidationInfo vi = new ValidationInfo(structure, defn, mode);
598    this.validationInfo.add(vi);
599    return vi;
600  }
601  
602
603  public boolean hasValidated(StructureDefinition sd, ElementDefinition ed) {
604    if (validationInfo != null) {
605      for (ValidationInfo vi : validationInfo) {
606        if (vi.definition == ed && vi.structure == sd) {
607          return true;
608        }
609      }
610    }
611    return false;
612  }
613  
614  // validation messages: the validator does not populate these (yet)
615  public Base addValidationMessage(ValidationMessage msg) {
616    if (validationMessages == null) {
617      validationMessages = new ArrayList<>();
618    }
619    validationMessages.add(msg);
620    return this;
621  }
622  
623  public boolean hasValidationMessages() {
624    return validationMessages != null && !validationMessages.isEmpty();
625  }
626  
627  public List<ValidationMessage> getValidationMessages() {
628    return validationMessages != null ? validationMessages : new ArrayList<>();
629  }
630
631  public abstract FhirPublication getFHIRPublicationVersion();
632}