Skip to content

Commit

Permalink
Merge pull request #2582 from FirelyTeam/feature/VONK-5093-IScopedNode
Browse files Browse the repository at this point in the history
VONK-5093: introducing IScopedNode
  • Loading branch information
marcovisserFurore authored Dec 11, 2023
2 parents 44a02c8 + 063f8e1 commit 7aac259
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 73 deletions.
15 changes: 9 additions & 6 deletions src/Benchmarks/EnumUtilityBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Hl7.Fhir.Utility;
using System;

#nullable enable

namespace Firely.Sdk.Benchmarks
{
[MemoryDiagnoser]
Expand All @@ -16,7 +18,7 @@ public string EnumToString()
=> SearchParamType.String.ToString();

[Benchmark]
public string EnumGetName()
public string? EnumGetName()
=> Enum.GetName(StringSearchParam);

[Benchmark]
Expand All @@ -37,26 +39,27 @@ public SearchParamType EnumParseIgnoreCase()

[Benchmark]
public SearchParamType EnumUtilityParseLiteral()
=> EnumUtility.ParseLiteral<SearchParamType>("string").Value;
=> EnumUtility.ParseLiteral<SearchParamType>("string")!.Value;

[Benchmark]
public Enum EnumUtilityParseLiteralNonGeneric()
public Enum? EnumUtilityParseLiteralNonGeneric()
=> EnumUtility.ParseLiteral("string", typeof(SearchParamType));

[Benchmark]
public SearchParamType EnumUtilityParseLiteralIgnoreCase()
=> EnumUtility.ParseLiteral<SearchParamType>("string", true).Value;
=> EnumUtility.ParseLiteral<SearchParamType>("string", true)!.Value;

[Benchmark]
public Enum EnumUtilityParseLiteralIgnoreCaseNonGeneric()
public Enum? EnumUtilityParseLiteralIgnoreCaseNonGeneric()
=> EnumUtility.ParseLiteral("string", typeof(SearchParamType), true);

[Benchmark]
public string? EnumUtilityGetSystem()
=> EnumUtility.GetSystem(StringSearchParam);

[Benchmark]
public string EnumUtilityGetSystemNonGeneric()
public string? EnumUtilityGetSystemNonGeneric()
=> EnumUtility.GetSystem(StringSearchParamEnum);
}
}
#nullable restore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2023, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE
*/

#nullable enable

using Hl7.Fhir.Specification;
using System.Collections.Generic;
using System.Linq;

namespace Hl7.Fhir.ElementModel
{
/// <summary>
/// An adapter from <see cref="IScopedNode"/> to <see cref="ITypedElement"/>.
/// </summary>
/// <remarks>Be careful, this adapter does not implement the <see cref="ITypedElement.Location"/> and
/// <see cref="ITypedElement.Definition"/> property.
/// </remarks>
internal class ScopedNodeToTypedElementAdapter : ITypedElement
{
private readonly IScopedNode _adaptee;

public ScopedNodeToTypedElementAdapter(IScopedNode adaptee)
{
_adaptee = adaptee;
}

public string Location => throw new System.NotImplementedException();

public IElementDefinitionSummary Definition => throw new System.NotImplementedException();

public string Name => _adaptee.Name;

public string InstanceType => _adaptee.InstanceType;

public object Value => _adaptee.Value;

public IEnumerable<ITypedElement> Children(string? name = null) =>
_adaptee.Children(name).Select(n => new ScopedNodeToTypedElementAdapter(n));
}
}

#nullable restore
9 changes: 9 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,14 @@ public static IReadOnlyCollection<IElementDefinitionSummary> ChildDefinitions(th

public static ScopedNode ToScopedNode(this ITypedElement node) =>
node as ScopedNode ?? new ScopedNode(node);

/// <summary>
/// Convert a <see cref="ITypedElement"/> to a <see cref="IScopedNode"/>.
/// </summary>
/// <param name="node">An <see cref="ITypedElement"/></param>
/// <returns></returns>
internal static IScopedNode AsScopedNode(this ITypedElement node) => ToScopedNode(node);
}
}

#nullable restore
70 changes: 70 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2023, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE
*/

using System;
using System.Collections.Generic;

#nullable enable

