Skip to content

Commit b85bc14

Browse files
author
Corey Floyd
committed
#258 Adds new utility to provide substrings of a given string as Span<char> given a char delimeter. Language version updated to 7.2 due to need for ref Struct to utilize Span<T> as a field.
1 parent e7c65db commit b85bc14

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.Linq;
5+
using JsonApiDotNetCore.Extensions;
6+
7+
namespace JsonApiDotNetCore.Internal
8+
{
9+
public readonly ref struct SpanSplitter
10+
{
11+
private readonly ReadOnlySpan<char> _span;
12+
private readonly List<int> _delimeterIndexes;
13+
private readonly List<Tuple<int, int>> _substringIndexes;
14+
15+
public int Count => _substringIndexes.Count();
16+
public ReadOnlySpan<char> this[int index] => GetSpanForSubstring(index + 1);
17+
18+
public SpanSplitter(ref string str, char delimeter)
19+
{
20+
_span = str.AsSpan();
21+
_delimeterIndexes = str.IndexesOf(delimeter).ToList();
22+
_substringIndexes = new List<Tuple<int, int>>();
23+
BuildSubstringIndexes();
24+
}
25+
26+
[EditorBrowsable(EditorBrowsableState.Never)]
27+
public override bool Equals(object obj) => throw new NotSupportedException();
28+
29+
[EditorBrowsable(EditorBrowsableState.Never)]
30+
public override int GetHashCode() => throw new NotSupportedException();
31+
32+
[EditorBrowsable(EditorBrowsableState.Never)]
33+
public override string ToString() => throw new NotSupportedException();
34+
35+
private ReadOnlySpan<char> GetSpanForSubstring(int substringNumber)
36+
{
37+
if (substringNumber > Count)
38+
{
39+
throw new ArgumentOutOfRangeException($"There are only {Count} substrings given the delimeter and base string provided");
40+
}
41+
42+
var indexes = _substringIndexes[substringNumber - 1];
43+
return _span.Slice(indexes.Item1, indexes.Item2);
44+
}
45+
46+
private void BuildSubstringIndexes()
47+
{
48+
var start = 0;
49+
var end = 0;
50+
foreach (var index in _delimeterIndexes)
51+
{
52+
end = index;
53+
if (start > end) break;
54+
_substringIndexes.Add(new Tuple<int, int>(start, end - start));
55+
start = ++end;
56+
}
57+
58+
if (end <= _span.Length)
59+
{
60+
_substringIndexes.Add(new Tuple<int, int>(start, _span.Length - start));
61+
}
62+
}
63+
}
64+
}

src/JsonApiDotNetCore/JsonApiDotNetCore.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" />
2222
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="$(EFCoreVersion)" />
2323
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftLoggingVersion)" />
24+
<PackageReference Include="System.Memory" Version="4.5.0-preview2-26406-04" />
2425
<PackageReference Include="System.ValueTuple" Version="$(TuplesVersion)" />
2526
</ItemGroup>
2627

@@ -31,6 +32,12 @@
3132
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
3233
<DocumentationFile>bin\Release\netstandard2.0\JsonApiDotNetCore.xml</DocumentationFile>
3334
</PropertyGroup>
35+
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
36+
<LangVersion>7.2</LangVersion>
37+
</PropertyGroup>
38+
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
39+
<LangVersion>7.2</LangVersion>
40+
</PropertyGroup>
3441
<ItemGroup Condition="$(IsWindows)=='true'">
3542
<PackageReference Include="docfx.console" Version="2.33.0" />
3643
</ItemGroup>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using JsonApiDotNetCore.Internal;
6+
using Xunit;
7+
8+
namespace UnitTests.Internal
9+
{
10+
public class SpanSplitterTests : SpanSplitterTestsBase
11+
{
12+
[Fact]
13+
public void StringWithDelimeterSplitsIntoCorrectNumberSubstrings()
14+
{
15+
GivenMultipleCommaDelimetedString();
16+
WhenSplittingIntoSubstrings();
17+
AssertCorrectSubstringsReturned();
18+
}
19+
20+
[Fact]
21+
public void StringWithSingleDelimeterSplitsIntoCorrectNumberSubstrings()
22+
{
23+
GivenSingleCommaDelimetedString();
24+
WhenSplittingIntoSubstrings();
25+
AssertCorrectSubstringsReturned();
26+
}
27+
28+
[Fact]
29+
public void StringWithNoDelimeterSplitsIntoSingleSubstring()
30+
{
31+
GivenNonCommaDelimetedString();
32+
WhenSplittingIntoSubstrings();
33+
AssertCorrectSubstringsReturned();
34+
}
35+
36+
[Fact]
37+
public void StringWithDelimeterAtEndSplitsIntoCorrectSubstring()
38+
{
39+
GivenStringWithCommaDelimeterAtEnd();
40+
WhenSplittingIntoSubstrings();
41+
AssertCorrectSubstringsReturned();
42+
}
43+
44+
[Fact]
45+
public void StringWithDelimeterAtBeginningSplitsIntoCorrectSubstring()
46+
{
47+
GivenStringWithCommaDelimeterAtBeginning();
48+
WhenSplittingIntoSubstrings();
49+
AssertCorrectSubstringsReturned();
50+
}
51+
}
52+
53+
public abstract class SpanSplitterTestsBase
54+
{
55+
private string _baseString;
56+
private char _delimeter;
57+
private readonly List<string> _substrings = new List<string>();
58+
59+
protected void GivenMultipleCommaDelimetedString()
60+
{
61+
_baseString = "This,Is,A,TestString";
62+
_delimeter = ',';
63+
}
64+
65+
protected void GivenSingleCommaDelimetedString()
66+
{
67+
_baseString = "This,IsATestString";
68+
_delimeter = ',';
69+
}
70+
71+
protected void GivenNonCommaDelimetedString()
72+
{
73+
_baseString = "ThisIsATestString";
74+
}
75+
76+
protected void GivenStringWithCommaDelimeterAtEnd()
77+
{
78+
_baseString = "This,IsATestString,";
79+
_delimeter = ',';
80+
}
81+
82+
protected void GivenStringWithCommaDelimeterAtBeginning()
83+
{
84+
_baseString = "/api/v1/articles";
85+
_delimeter = '/';
86+
}
87+
88+
protected void WhenSplittingIntoSubstrings()
89+
{
90+
SpanSplitter spanSplitter;
91+
spanSplitter = new SpanSplitter(ref _baseString, _delimeter);
92+
for (var i = 0; i < spanSplitter.Count; i++)
93+
{
94+
var span = spanSplitter[i];
95+
_substrings.Add(span.ToString());
96+
}
97+
}
98+
99+
protected void AssertCorrectSubstringsReturned()
100+
{
101+
Assert.NotEmpty(_substrings);
102+
var stringSplitArray = _baseString.Split(_delimeter);
103+
Assert.Equal(stringSplitArray.Length, _substrings.Count);
104+
Assert.True(stringSplitArray.SequenceEqual(_substrings));
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)