Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 28 additions & 0 deletions src/Hl7.Fhir.Base/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,32 @@
<Left>lib/netstandard2.0/Hl7.Fhir.Base.dll</Left>
<Right>lib/net8.0/Hl7.Fhir.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Hl7.Fhir.Model.Parameters.get_Item(System.String)</Target>
<Left>lib/net8.0/Hl7.Fhir.Base.dll</Left>
<Right>lib/net8.0/Hl7.Fhir.Base.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Hl7.Fhir.Utility.ReflectionHelper.IsTypedCollection(System.Type)</Target>
<Left>lib/net8.0/Hl7.Fhir.Base.dll</Left>
<Right>lib/net8.0/Hl7.Fhir.Base.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Hl7.Fhir.Model.Parameters.get_Item(System.String)</Target>
<Left>lib/netstandard2.0/Hl7.Fhir.Base.dll</Left>
<Right>lib/netstandard2.0/Hl7.Fhir.Base.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Hl7.Fhir.Utility.ReflectionHelper.IsTypedCollection(System.Type)</Target>
<Left>lib/netstandard2.0/Hl7.Fhir.Base.dll</Left>
<Right>lib/netstandard2.0/Hl7.Fhir.Base.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
2 changes: 1 addition & 1 deletion src/Hl7.Fhir.Base/Introspection/PropertyMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public static bool TryCreate(PropertyInfo prop, [NotNullWhen(true)] out Property
// FHIR repeating elements. If we would allow this, we'd also have stuff like `string` and binary
// data as repeating element, and would need to exclude these exceptions on a case by case basis.
// This is pretty ugly, so we prefer to not support arrays - you should use lists instead.
bool isCollection = ReflectionHelper.IsTypedCollection(prop.PropertyType) && !prop.PropertyType.IsArray;
bool isCollection = ReflectionHelper.IsTypedList(prop.PropertyType) && !prop.PropertyType.IsArray;

var cardinalityAttr = ClassMapping.GetAttribute<CardinalityAttribute>(prop, release);

Expand Down
91 changes: 91 additions & 0 deletions src/Hl7.Fhir.Base/Model/Base.Dictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Hl7.Fhir.Model;

public abstract partial class Base: IReadOnlyDictionary<string,object>, IDictionary<string, object>
{
private object get(string key) =>
this.TryGetValue(key, out var value)
? value
: throw new KeyNotFoundException($"Element '{key}' is not a known FHIR element or has no value.");

public IReadOnlyDictionary<string, object> AsReadOnlyDictionary() => this;
public IDictionary<string, object> AsDictionary() => this;

#region IReadOnlyDictionary

IEnumerable<string> IReadOnlyDictionary<string, object>.Keys => GetElementPairs().Select(kvp => kvp.Key);
IEnumerable<object> IReadOnlyDictionary<string, object>.Values => GetElementPairs().Select(kvp => kvp.Value);
int IReadOnlyCollection<KeyValuePair<string, object>>.Count => GetElementPairs().Count();
object IReadOnlyDictionary<string, object>.this[string key] => get(key);

bool IReadOnlyDictionary<string, object>.TryGetValue(string key, out object value) =>
TryGetValue(key, out value!);

bool IReadOnlyDictionary<string, object>.ContainsKey(string key) => TryGetValue(key, out _);

IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator() =>
GetElementPairs().GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetElementPairs().GetEnumerator();

#endregion

#region IDictionary

void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) =>
AsDictionary().Add(item.Key, item.Value);

void ICollection<KeyValuePair<string, object>>.Clear()
{
// Slow....
foreach (var kvp in this)
SetValue(kvp.Key, null);
}

bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) =>
TryGetValue(item.Key, out _); // we don't care about the item, we cannot have the same key twice.

void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
foreach (var kvp in this)
array[arrayIndex++] = kvp;
}

bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) =>
AsDictionary().Remove(item.Key);

int ICollection<KeyValuePair<string, object>>.Count => AsReadOnlyDictionary().Count;
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
ICollection<object> IDictionary<string, object>.Values => AsReadOnlyDictionary().Values.ToList();
ICollection<string> IDictionary<string, object>.Keys => AsReadOnlyDictionary().Keys.ToList();
bool IDictionary<string, object>.TryGetValue(string key, out object value) => TryGetValue(key, out value!);

object IDictionary<string, object>.this[string key]
{
get => this.AsReadOnlyDictionary()[key];
set => SetValue(key, value);
}

void IDictionary<string, object>.Add(string key, object value)
{
if (TryGetValue(key, out _))
throw new ArgumentException($"An element with the key '{key}' already exists in the dictionary.");

SetValue(key, value);
}

bool IDictionary<string, object>.ContainsKey(string key) => TryGetValue(key, out _);

bool IDictionary<string, object>.Remove(string key)
{
var existed = TryGetValue(key, out _);
SetValue(key, null);
return existed;
}

