diff --git a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml index 3cc871a611..66fb3ee335 100644 --- a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml @@ -43,6 +43,13 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + F:Hl7.Fhir.Model.Resource.SyncLock + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 F:Hl7.Fhir.Serialization.FhirJsonException.INCOMPATIBLE_SIMPLE_VALUE_CODE @@ -316,6 +323,13 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Model.Resource.get_HasVersionId + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Rest.ContentType.BuildContentType(Hl7.Fhir.Rest.ResourceFormat,System.String) @@ -400,6 +414,13 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Utility.AnnotatableExtensions.RemoveAnnotations``1(Hl7.Fhir.Utility.IAnnotatable) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Utility.ReflectionHelper.IsTypedCollection(System.Type) diff --git a/src/Hl7.Fhir.Base/ElementModel/DomNode.cs b/src/Hl7.Fhir.Base/ElementModel/DomNode.cs index bd1e4b0977..6b7c152cbc 100644 --- a/src/Hl7.Fhir.Base/ElementModel/DomNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/DomNode.cs @@ -14,44 +14,48 @@ using System.Linq; using System.Threading; -namespace Hl7.Fhir.ElementModel +namespace Hl7.Fhir.ElementModel; + +public class DomNode : IAnnotatable where T : DomNode { - public class DomNode : IAnnotatable where T : DomNode - { - public string Name { get; set; } = null!; + public string Name { get; set; } = null!; - private List? _childList; + private List? _childList; - protected List ChildList - { - get => LazyInitializer.EnsureInitialized(ref _childList, () => [])!; - set => _childList = value; - } + protected List ChildList + { + get => LazyInitializer.EnsureInitialized(ref _childList, () => [])!; + set => _childList = value; + } - internal IEnumerable ChildrenInternal(string? name = null) => - name == null ? ChildList : ChildList.Where(c => c.Name.MatchesPrefix(name)); + internal IEnumerable ChildrenInternal(string? name = null) => + name == null ? ChildList : ChildList.Where(c => c.Name.MatchesPrefix(name)); - public T? Parent { get; protected set; } + public T? Parent { get; protected set; } - public DomNodeList this[string name] => new (ChildrenInternal(name)); + public DomNodeList this[string name] => new (ChildrenInternal(name)); - public T this[int index] => ChildList[index]; + public T this[int index] => ChildList[index]; - #region << Annotations >> - private AnnotationList? _annotations; - protected AnnotationList AnnotationsInternal => LazyInitializer.EnsureInitialized(ref _annotations, () => [])!; + #region << Annotations >> + private AnnotationList? _annotations; + protected AnnotationList AnnotationsInternal => LazyInitializer.EnsureInitialized(ref _annotations, () => [])!; - protected bool HasAnnotations => _annotations is not null && !_annotations.IsEmpty; + protected bool HasAnnotations => _annotations is not null && !_annotations.IsEmpty; - public void AddAnnotation(object annotation) - { - AnnotationsInternal.AddAnnotation(annotation); - } + public void AddAnnotation(object annotation) + { + AnnotationsInternal.AddAnnotation(annotation); + } - public void RemoveAnnotations(Type type) - { - AnnotationsInternal.RemoveAnnotations(type); - } - #endregion + public void RemoveAnnotations(Type type) + { + AnnotationsInternal.RemoveAnnotations(type); } -} + + public virtual IEnumerable Annotations(Type type) => AnnotationsInternal.Annotations(type); + + #endregion + + +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/ElementNode.cs b/src/Hl7.Fhir.Base/ElementModel/ElementNode.cs index 6298866f86..44c4216c5b 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ElementNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ElementNode.cs @@ -17,332 +17,326 @@ using System.Threading; using P = Hl7.Fhir.ElementModel.Types; -namespace Hl7.Fhir.ElementModel +namespace Hl7.Fhir.ElementModel; + +public class ElementNode : DomNode, ITypedElement, IShortPathGenerator { - public class ElementNode : DomNode, ITypedElement, IAnnotated, IShortPathGenerator + /// + /// Creates an implementation of ITypedElement that represents a primitive value + /// + /// + /// + // HACK: For now, allow a Quantity (which is NOT a primitive) in the .Value property + // of ITypedElement. This is a temporary situation to make a quick & dirty upgrade of + // FP to Normative (with Quantity support) possible. + public static ITypedElement ForPrimitive(object value) { - /// - /// Creates an implementation of ITypedElement that represents a primitive value - /// - /// - /// - // HACK: For now, allow a Quantity (which is NOT a primitive) in the .Value property - // of ITypedElement. This is a temporary situation to make a quick & dirty upgrade of - // FP to Normative (with Quantity support) possible. - public static ITypedElement ForPrimitive(object value) + return value switch { - return value switch - { - P.Quantity q => PrimitiveElement.ForQuantity(q), - _ => new PrimitiveElement(value, useFullTypeName: true) - }; - } + P.Quantity q => PrimitiveElement.ForQuantity(q), + _ => new PrimitiveElement(value, useFullTypeName: true) + }; + } - /// - /// Converts a .NET primitive to the expected object value to use in the - /// value property of ITypedElement. - /// - /// - /// - /// - public static bool TryConvertToElementValue(object? value, [NotNullWhen(true)] out object? primitiveValue) - { - primitiveValue = conv(); - return primitiveValue is not null; + /// + /// Converts a .NET primitive to the expected object value to use in the + /// value property of ITypedElement. + /// + /// + /// + /// + public static bool TryConvertToElementValue(object? value, [NotNullWhen(true)] out object? primitiveValue) + { + primitiveValue = conv(); + return primitiveValue is not null; - object? conv() + object? conv() + { + // NOTE: Keep Any.TryConvertToSystemValue, TypeSpecifier.TryGetNativeType and TypeSpecifier.ForNativeType in sync + switch (value) { - // NOTE: Keep Any.TryConvertToSystemValue, TypeSpecifier.TryGetNativeType and TypeSpecifier.ForNativeType in sync - switch (value) - { - case P.Any a: - return a; - case bool b: - return b; - case string s: - return s; - case char c: - return new string(c, 1); - case int _: - case short _: - case ushort _: - case uint _: - return Convert.ToInt32(value); - case long _: - case ulong _: - return Convert.ToInt64(value); - case DateTimeOffset dto: - return P.DateTime.FromDateTimeOffset(dto); - case float _: - case double _: - case decimal _: - return Convert.ToDecimal(value); - case Enum en: - return en.GetLiteral(); - case Uri u: - return u.OriginalString; - default: - return null; - } + case P.Any a: + return a; + case bool b: + return b; + case string s: + return s; + case char c: + return new string(c, 1); + case int _: + case short _: + case ushort _: + case uint _: + return Convert.ToInt32(value); + case long _: + case ulong _: + return Convert.ToInt64(value); + case DateTimeOffset dto: + return P.DateTime.FromDateTimeOffset(dto); + case float _: + case double _: + case decimal _: + return Convert.ToDecimal(value); + case Enum en: + return en.GetLiteral(); + case Uri u: + return u.OriginalString; + default: + return null; } } + } - /// - /// Create a fixed length set of values (but also support variable number of parameter values) - /// - /// - /// - public static IEnumerable CreateList(params object[] values) => - values switch - { - null => EmptyList, - [var one] => [toTe(one)!], - _ => values.Select(toTe).ToList()! - }; - - /// - /// Create a variable list of values using an enumeration - /// - so doesn't have to be converted to an array in memory (issue with larger dynamic lists) - /// - /// - /// - public static IEnumerable CreateList(IEnumerable values) => values switch + /// + /// Create a fixed length set of values (but also support variable number of parameter values) + /// + /// + /// + public static IEnumerable CreateList(params object[] values) => + values switch { null => EmptyList, + [var one] => [toTe(one)!], _ => values.Select(toTe).ToList()! }; - private static ITypedElement? toTe(object? value) => value switch - { - null => null, - ITypedElement element => element, - _ => ForPrimitive(value) - }; + /// + /// Create a variable list of values using an enumeration + /// - so doesn't have to be converted to an array in memory (issue with larger dynamic lists) + /// + /// + /// + public static IEnumerable CreateList(IEnumerable values) => values switch + { + null => EmptyList, + _ => values.Select(toTe).ToList()! + }; + private static ITypedElement? toTe(object? value) => value switch + { + null => null, + ITypedElement element => element, + _ => ForPrimitive(value) + }; - public static readonly IEnumerable EmptyList = []; - public IEnumerable Children(string? name = null) => ChildrenInternal(name); - private ElementNode(string name, object? value, string? instanceType, IElementDefinitionSummary? definition) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - InstanceType = instanceType; - Value = value; - Definition = definition; - } + public static readonly IEnumerable EmptyList = []; + public IEnumerable Children(string? name = null) => ChildrenInternal(name); - private IReadOnlyCollection _childDefinitions = null!; + private ElementNode(string name, object? value, string? instanceType, IElementDefinitionSummary? definition) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + InstanceType = instanceType; + Value = value; + Definition = definition; + } - private IReadOnlyCollection getChildDefinitions(IStructureDefinitionSummaryProvider provider) - { - LazyInitializer.EnsureInitialized(ref _childDefinitions, () => this.ChildDefinitions(provider)); + private IReadOnlyCollection _childDefinitions = null!; - return _childDefinitions; - } + private IReadOnlyCollection getChildDefinitions(IStructureDefinitionSummaryProvider provider) + { + LazyInitializer.EnsureInitialized(ref _childDefinitions, () => this.ChildDefinitions(provider)); - public ElementNode Add(IStructureDefinitionSummaryProvider provider, ElementNode child, string? name = null) - { - if (provider == null) throw new ArgumentNullException(nameof(provider)); - if (child == null) throw new ArgumentNullException(nameof(child)); + return _childDefinitions; + } - importChild(provider, child, name); - return child; - } + public ElementNode Add(IStructureDefinitionSummaryProvider provider, ElementNode child, string? name = null) + { + if (provider == null) throw new ArgumentNullException(nameof(provider)); + if (child == null) throw new ArgumentNullException(nameof(child)); - public ElementNode Add(IStructureDefinitionSummaryProvider provider, string name, object? value = null, string? instanceType = null) - { - if (provider == null) throw new ArgumentNullException(nameof(provider)); - if (name == null) throw new ArgumentNullException(nameof(name)); + importChild(provider, child, name); + return child; + } - var child = new ElementNode(name, value, instanceType, null); + public ElementNode Add(IStructureDefinitionSummaryProvider provider, string name, object? value = null, string? instanceType = null) + { + if (provider == null) throw new ArgumentNullException(nameof(provider)); + if (name == null) throw new ArgumentNullException(nameof(name)); - // Add() will supply the definition and the instanceType (if necessary) - return Add(provider, child); - } + var child = new ElementNode(name, value, instanceType, null); - public void ReplaceWith(IStructureDefinitionSummaryProvider provider, ElementNode node) - { - if (provider == null) throw new ArgumentNullException(nameof(provider)); - if (node == null) throw new ArgumentNullException(nameof(node)); + // Add() will supply the definition and the instanceType (if necessary) + return Add(provider, child); + } - if (Parent == null) throw Error.Argument("Current node is a root node and cannot be replaced."); - Parent.Replace(provider, this, node); - } + public void ReplaceWith(IStructureDefinitionSummaryProvider provider, ElementNode node) + { + if (provider == null) throw new ArgumentNullException(nameof(provider)); + if (node == null) throw new ArgumentNullException(nameof(node)); - public void Replace(IStructureDefinitionSummaryProvider provider, ElementNode oldChild, ElementNode newChild) - { - if (provider == null) throw new ArgumentNullException(nameof(provider)); - if (oldChild == null) throw new ArgumentNullException(nameof(oldChild)); - if (newChild == null) throw new ArgumentNullException(nameof(newChild)); - - int childIndex = ChildList.IndexOf(oldChild); - if (childIndex == -1) throw Error.Argument("Node to be replaced is not one of the children of this node"); - importChild(provider, newChild, oldChild.Name, childIndex); - Remove(oldChild); - } + if (Parent == null) throw Error.Argument("Current node is a root node and cannot be replaced."); + Parent.Replace(provider, this, node); + } - /// - /// Will update the child to reflect it being a child of this element, but will not yet add the child at any position within this element - /// - private void importChild(IStructureDefinitionSummaryProvider provider, ElementNode child, string? name, int? position = null) - { - child.Name = name ?? child.Name; - if (child.Name == null) throw Error.Argument($"The ElementNode given should have its Name property set or the '{nameof(name)}' parameter should be given."); + public void Replace(IStructureDefinitionSummaryProvider provider, ElementNode oldChild, ElementNode newChild) + { + if (provider == null) throw new ArgumentNullException(nameof(provider)); + if (oldChild == null) throw new ArgumentNullException(nameof(oldChild)); + if (newChild == null) throw new ArgumentNullException(nameof(newChild)); + + int childIndex = ChildList.IndexOf(oldChild); + if (childIndex == -1) throw Error.Argument("Node to be replaced is not one of the children of this node"); + importChild(provider, newChild, oldChild.Name, childIndex); + Remove(oldChild); + } - // Remove this child from the current parent (if any), then reassign to me - if (child.Parent != null) child.Parent.Remove(child); - child.Parent = this; + /// + /// Will update the child to reflect it being a child of this element, but will not yet add the child at any position within this element + /// + private void importChild(IStructureDefinitionSummaryProvider provider, ElementNode child, string? name, int? position = null) + { + child.Name = name ?? child.Name; + if (child.Name == null) throw Error.Argument($"The ElementNode given should have its Name property set or the '{nameof(name)}' parameter should be given."); - // If we add a child, we better overwrite it's definition with what - // we think it should be - this way you can safely first create a node representing - // an independently created root for a resource of datatype, and then add it to the tree. - var childDefs = getChildDefinitions(provider ?? throw Error.ArgumentNull(nameof(provider))); - var childDef = childDefs.SingleOrDefault(cd => cd.ElementName == child.Name); + // Remove this child from the current parent (if any), then reassign to me + if (child.Parent != null) child.Parent.Remove(child); + child.Parent = this; - child.Definition = childDef ?? child.Definition; // if we don't know about the definition, stick with the old one (if any) + // If we add a child, we better overwrite it's definition with what + // we think it should be - this way you can safely first create a node representing + // an independently created root for a resource of datatype, and then add it to the tree. + var childDefs = getChildDefinitions(provider ?? throw Error.ArgumentNull(nameof(provider))); + var childDef = childDefs.SingleOrDefault(cd => cd.ElementName == child.Name); - if (child.InstanceType == null && child.Definition != null) + child.Definition = childDef ?? child.Definition; // if we don't know about the definition, stick with the old one (if any) + + if (child.InstanceType == null && child.Definition != null) + { + if (child.Definition.IsResource || child.Definition.IsChoiceElement) { - if (child.Definition.IsResource || child.Definition.IsChoiceElement) - { - // Note that we just demand InstanceType to be set on any kind of choice, even if - // some profile has limited the choice to a single type. Too hard to figure out - // whether it actually allows more than one choice, since the single type might - // also be abstract, and still allow choices. - throw Error.Argument("The ElementNode given should have its InstanceType property set, since the element is a choice or resource."); - - // [EK20190822] This functionality has been removed since it heavily depends on knowledge about - // FHIR types, it would automatically try to derive a *FHIR* type from the given child.Value, - // however, this would not work correctly if the model used is something else than FHIR, - // so this cannot be expected to work correctly in general, and I have chosen to remove - // this. - //// We are in a situation where we are on an polymorphic element, but the caller did not specify - //// the instance type. We can try to auto-set it by deriving it from the instance's type, if it is a primitive - //if (child.Value != null && IsSupportedValue(child.Value)) - // child.InstanceType = TypeSpecifier.ForNativeType(child.Value.GetType()).Name; - } - else - child.InstanceType = child.Definition.Type.Single().GetTypeName(); + // Note that we just demand InstanceType to be set on any kind of choice, even if + // some profile has limited the choice to a single type. Too hard to figure out + // whether it actually allows more than one choice, since the single type might + // also be abstract, and still allow choices. + throw Error.Argument("The ElementNode given should have its InstanceType property set, since the element is a choice or resource."); + + // [EK20190822] This functionality has been removed since it heavily depends on knowledge about + // FHIR types, it would automatically try to derive a *FHIR* type from the given child.Value, + // however, this would not work correctly if the model used is something else than FHIR, + // so this cannot be expected to work correctly in general, and I have chosen to remove + // this. + //// We are in a situation where we are on an polymorphic element, but the caller did not specify + //// the instance type. We can try to auto-set it by deriving it from the instance's type, if it is a primitive + //if (child.Value != null && IsSupportedValue(child.Value)) + // child.InstanceType = TypeSpecifier.ForNativeType(child.Value.GetType()).Name; } - - if (position == null || position >= ChildList.Count) - ChildList.Add(child); else - ChildList.Insert(position.Value, child); - + child.InstanceType = child.Definition.Type.Single().GetTypeName(); } - public static ElementNode Root(IStructureDefinitionSummaryProvider provider, string type, string? name = null, object? value = null) - { - if (provider == null) throw Error.ArgumentNull(nameof(provider)); - if (type == null) throw Error.ArgumentNull(nameof(type)); + if (position == null || position >= ChildList.Count) + ChildList.Add(child); + else + ChildList.Insert(position.Value, child); - var sd = provider.Provide(type); - var definition = sd is not null ? ElementDefinitionSummary.ForRoot(sd) : null; + } - return new ElementNode(name ?? type, value, type, definition); - } + public static ElementNode Root(IStructureDefinitionSummaryProvider provider, string type, string? name = null, object? value = null) + { + if (provider == null) throw Error.ArgumentNull(nameof(provider)); + if (type == null) throw Error.ArgumentNull(nameof(type)); - public static ElementNode FromElement(ITypedElement node, bool recursive = true, IEnumerable? annotationsToCopy = null) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - return buildNode(node, recursive, annotationsToCopy, null); - } + var sd = provider.Provide(type); + var definition = sd is not null ? ElementDefinitionSummary.ForRoot(sd) : null; - private static ElementNode buildNode(ITypedElement node, bool recursive, IEnumerable? annotationsToCopy, ElementNode? parent) + return new ElementNode(name ?? type, value, type, definition); + } + + public static ElementNode FromElement(ITypedElement node, bool recursive = true, IEnumerable? annotationsToCopy = null) + { + if (node == null) throw new ArgumentNullException(nameof(node)); + return buildNode(node, recursive, annotationsToCopy, null); + } + + private static ElementNode buildNode(ITypedElement node, bool recursive, IEnumerable? annotationsToCopy, ElementNode? parent) + { + var me = new ElementNode(node.Name, node.Value, node.InstanceType, node.Definition) { - var me = new ElementNode(node.Name, node.Value, node.InstanceType, node.Definition) - { - Parent = parent - }; + Parent = parent + }; - foreach (var t in annotationsToCopy ?? Enumerable.Empty()) - foreach (var ann in node.Annotations(t)) - me.AddAnnotation(ann); + foreach (var t in annotationsToCopy ?? []) + foreach (var ann in node.Annotations(t)) + me.AddAnnotation(ann); - if (recursive) - me.ChildList.AddRange(node.Children().Select(c => buildNode(c, recursive: true, annotationsToCopy: annotationsToCopy, me))); + if (recursive) + me.ChildList.AddRange(node.Children().Select(c => buildNode(c, recursive: true, annotationsToCopy: annotationsToCopy, me))); - return me; - } + return me; + } - public bool Remove(ElementNode child) - { - if (child == null) throw new ArgumentNullException(nameof(child)); + public bool Remove(ElementNode child) + { + if (child == null) throw new ArgumentNullException(nameof(child)); - var success = ChildList.Remove(child); - if (success) child.Parent = null; + var success = ChildList.Remove(child); + if (success) child.Parent = null; - return success; - } + return success; + } - public ElementNode ShallowCopy() + public ElementNode ShallowCopy() + { + var copy = new ElementNode(Name, Value, InstanceType, Definition) { - var copy = new ElementNode(Name, Value, InstanceType, Definition) - { - Parent = Parent, - ChildList = ChildList - }; + Parent = Parent, + ChildList = ChildList + }; - if (HasAnnotations) - copy.AnnotationsInternal.AddRange(AnnotationsInternal); + if (HasAnnotations) + copy.AnnotationsInternal.AddRange(AnnotationsInternal); - return copy; - } + return copy; + } - public IElementDefinitionSummary? Definition { get; private set; } + public IElementDefinitionSummary? Definition { get; private set; } - public string? InstanceType { get; private set; } + public string? InstanceType { get; private set; } - public object? Value { get; set; } + public object? Value { get; set; } - public IEnumerable Annotations(Type type) - { - if (type == null) throw new ArgumentNullException(nameof(type)); - return (type == typeof(ElementNode) || type == typeof(ITypedElement) || type == typeof(IShortPathGenerator)) - ? (new[] { this }) - : HasAnnotations ? AnnotationsInternal.OfType(type) : Enumerable.Empty(); - } + public override IEnumerable Annotations(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + + return (type == typeof(ElementNode) || type == typeof(ITypedElement) || type == typeof(IShortPathGenerator)) + ? [this] : base.Annotations(type); + } - public string Location + public string Location + { + get { - get + if (Parent != null) { - if (Parent != null) - { - //TODO: Slow - but since we'll change the use of this property to informational - //(i.e. for error messages), it may not be necessary to improve it. - var basePath = Parent.Location; - var myIndex = Parent.ChildList.Where(c => c.Name == Name).ToList().IndexOf(this); - return $"{basePath}.{Name}[{myIndex}]"; - - } - else - return Name; + //TODO: Slow - but since we'll change the use of this property to informational + //(i.e. for error messages), it may not be necessary to improve it. + var basePath = Parent.Location; + var myIndex = Parent.ChildList.Where(c => c.Name == Name).ToList().IndexOf(this); + return $"{basePath}.{Name}[{myIndex}]"; + } + else + return Name; } + } - public string ShortPath + public string ShortPath + { + get { - get - { - if (Parent != null) - { - //TODO: Slow - but since we'll change the use of this property to informational - //(i.e. for error messages), it may not be necessary to improve it. - var basePath = Parent.ShortPath; - - if (Definition?.IsCollection == false) - return $"{basePath}.{Name}"; - else - { - var myIndex = Parent.ChildList.Where(c => c.Name == Name).ToList().IndexOf(this); - return $"{basePath}.{Name}[{myIndex}]"; - } - } - else - return Name; - } + if (Parent == null) return Name; + + //TODO: Slow - but since we'll change the use of this property to informational + //(i.e. for error messages), it may not be necessary to improve it. + var basePath = Parent.ShortPath; + + if (Definition?.IsCollection == false) + return $"{basePath}.{Name}"; + + var myIndex = Parent.ChildList.Where(c => c.Name == Name).ToList().IndexOf(this); + return $"{basePath}.{Name}[{myIndex}]"; } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs index 380f61da11..ae5c1561a7 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs @@ -8,7 +8,6 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Specification; -using Hl7.Fhir.Support.Poco; using Hl7.Fhir.Utility; using System; using System.Collections.Generic; @@ -94,9 +93,9 @@ private ScopedNode(ScopedNode parentNode, ScopedNode? parentResource, ITypedElem public NodeType Type => this switch { { AtResource: true } when Current.Children("contained").Any() => NodeType.DomainResource | NodeType.Resource, - { InstanceType: FhirTypeConstants.BUNDLE } => NodeType.Bundle | NodeType.Resource, + { InstanceType: FhirTypeNames.BUNDLE } => NodeType.Bundle | NodeType.Resource, { AtResource: true } => NodeType.Resource, - { InstanceType: FhirTypeConstants.REFERENCE or FhirTypeConstants.CANONICAL or FhirTypeConstants.CODEABLEREFERENCE } => NodeType.Reference, + { InstanceType: FhirTypeNames.REFERENCE or FhirTypeNames.CANONICAL or FhirTypeNames.CODEABLEREFERENCE } => NodeType.Reference, { Value: not null } => NodeType.Primitive, _ => 0 }; @@ -293,4 +292,4 @@ public IEnumerable Children(string? name = null) => public string ShortPath => Current is ElementNode en ? en.ShortPath : Current.Location; } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/SourceNode.cs b/src/Hl7.Fhir.Base/ElementModel/SourceNode.cs index dd91186fcd..db0c4c24f3 100644 --- a/src/Hl7.Fhir.Base/ElementModel/SourceNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/SourceNode.cs @@ -5,113 +5,109 @@ * This file is licensed under the BSD 3-Clause license * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +#nullable enable -using Hl7.Fhir.Utility; using System; using System.Collections.Generic; using System.Linq; -namespace Hl7.Fhir.ElementModel +namespace Hl7.Fhir.ElementModel; + +public class SourceNode : DomNode, ISourceNode, IResourceTypeSupplier { - public class SourceNode : DomNode, ISourceNode, IAnnotated, IResourceTypeSupplier - { - public IEnumerable Children(string name = null) => ChildrenInternal(name); + public IEnumerable Children(string? name = null) => ChildrenInternal(name); - public string ResourceType { get; set; } + public string? ResourceType { get; set; } - public string Text { get; set; } + public string? Text { get; set; } - private SourceNode(string name, string text, string resourceType = null) - { - Name = name; - Text = text; - ResourceType = resourceType; - } + private SourceNode(string name, string? text, string? resourceType = null) + { + Name = name; + Text = text; + ResourceType = resourceType; + } - public SourceNode Add(SourceNode child) => AddRange(new[] { child }); + public SourceNode Add(SourceNode child) => AddRange([child]); - public SourceNode AddRange(IEnumerable children) - { - base.ChildList.AddRange(children); - foreach (var c in base.ChildList) c.Parent = this; + public SourceNode AddRange(IEnumerable children) + { + base.ChildList.AddRange(children); + foreach (var c in base.ChildList) c.Parent = this; - return this; - } + return this; + } - public static SourceNode Valued(string name, string value, params SourceNode[] children) - => new SourceNode(name, value).AddRange(children); + public static SourceNode Valued(string name, string value, params SourceNode[] children) + => new SourceNode(name, value).AddRange(children); - public static SourceNode Resource(string name, string type, params SourceNode[] children) - => new SourceNode(name, null, type).AddRange(children); + public static SourceNode Resource(string name, string type, params SourceNode[] children) + => new SourceNode(name, null, type).AddRange(children); - public static SourceNode Node(string name, params SourceNode[] children) - => new SourceNode(name, null).AddRange(children); + public static SourceNode Node(string name, params SourceNode[] children) + => new SourceNode(name, null).AddRange(children); - /// - /// - /// - /// - /// - /// Maybe: if null - copy all, if empty, copy none, else specifcy which - /// - public static SourceNode FromNode(ISourceNode node, bool recursive = true, IEnumerable annotationsToCopy = null) - => buildNode(node, recursive, annotationsToCopy); + /// + /// + /// + /// + /// + /// Maybe: if null - copy all, if empty, copy none, else specifcy which + /// + public static SourceNode FromNode(ISourceNode node, bool recursive = true, IEnumerable? annotationsToCopy = null) + => buildNode(node, recursive, annotationsToCopy); - private static SourceNode buildNode(ISourceNode node, bool recursive, IEnumerable annotationsToCopy) - { - var me = new SourceNode(node.Name, node.Text); + private static SourceNode buildNode(ISourceNode node, bool recursive, IEnumerable? annotationsToCopy) + { + var me = new SourceNode(node.Name, node.Text); - var rts = node.Annotation(); - if (rts != null) - me.ResourceType = rts.ResourceType; + var rts = node.Annotation(); + if (rts != null) + me.ResourceType = rts.ResourceType; - foreach (var t in annotationsToCopy ?? Enumerable.Empty()) - foreach (var ann in node.Annotations(t)) - me.AddAnnotation(ann); + foreach (var t in annotationsToCopy ?? []) + foreach (var ann in node.Annotations(t)) + me.AddAnnotation(ann); - if (recursive) - me.AddRange(node.Children().Select(c => buildNode(c, recursive: true, annotationsToCopy: annotationsToCopy))); + if (recursive) + me.AddRange(node.Children().Select(c => buildNode(c, recursive: true, annotationsToCopy: annotationsToCopy))); - return me; - } + return me; + } - public SourceNode Clone() + public SourceNode Clone() + { + var copy = new SourceNode(Name, Text, ResourceType) { - var copy = new SourceNode(Name, Text, ResourceType) - { - Parent = Parent - }; + Parent = Parent + }; - copy.AddRange(Children().Cast().Select(c => c.Clone())); + copy.AddRange(Children().Cast().Select(c => c.Clone())); - if (HasAnnotations) - copy.AnnotationsInternal.AddRange(AnnotationsInternal); + if (HasAnnotations) + copy.AnnotationsInternal.AddRange(AnnotationsInternal); - return copy; - } + return copy; + } - public IEnumerable Annotations(Type type) - { - return type == typeof(SourceNode) || type == typeof(ISourceNode) || type == typeof(IResourceTypeSupplier) - ? (new[] { this }) - : HasAnnotations ? AnnotationsInternal.OfType(type) : Enumerable.Empty(); - } + public override IEnumerable Annotations(Type type) + { + return type == typeof(SourceNode) || type == typeof(ISourceNode) || type == typeof(IResourceTypeSupplier) + ? [this] + : base.Annotations(type); + } - public string Location + public string Location + { + get { - get - { - if (Parent != null) - { - //TODO: Slow - but since we'll change the use of this property to informational - //(i.e. for error messages), it may not be necessary to improve it. - var basePath = Parent.Location; - var myIndex = Parent.ChildList.Where(c => c.Name == Name).ToList().IndexOf(this); - return $"{basePath}.{Name}[{myIndex}]"; - } - else - return Name; - } + if (Parent == null) return Name; + + //TODO: Slow - but since we'll change the use of this property to informational + //(i.e. for error messages), it may not be necessary to improve it. + var basePath = Parent.Location; + var myIndex = Parent.ChildList.Where(c => c.Name == Name).ToList().IndexOf(this); + return $"{basePath}.{Name}[{myIndex}]"; } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs index 1e03012992..442327d8d4 100644 --- a/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs @@ -9,7 +9,6 @@ */ using Hl7.Fhir.Model; -using Hl7.Fhir.Support.Poco; using System; using System.Collections.Generic; using System.Linq; @@ -47,14 +46,14 @@ public static class TypedElementParseExtensions { return instance.InstanceType switch { - FhirTypeConstants.CODE => instance.ParsePrimitiveInternal(), - FhirTypeConstants.STRING => new Code(instance.ParsePrimitiveInternal().Value), - FhirTypeConstants.URI => new Code(instance.ParsePrimitiveInternal().Value), - FhirTypeConstants.CODING => instance.ParseCodingInternal(), - FhirTypeConstants.CODEABLE_CONCEPT => instance.ParseCodeableConceptInternal(), - FhirTypeConstants.QUANTITY => parseQuantity(), - FhirTypeConstants.EXTENSION => parseExtension(), - FhirTypeConstants.CODEABLEREFERENCE => parseCodeableReference(), + FhirTypeNames.CODE => instance.ParsePrimitiveInternal(), + FhirTypeNames.STRING => new Code(instance.ParsePrimitiveInternal().Value), + FhirTypeNames.URI => new Code(instance.ParsePrimitiveInternal().Value), + FhirTypeNames.CODING => instance.ParseCodingInternal(), + FhirTypeNames.CODEABLE_CONCEPT => instance.ParseCodeableConceptInternal(), + FhirTypeNames.QUANTITY => parseQuantity(), + FhirTypeNames.EXTENSION => parseExtension(), + FhirTypeNames.CODEABLEREFERENCE => parseCodeableReference(), _ => null, }; diff --git a/src/Hl7.Fhir.Base/Model/Base.cs b/src/Hl7.Fhir.Base/Model/Base.cs index f204ac170c..a6b62969dd 100644 --- a/src/Hl7.Fhir.Base/Model/Base.cs +++ b/src/Hl7.Fhir.Base/Model/Base.cs @@ -40,8 +40,8 @@ POSSIBILITY OF SUCH DAMAGE. namespace Hl7.Fhir.Model; -public abstract partial class Base : IDeepCopyable, IDeepComparable, - IAnnotated, IAnnotatable, IValidatableObject, INotifyPropertyChanged +public abstract partial class Base : IDeepCopyable, IDeepComparable, IAnnotatable, + IValidatableObject, INotifyPropertyChanged { /// /// FHIR Type Name diff --git a/src/Hl7.Fhir.Base/Model/FhirTypeConstants.cs b/src/Hl7.Fhir.Base/Model/FhirTypeConstants.cs deleted file mode 100644 index 8847be458c..0000000000 --- a/src/Hl7.Fhir.Base/Model/FhirTypeConstants.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://github.com/FirelyTeam/firely-net-sdk/blob/master/LICENSE - */ - -namespace Hl7.Fhir.Support.Poco -{ - /// - /// List of Fhir types constants. This will be removed when we introduce ModelSpace, but for now we will use this. - /// - internal class FhirTypeConstants - { - // primitive types - public const string BOOLEAN = "boolean"; - public const string INTEGER = "integer"; - public const string INTEGER64 = "integer64"; - public const string UNSIGNED_INT = "unsignedInt"; - public const string POSITIVE_INT = "positiveInt"; - public const string TIME = "time"; - public const string DATE = "date"; - public const string INSTANT = "instant"; - public const string DATE_TIME = "dateTime"; - public const string DECIMAL = "decimal"; - public const string STRING = "string"; - public const string CODE = "code"; - public const string ID = "id"; - public const string URI = "uri"; - public const string OID = "oid"; - public const string UUID = "uuid"; - public const string CANONICAL = "canonical"; - public const string URL = "url"; - public const string MARKDOWN = "markdown"; - public const string BASE64_BINARY = "base64Binary"; - - // General-Purpose Data types - public const string CODING = "Coding"; - public const string CODEABLE_CONCEPT = "CodeableConcept"; - public const string QUANTITY = "Quantity"; - - // Special Purpose Data types - public const string EXTENSION = "Extension"; - public const string REFERENCE = "Reference"; - public const string CODEABLEREFERENCE = "CodeableReference"; - public const string XHTML = "xhtml"; - - // Resource type - public const string BUNDLE = "Bundle"; - } -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Model/FhirTypeNames.cs b/src/Hl7.Fhir.Base/Model/FhirTypeNames.cs index 63b6b1237c..4d9c6546c4 100644 --- a/src/Hl7.Fhir.Base/Model/FhirTypeNames.cs +++ b/src/Hl7.Fhir.Base/Model/FhirTypeNames.cs @@ -1,20 +1,53 @@ -namespace Hl7.Fhir.Model +namespace Hl7.Fhir.Model; + +internal static class FhirTypeNames { - internal class FhirTypeNames - { - public const string PATIENT_NAME = "Patient"; - public const string BINARY_NAME = "Binary"; - public const string EXTENSION_NAME = "Extension"; - public const string SIMPLEQUANTITY_NAME = "SimpleQuantity"; - public const string STRUCTUREDEFINITION_NAME = "StructureDefinition"; - public const string CONCEPTMAP_NAME = "ConceptMap"; - public const string CODESYSTEM_NAME = "CodeSystem"; - public const string NAMINGSYSTEM_NAME = "NamingSystem"; - public const string DOMAINRESOURCE_NAME = "DomainResource"; - public const string REFERENCE_NAME = "Reference"; - public const string ELEMENT_NAME = "Element"; - public const string STRING_NAME = "string"; - public const string MARKDOWN_NAME = "markdown"; + public const string BOOLEAN = "boolean"; + public const string INTEGER = "integer"; + public const string INTEGER64 = "integer64"; + public const string UNSIGNED_INT = "unsignedInt"; + public const string POSITIVE_INT = "positiveInt"; + public const string TIME = "time"; + public const string DATE = "date"; + public const string INSTANT = "instant"; + public const string DATE_TIME = "dateTime"; + public const string DECIMAL = "decimal"; + public const string STRING = "string"; + public const string CODE = "code"; + public const string ID = "id"; + public const string URI = "uri"; + public const string OID = "oid"; + public const string UUID = "uuid"; + public const string CANONICAL = "canonical"; + public const string URL = "url"; + public const string MARKDOWN = "markdown"; + public const string BASE64_BINARY = "base64Binary"; + + // General-Purpose Data types + public const string CODING = "Coding"; + public const string CODEABLE_CONCEPT = "CodeableConcept"; + public const string QUANTITY = "Quantity"; + + // Special Purpose Data types + public const string EXTENSION = "Extension"; + public const string REFERENCE = "Reference"; + public const string CODEABLEREFERENCE = "CodeableReference"; + public const string XHTML = "xhtml"; + + // Resource type + public const string BUNDLE = "Bundle"; + public const string PATIENT_NAME = "Patient"; + public const string BINARY_NAME = "Binary"; + public const string EXTENSION_NAME = "Extension"; + public const string SIMPLEQUANTITY_NAME = "SimpleQuantity"; + public const string STRUCTUREDEFINITION_NAME = "StructureDefinition"; + public const string CONCEPTMAP_NAME = "ConceptMap"; + public const string CODESYSTEM_NAME = "CodeSystem"; + public const string NAMINGSYSTEM_NAME = "NamingSystem"; + public const string DOMAINRESOURCE_NAME = "DomainResource"; + public const string REFERENCE_NAME = "Reference"; + public const string ELEMENT_NAME = "Element"; + public const string STRING_NAME = "string"; + public const string MARKDOWN_NAME = "markdown"; - } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Model/Resource.cs b/src/Hl7.Fhir.Base/Model/Resource.cs index d25760565d..63c0d9f258 100644 --- a/src/Hl7.Fhir.Base/Model/Resource.cs +++ b/src/Hl7.Fhir.Base/Model/Resource.cs @@ -28,56 +28,47 @@ POSSIBILITY OF SUCH DAMAGE. */ +#nullable enable + using Hl7.Fhir.Utility; using System; -namespace Hl7.Fhir.Model +namespace Hl7.Fhir.Model; + +[System.Diagnostics.DebuggerDisplay("\\{\"{TypeName,nq}/{Id,nq}\"}")] +public partial class Resource { - [System.Diagnostics.DebuggerDisplay("\\{\"{TypeName,nq}/{Id,nq}\"}")] - public partial class Resource + /// + /// This is the base URL of the FHIR server that this resource is hosted on + /// + public Uri? ResourceBase { - /// - /// This is the base URL of the FHIR server that this resource is hosted on - /// - public Uri ResourceBase + get { - get - { - var bd = this.Annotation(); - return bd?.Base; - } - - set - { - this.RemoveAnnotations(); - AddAnnotation(new ResourceBaseData { Base = value }); - } + var bd = this.Annotation(); + return bd?.Base; } - private class ResourceBaseData + set { - public Uri Base; + this.RemoveAnnotations(); + + if(value is not null) + AddAnnotation(new ResourceBaseData(value)); } + } - /// - /// This object is internally used for locking the resource in a multithreaded environment. - /// - /// - /// As a consumer of this API, please do not use this object. - /// - public readonly object SyncLock = new(); + private record ResourceBaseData(Uri Base); - public string VersionId + /// + /// Returns the Meta.VersionId, if available. + /// + public string? VersionId + { + get => Meta?.VersionId; + set { - get => Meta?.VersionId; - set - { - Meta ??= new Meta(); - Meta.VersionId = value; - } + Meta ??= new Meta { VersionId = value }; } - - public bool HasVersionId => Meta?.VersionId != null; } - } \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs index 7875c314c6..a6c38fd0a8 100644 --- a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs +++ b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs @@ -241,8 +241,8 @@ public BaseFhirClient(Uri endpoint, ModelInspector inspector, FhirClientSettings var upd = new TransactionBuilder(Endpoint); - if (versionAware && resource.HasVersionId) - upd.Update(resource.Id, resource, versionId: resource.VersionId); + if (versionAware && resource.VersionId is { } vid) + upd.Update(resource.Id, resource, versionId: vid); else upd.Update(resource.Id, resource); @@ -267,8 +267,8 @@ public BaseFhirClient(Uri endpoint, ModelInspector inspector, FhirClientSettings var upd = new TransactionBuilder(Endpoint); - if (versionAware && resource.HasVersionId) - upd.ConditionalUpdate(condition, resource, versionId: resource.VersionId); + if (versionAware && resource.VersionId is {} vid) + upd.ConditionalUpdate(condition, resource, versionId: vid); else upd.ConditionalUpdate(condition, resource); diff --git a/src/Hl7.Fhir.Base/Utility/AnnotationList.cs b/src/Hl7.Fhir.Base/Utility/AnnotationList.cs index b526ecc5f8..90179f1186 100644 --- a/src/Hl7.Fhir.Base/Utility/AnnotationList.cs +++ b/src/Hl7.Fhir.Base/Utility/AnnotationList.cs @@ -11,68 +11,73 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -namespace Hl7.Fhir.Utility +namespace Hl7.Fhir.Utility; + +/// +/// This class implements the interfaces and . It can be used by the classes that also implements these +/// interfaces to have a common implementation. +/// This list is thread safe +/// +public class AnnotationList : IAnnotatable, IEnumerable { + private Lazy>> _annotations = new(() => new ConcurrentDictionary>()); + private ConcurrentDictionary> annotations { get { return _annotations.Value; } } + /// - /// This class implements the interfaces and . It can be used by the classes that also implements these - /// interfaces to have a common implementation. - /// This list is thread safe + /// Create a list containing the given annotations. /// - public class AnnotationList : IAnnotatable, IAnnotated, IEnumerable + public static AnnotationList Create(ReadOnlySpan annotations) { - private Lazy>> _annotations = new Lazy>>(() => new ConcurrentDictionary>()); - private ConcurrentDictionary> annotations { get { return _annotations.Value; } } - - public static AnnotationList Create(ReadOnlySpan annotations) + var list = new AnnotationList(); + foreach (var annotation in annotations) { - var list = new AnnotationList(); - foreach (var annotation in annotations) - { - list.AddAnnotation(annotation); - } - return list; + list.AddAnnotation(annotation); } - - public void AddAnnotation(object annotation) - { - annotations.AddOrUpdate( - annotation.GetType(), - [annotation], - (_, existingList) => [..existingList, annotation]); - } - - public void RemoveAnnotations(Type type) => annotations.TryRemove(type, out _); + return list; + } - public IEnumerable Annotations(Type type) - { - if (annotations.TryGetValue(type, out var values)) - return values; - return Enumerable.Empty(); - } + /// + /// Add an annotation to the list, possibly replacing an existing one. + /// + public void AddAnnotation(object annotation) + { + annotations.AddOrUpdate( + annotation.GetType(), + [annotation], + (_, existingList) => [..existingList, annotation]); + } - /// - /// Returns all annotations of type - /// - public IEnumerable OfType(Type type) => Annotations(type); + /// + /// Removes the annotation of the given type. If it does not exist, nothing happens. + /// + public void RemoveAnnotations(Type type) => annotations.TryRemove(type, out _); - /// - /// Gets a value that indicates whether there is an annotation present - /// - public bool IsEmpty => annotations.IsEmpty; + /// + /// Returns all annotations of type + /// + public IEnumerable Annotations(Type type) => + annotations.TryGetValue(type, out var values) ? values : []; - /// - /// Adds all the annotations from the to here. It will remove all existing annotations - /// - /// - public void AddRange(AnnotationList source) - { - _annotations = new Lazy>>(() => new ConcurrentDictionary>(source.annotations)); - } + /// + /// Returns all annotations of type + /// + public IEnumerable OfType(Type type) => Annotations(type); - IEnumerator IEnumerable.GetEnumerator() => annotations.Values.SelectMany(v => v).GetEnumerator(); + /// + /// Gets a value that indicates whether there is an annotation present + /// + public bool IsEmpty => annotations.IsEmpty; - public IEnumerator GetEnumerator() => ((IEnumerable)this).GetEnumerator(); + /// + /// Adds all the annotations from the to here. It will remove all existing annotations + /// + public void AddRange(AnnotationList source) + { + _annotations = new Lazy>>(() => new ConcurrentDictionary>(source.annotations)); } -} + + IEnumerator IEnumerable.GetEnumerator() => annotations.Values.SelectMany(v => v).GetEnumerator(); + + public IEnumerator GetEnumerator() => ((IEnumerable)this).GetEnumerator(); +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Utility/IAnnotatable.cs b/src/Hl7.Fhir.Base/Utility/IAnnotatable.cs index ab21824f77..43a6ebc69a 100644 --- a/src/Hl7.Fhir.Base/Utility/IAnnotatable.cs +++ b/src/Hl7.Fhir.Base/Utility/IAnnotatable.cs @@ -5,35 +5,35 @@ * This file is licensed under the BSD 3-Clause license * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +#nullable enable using System; -namespace Hl7.Fhir.Utility +namespace Hl7.Fhir.Utility; + +public interface IAnnotatable : IAnnotated { - public interface IAnnotatable - { - void AddAnnotation(object annotation); + void AddAnnotation(object annotation); - void RemoveAnnotations(Type type); - } + void RemoveAnnotations(Type type); +} - public static class AnnotatableExtensions +public static class AnnotatableExtensions +{ + public static void RemoveAnnotations(this IAnnotatable annotatable) { - public static void RemoveAnnotations(this IAnnotatable annotatable) - { - annotatable.RemoveAnnotations(typeof(A)); - } + annotatable.RemoveAnnotations(typeof(T)); + } - private static readonly object _lock = new object(); + private static readonly object _lock = new(); - public static void SetAnnotation(this IAnnotatable annotatable, A annotation) + public static void SetAnnotation(this IAnnotatable annotatable, A annotation) + { + lock (_lock) { - lock (_lock) - { - annotatable.RemoveAnnotations(); - if (annotation != null) - annotatable.AddAnnotation(annotation); - } + annotatable.RemoveAnnotations(); + if (annotation != null) + annotatable.AddAnnotation(annotation); } } -} +} \ No newline at end of file