namespace Hl7.Fhir.ElementModel
{
/// <summary>
/// The base interface for <see cref="ITypedElement"/> and <see cref="IScopedNode"/>."/>
/// </summary>
/// <typeparam name="TDerived"></typeparam>
[Obsolete("WARNING! Intended for internal API usage exclusively, this interface ideally should be kept internal. " +
"However, due to its derivation by the public interface ITypedElement, maintaining its internal status is impossible.")]
public interface IBaseElementNavigator<TDerived> where TDerived : IBaseElementNavigator<TDerived>
{
/// <summary>
/// Enumerate the child nodes present in the source representation (if any)
/// </summary>
/// <param name="name">Return only the children with the given name.</param>
/// <returns></returns>
IEnumerable<TDerived> Children(string? name = null);

/// <summary>
/// Name of the node, e.g. "active", "value".
/// </summary>
string Name { get; }

/// <summary>
/// Type of the node. If a FHIR type, this is just a simple string, otherwise a StructureDefinition url for a type defined as a logical model.
/// </summary>
string InstanceType { get; }

/// <summary>
/// The value of the node (if it represents a primitive FHIR value)
/// </summary>
/// <remarks>
/// FHIR primitives are mapped to underlying C# types as follows:
///
/// instant Hl7.Fhir.ElementModel.Types.DateTime
/// time Hl7.Fhir.ElementModel.Types.Time
/// date Hl7.Fhir.ElementModel.Types.Date
/// dateTime Hl7.Fhir.ElementModel.Types.DateTime
/// decimal decimal
/// boolean bool
/// integer int
/// unsignedInt int
/// positiveInt int
/// long/integer64 long (name will be finalized in R5)
/// string string
/// code string
/// id string
/// uri, oid, uuid,
/// canonical, url string
/// markdown string
/// base64Binary string (uuencoded)
/// xhtml string
/// </remarks>
object Value { get; }
}
}

#nullable restore
31 changes: 31 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2023, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE
*/

#nullable enable

namespace Hl7.Fhir.ElementModel
{
/// <summary>
/// An element within a tree of typed FHIR data with also a parent element.
/// </summary>
/// <remarks>
/// This interface represents FHIR data as a tree of elements, including type information either present in
/// the instance or derived from fully aware of the FHIR definitions and types
/// </remarks>
#pragma warning disable CS0618 // Type or member is obsolete
internal interface IScopedNode : IBaseElementNavigator<IScopedNode>
#pragma warning restore CS0618 // Type or member is obsolete
{
/// <summary>
/// The parent node of this node, or null if this is the root node.
/// </summary>
IScopedNode? Parent { get; }
}
}

#nullable restore
45 changes: 45 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2023, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE
*/

#nullable enable


using System;
using System.Collections.Generic;
using System.Linq;

namespace Hl7.Fhir.ElementModel
{
internal static class IScopedNodeExtensions
{
/// <summary>
/// Converts a <see cref="IScopedNode"/> to a <see cref="ITypedElement"/>.
/// </summary>
/// <param name="node">An <see cref="IScopedNode"/> node</param>
/// <returns>An implementation of <see cref="ITypedElement"/></returns>
/// <remarks>Be careful when using this method, the returned <see cref="ITypedElement"/> does not implement
/// the methods <see cref="ITypedElement.Location"/> and <see cref="ITypedElement.Definition"/>.
/// </remarks>
[Obsolete("WARNING! For internal API use only. Turning an IScopedNode into an ITypedElement will cause problems for" +
"Location and Definitions. Those properties are not implemented using this method and can cause problems " +
"elsewhere. Please don't use this method unless you know what you are doing.")]
public static ITypedElement AsTypedElement(this IScopedNode node) =>
node is ITypedElement ite ? ite : new ScopedNodeToTypedElementAdapter(node);

/// <summary>
/// Returns the parent resource of this node, or null if this node is not part of a resource.
/// </summary>
/// <param name="nodes"></param>
/// <param name="name"></param>
/// <returns></returns>
public static IEnumerable<IScopedNode> Children(this IEnumerable<IScopedNode> nodes, string? name = null) =>
nodes.SelectMany(n => n.Children(name));
}
}

#nullable restore
47 changes: 3 additions & 44 deletions src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
*/

using Hl7.Fhir.Specification;
using System.Collections.Generic;