#endregion
}
144 changes: 94 additions & 50 deletions src/Hl7.Fhir.Base/Model/Base.cs
Original file line number Diff line number Diff line change
@@ -1,90 +1,134 @@
/*
Copyright (c) 2011-2012, HL7, Inc
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

using Hl7.Fhir.Introspection;
#nullable enable

using Hl7.Fhir.Model;
using Hl7.Fhir.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using DataType = System.ComponentModel.DataAnnotations.DataType;

namespace Hl7.Fhir.Model;

namespace Hl7.Fhir.Model
public abstract partial class Base : IDeepCopyable, IDeepComparable,
IAnnotated, IAnnotatable, IValidatableObject, INotifyPropertyChanged
{
public abstract partial class Base : IDeepCopyable, IDeepComparable,
IAnnotated, IAnnotatable, IValidatableObject, INotifyPropertyChanged, IReadOnlyDictionary<string, object>
{
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext) => Enumerable.Empty<ValidationResult>();
/// <summary>
/// FHIR Type Name
/// </summary>
public virtual string TypeName => GetType().Name;


private Dictionary<string, object>? _overflow = null;

/// <summary>
/// A dictionary containing all elements that are not explicitly defined in the class.
/// </summary>
protected Dictionary<string, object> Overflow =>
LazyInitializer.EnsureInitialized(ref _overflow, () => new Dictionary<string, object>())!;

public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext) => [];

#region << Annotations >>
[NonSerialized]
private AnnotationList _annotations = null;
#region << Annotations >>

private AnnotationList annotations => LazyInitializer.EnsureInitialized(ref _annotations, () => new());
[NonSerialized] private AnnotationList? _annotations = null;

public IEnumerable<object> Annotations(Type type) => annotations.OfType(type);
private AnnotationList annotations => LazyInitializer.EnsureInitialized(ref _annotations, () => [])!;

public void AddAnnotation(object annotation) => annotations.AddAnnotation(annotation);
public IEnumerable<object> Annotations(Type type) => annotations.OfType(type);

public void RemoveAnnotations(Type type) => annotations.RemoveAnnotations(type);
#endregion
public void AddAnnotation(object annotation) => annotations.AddAnnotation(annotation);

#region INotifyPropertyChanged
public void RemoveAnnotations(Type type) => annotations.RemoveAnnotations(type);

public event PropertyChangedEventHandler PropertyChanged;
#endregion

protected virtual void OnPropertyChanged(String property) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
#region INotifyPropertyChanged

#endregion
public event PropertyChangedEventHandler? PropertyChanged;

public IReadOnlyDictionary<string, object> AsReadOnlyDictionary() => this;
protected virtual void OnPropertyChanged(string property) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));

#region IReadOnlyDictionary
IEnumerable<string> IReadOnlyDictionary<string, object>.Keys => GetElementPairs().Select(kvp => kvp.Key);
#endregion

IEnumerable<object> IReadOnlyDictionary<string, object>.Values => GetElementPairs().Select(kvp => kvp.Value);
protected virtual Base SetValue(string key, object? value)
{
if (value is null)
Overflow.Remove(key);
else
Overflow[key] = value;

return this;
}

int IReadOnlyCollection<KeyValuePair<string, object>>.Count => GetElementPairs().Count();
protected virtual bool TryGetValue(string key, [NotNullWhen(true)] out object? value) =>
Overflow.TryGetValue(key, out value);

object IReadOnlyDictionary<string, object>.this[string key] => TryGetValue(key, out var value) ? value : throw new KeyNotFoundException();
protected virtual IEnumerable<KeyValuePair<string, object>> GetElementPairs() => Overflow;

bool IReadOnlyDictionary<string, object>.ContainsKey(string key) => TryGetValue(key, out _);
// TODO bring Children + NamedChildren over as well.
}

IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator() => GetElementPairs().GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetElementPairs().GetEnumerator();
/// <summary>
/// A dynamic data type that can hold any element.
/// </summary>
public class DynamicDataType : DataType
{
public void Add(string arg1, object arg2) => this.SetValue(arg1, arg2);

bool IReadOnlyDictionary<string, object>.TryGetValue(string key, out object value) => TryGetValue(key, out value);
#endregion
public object this[string key]
{
get => this.AsReadOnlyDictionary()[key];
set => SetValue(key, value);
}
}


/// <summary>
/// A dynamic resource that can hold any element.
/// </summary>
public class DynamicResource : Resource
{
public void Add(string arg1, object arg2) => this.SetValue(arg1, arg2);

public object this[string key]
{
get => this.AsReadOnlyDictionary()[key];
set => SetValue(key, value);
}
}
51 changes: 50 additions & 1 deletion src/Hl7.Fhir.Base/Model/Generated/Attachment.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// <auto-generated/>
// Contents of: hl7.fhir.r5.expansions#5.0.0, hl7.fhir.r5.core#5.0.0
// Contents of: hl7.fhir.r5.expansions@5.0.0, hl7.fhir.r5.core@5.0.0

using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -638,6 +638,55 @@ protected override bool TryGetValue(string key, out object value)

}

protected override Base SetValue(string key, object value)
{
switch (key)
{
case "contentType":
ContentTypeElement = (Hl7.Fhir.Model.Code)value;
return this;
case "language":
LanguageElement = (Hl7.Fhir.Model.Code)value;
return this;
case "data":
DataElement = (Hl7.Fhir.Model.Base64Binary)value;
return this;
case "url":
UrlElement = (Hl7.Fhir.Model.FhirUrl)value;
return this;
case "size":
SizeElement = (Hl7.Fhir.Model.Integer64)value;
return this;
case "hash":
HashElement = (Hl7.Fhir.Model.Base64Binary)value;
return this;
case "title":
TitleElement = (Hl7.Fhir.Model.FhirString)value;
return this;
case "creation":
CreationElement = (Hl7.Fhir.Model.FhirDateTime)value;
return this;
case "height":
HeightElement = (Hl7.Fhir.Model.PositiveInt)value;
return this;
case "width":
WidthElement = (Hl7.Fhir.Model.PositiveInt)value;
return this;
case "frames":
FramesElement = (Hl7.Fhir.Model.PositiveInt)value;
return this;
case "duration":
DurationElement = (Hl7.Fhir.Model.FhirDecimal)value;
return this;
case "pages":
PagesElement = (Hl7.Fhir.Model.PositiveInt)value;
return this;
default:
return base.SetValue(key, value);
}

}

protected override IEnumerable<KeyValuePair<string, object>> GetElementPairs()
{
foreach (var kvp in base.GetElementPairs()) yield return kvp;
Expand Down
Loading