Skip to content

Commit 8b4821f

Browse files
committed
Implement IBufferWriter<char> in OpenStringBuilder and CharTermAttribute, #1315
1 parent 80b2a66 commit 8b4821f

6 files changed

Lines changed: 1173 additions & 9 deletions

File tree

src/Lucene.Net.Analysis.Common/Analysis/Util/OpenStringBuilder.cs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using J2N.Text;
33
using Lucene.Net.Support;
44
using System;
5+
using System.Buffers;
6+
using System.Diagnostics;
57
using System.Diagnostics.CodeAnalysis;
68
using System.Text;
79
using WritableArrayAttribute = Lucene.Net.Support.WritableArrayAttribute;
@@ -28,7 +30,15 @@ namespace Lucene.Net.Analysis.Util
2830
/// <summary>
2931
/// A StringBuilder that allows one to access the array.
3032
/// </summary>
31-
public class OpenStringBuilder : IAppendable, ISpanAppendable, ICharSequence // LUCENENET specific - implemented ISpanAppendable to support ReadOnlySpan<char>
33+
/// <remarks>
34+
/// LUCENENET specific: This type implements <see cref="IBufferWriter{T}"/> to allow for efficient access to the underlying buffer.
35+
/// Note that if you hold a view into the buffer via either <see cref="GetMemory(int)"/> or <see cref="GetSpan(int)"/>,
36+
/// this view can be invalidated by any operation that changes the position or length of the buffer.
37+
/// It is recommended to avoid any non-<see cref="IBufferWriter{T}"/> operations while holding a view into the buffer.
38+
/// <para />
39+
/// This type also implements <see cref="ISpanAppendable"/> to allow for efficient appending of <see cref="ReadOnlySpan{T}"/>.
40+
/// </remarks>
41+
public class OpenStringBuilder : IAppendable, ISpanAppendable, ICharSequence, IBufferWriter<char>
3242
{
3343
protected char[] m_buf;
3444
protected int m_len;
@@ -42,6 +52,12 @@ public OpenStringBuilder()
4252

4353
public OpenStringBuilder(int size)
4454
{
55+
// LUCENENET specific - validate argument
56+
if (size <= 0)
57+
{
58+
throw new ArgumentOutOfRangeException(nameof(size), "size must be greater than zero");
59+
}
60+
4561
m_buf = new char[size];
4662
}
4763

@@ -400,5 +416,72 @@ IAppendable IAppendable.Append(ICharSequence value, int startIndex, int count)
400416
ISpanAppendable ISpanAppendable.Append(ReadOnlySpan<char> value) => Append(value);
401417

402418
#endregion IAppendable Members
419+
420+
#region IBufferWriter<char> members and support
421+
422+
// LUCENENET-specific: IBufferWriter<char> support.
423+
// See ArrayBufferWriter for an inspiration and reference implementation:
424+
// https://github.com/dotnet/runtime/blob/v10.0.6/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs
425+
426+
public void Advance(int count)
427+
{
428+
if (count < 0)
429+
throw new ArgumentException(null, nameof(count));
430+
431+
if (m_len > m_buf.Length - count)
432+
throw new InvalidOperationException($"Cannot advance {count} characters beyond the end of the buffer.");
433+
434+
m_len += count;
435+
}
436+
437+
public Memory<char> GetMemory(int sizeHint = 0)
438+
{
439+
CheckAndResizeBufferForBufferWriter(sizeHint);
440+
Debug.Assert(m_buf.Length > m_len);
441+
return m_buf.AsMemory(m_len);
442+
}
443+
444+
public Span<char> GetSpan(int sizeHint = 0)
445+
{
446+
CheckAndResizeBufferForBufferWriter(sizeHint);
447+
Debug.Assert(m_buf.Length > m_len);
448+
return m_buf.AsSpan(m_len);
449+
}
450+
451+
private void CheckAndResizeBufferForBufferWriter(int sizeHint)
452+
{
453+
if (sizeHint < 0)
454+
{
455+
throw new ArgumentException("sizeHint must be non-negative", nameof(sizeHint));
456+
}
457+
458+
if (sizeHint == 0)
459+
{
460+
sizeHint = 1;
461+
}
462+
463+
EnsureCapacity(sizeHint);
464+
}
465+
466+
/// <summary>
467+
/// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow.
468+
/// </summary>
469+
public int FreeCapacity => m_buf.Length - m_len;
470+
471+
#endregion
472+
473+
#region AsSpan/AsMemory support
474+
475+
/// <summary>
476+
/// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
477+
/// </summary>
478+
public ReadOnlyMemory<char> AsMemory() => m_buf.AsMemory(0, m_len);
479+
480+
/// <summary>
481+
/// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
482+
/// </summary>
483+
public ReadOnlySpan<char> AsSpan() => m_buf.AsSpan(0, m_len);
484+
485+
#endregion
403486
}
404487
}

src/Lucene.Net.Analysis.ICU/Analysis/Icu/ICUNormalizer2Filter.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
using Lucene.Net.Analysis.TokenAttributes.Extensions;
55
using Lucene.Net.Analysis.Util;
66
using Lucene.Net.Support;
7-
using System;
8-
using System.Text;
97

108
namespace Lucene.Net.Analysis.Icu
119
{
@@ -91,11 +89,12 @@ public override sealed bool IncrementToken()
9189
{
9290
if (m_input.IncrementToken())
9391
{
94-
if (normalizer.QuickCheck(termAtt.Buffer.AsSpan(0, termAtt.Length)) != QuickCheckResult.Yes)
92+
if (normalizer.QuickCheck(termAtt.AsSpan()) != QuickCheckResult.Yes)
9593
{
96-
buffer.Length = 0;
97-
normalizer.Normalize(termAtt.Buffer.AsSpan(0, termAtt.Length), buffer);
98-
termAtt.SetEmpty().Append(buffer);
94+
buffer.Reset(); // LUCENENET specific - using Reset() instead of Length = 0
95+
// LUCENENET TODO: ensure the IBufferWriter<char> version of Normalize is called when ICU4N is updated to support it
96+
normalizer.Normalize(termAtt.AsSpan(), buffer);
97+
termAtt.SetEmpty().Append(buffer.AsSpan()); // LUCENENET specific - Using ReadOnlySpan<char> version of Append
9998
}
10099
return true;
101100
}

0 commit comments

Comments
 (0)