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