namespace Hl7.Fhir.ElementModel
{
Expand All @@ -19,51 +18,11 @@ namespace Hl7.Fhir.ElementModel
/// the instance or derived from fully aware of the FHIR definitions and types
/// </remarks>

public interface ITypedElement
#pragma warning disable CS0618 // Type or member is obsolete
public interface ITypedElement : IBaseElementNavigator<ITypedElement>
#pragma warning restore CS0618 // Type or member is obsolete
{
/// <summary>
/// Enumerate the child nodes present in the source representation (if any)
/// </summary>
/// <param name="name">Return only the children with the given name.</param>
/// <returns></returns>
IEnumerable<ITypedElement> Children(string name = null);

/// <summary>
/// Name of the node, e.g. "active", "value".
/// </summary>
string Name { get; }

/// <summary>
/// Type of the node. If a FHIR type, this is just a simple string, otherwise a StructureDefinition url for a type defined as a logical model.
/// </summary>
string InstanceType { get; }

/// <summary>
/// The value of the node (if it represents a primitive FHIR value)
/// </summary>
/// <remarks>
/// FHIR primitives are mapped to underlying C# types as follows:
///
/// instant Hl7.Fhir.ElementModel.Types.DateTime
/// time Hl7.Fhir.ElementModel.Types.Time
/// date Hl7.Fhir.ElementModel.Types.Date
/// dateTime Hl7.Fhir.ElementModel.Types.DateTime
/// decimal decimal
/// boolean bool
/// integer int
/// unsignedInt int
/// positiveInt int
/// long/integer64 long (name will be finalized in R5)
/// string string
/// code string
/// id string
/// uri, oid, uuid,
/// canonical, url string
/// markdown string
/// base64Binary string (uuencoded)
/// xhtml string
/// </remarks>
object Value { get; }

/// <summary>
/// An indication of the location of this node within the data represented by the <c>ITypedElement</c>.
Expand Down
22 changes: 18 additions & 4 deletions src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace Hl7.Fhir.ElementModel
{
public class ScopedNode : ITypedElement, IAnnotated, IExceptionSource
public class ScopedNode : ITypedElement, IScopedNode, IAnnotated, IExceptionSource
{
private class Cache
{
Expand All @@ -32,6 +32,7 @@ private class Cache
private readonly Cache _cache = new();

public readonly ITypedElement Current;
private readonly ScopedNode? _parent;

public ScopedNode(ITypedElement wrapped, string? instanceUri = null)
{
Expand All @@ -45,6 +46,7 @@ public ScopedNode(ITypedElement wrapped, string? instanceUri = null)
private ScopedNode(ScopedNode parentNode, ScopedNode? parentResource, ITypedElement wrapped, string? fullUrl)
{
Current = wrapped;
_parent = parentNode;
ExceptionHandler = parentNode.ExceptionHandler;
ParentResource = parentNode.AtResource ? parentNode : parentResource;

Expand Down Expand Up @@ -217,8 +219,7 @@ public string? InstanceUri
{
// Scan up until the first parent that knowns the instance uri (at the last the
// root, if it has been supplied).
if (_cache.InstanceUri is null)
_cache.InstanceUri = ParentResources().SkipWhile(p => p.InstanceUri is null).FirstOrDefault()?.InstanceUri;
_cache.InstanceUri ??= ParentResources().SkipWhile(p => p.InstanceUri is null).FirstOrDefault()?.InstanceUri;

return _cache.InstanceUri;
}
Expand All @@ -229,11 +230,24 @@ private set
}
}

/// <inheritdoc />


IScopedNode? IScopedNode.Parent => _parent;

/// <inheritdoc />
public IEnumerable<object> Annotations(Type type) => type == typeof(ScopedNode) ? (new[] { this }) : Current.Annotations(type);

private IEnumerable<ScopedNode> childrenInternal(string? name = null) =>
Current.Children(name).Select(c => new ScopedNode(this, ParentResource, c, _fullUrl));

/// <inheritdoc />
public IEnumerable<ITypedElement> Children(string? name = null) =>
Current.Children(name).Select(c => new ScopedNode(this, ParentResource, c, _fullUrl));
childrenInternal(name);

IEnumerable<IScopedNode> IBaseElementNavigator<IScopedNode>.Children(string? name) =>
childrenInternal(name);
}
}

#nullable restore
Loading

0 comments on commit 7aac259

Please sign in to comment.