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