Skip to content

Commit 88ff2f0

Browse files
authored
Merge pull request #506 from LogExperts/new_reader
This pull request introduces a new set of "Memory"-based interfaces and updates the `Column` and related classes to use `ReadOnlyMemory<char>` instead of `string` for improved performance and flexibility. It also adds new helper methods for memory-based string manipulation and updates unit tests accordingly. The changes are grouped into interface additions/updates, core class refactoring, and unit test adjustments. **Interface additions and updates:** * Introduced new interfaces: `IColumnMemory`, `IColumnizedLogLineMemory`, `IAutoLogLineMemoryColumnizerCallback`, `IColumnizerConfiguratorMemory`, and `IColumnizerPriorityMemory` to support `ReadOnlyMemory<char>`-based operations and memory-efficient log processing. [[1]](diffhunk://#diff-eaf296844edf561a63b84b9ef2b8c924f4b8fad5be46d0a9fb1bbfbeabc5252aR1-R14) [[2]](diffhunk://#diff-e02515e0983411eb71e6aa95f8763d2e05e49580c029d5a4ec321cf2ca6cac85R1-R12) [[3]](diffhunk://#diff-e5cc913c39aac5f822d1a787dfcd6e87d7ece16c3488ef503a37e9c8a692aa0dR1-R11) [[4]](diffhunk://#diff-f76163582848504aa13c43d097cb606347dfd67fff88d79e941b7c86a2195be1R1-R33) [[5]](diffhunk://#diff-5820099353f1719a29f19a82fd07602bf7ee6c4c447716e0941d3a709a322845R1-R18) * Updated existing interfaces and consumers to provide backward compatibility via `[Obsolete]` members and to reference new memory-based types where appropriate, such as in `IColumnizedLogLine` and `IContextMenuEntry`. [[1]](diffhunk://#diff-4e43acad58ef97a690c3d2fb084f7404e1b8966b32cdb4f3ab0f061f69881102L3-R15) [[2]](diffhunk://#diff-3ab334a54d4cee00572d2fc337cf3f7d2a32714743067dc0e31cf4c513204335L35-R37) [[3]](diffhunk://#diff-3ab334a54d4cee00572d2fc337cf3f7d2a32714743067dc0e31cf4c513204335L49-R51) **Core class refactoring:** * Refactored the `Column` class and related factory methods to operate on `ReadOnlyMemory<char>` instead of `string`, updated the replacement pipeline to use memory-based functions, and added private helpers for tab, truncation, and null character replacement. [[1]](diffhunk://#diff-ebd366a6ccebff87772fc1844a80b4d3d62420539e4d9d6a19d74b30a048c046L3-R5) [[2]](diffhunk://#diff-ebd366a6ccebff87772fc1844a80b4d3d62420539e4d9d6a19d74b30a048c046L13-R20) [[3]](diffhunk://#diff-ebd366a6ccebff87772fc1844a80b4d3d62420539e4d9d6a19d74b30a048c046L29-R86) [[4]](diffhunk://#diff-ebd366a6ccebff87772fc1844a80b4d3d62420539e4d9d6a19d74b30a048c046L74-R97) [[5]](diffhunk://#diff-ebd366a6ccebff87772fc1844a80b4d3d62420539e4d9d6a19d74b30a048c046L102-R128) [[6]](diffhunk://#diff-ebd366a6ccebff87772fc1844a80b4d3d62420539e4d9d6a19d74b30a048c046L121-R230) * Updated `ColumnizedLogLine` to implement the new memory-based interface and expose both old and new properties for compatibility. **Unit test adjustments:** * Updated unit tests in `ColumnTests.cs` to use `AsMemory()` for test values and to call `.ToString()` when checking `DisplayValue` for string assertions. Also added suppression attributes for globalization warnings in tests. [[1]](diffhunk://#diff-0360b593d6854dd1529b1803a960513e97d09daed09033e70748b6dfc35aa4a5L47-R47) [[2]](diffhunk://#diff-0360b593d6854dd1529b1803a960513e97d09daed09033e70748b6dfc35aa4a5L60-R61) [[3]](diffhunk://#diff-0360b593d6854dd1529b1803a960513e97d09daed09033e70748b6dfc35aa4a5L72-R72) [[4]](diffhunk://#diff-0360b593d6854dd1529b1803a960513e97d09daed09033e70748b6dfc35aa4a5R83-R88) [[5]](diffhunk://#diff-0360b593d6854dd1529b1803a960513e97d09daed09033e70748b6dfc35aa4a5R107-R112) **General codebase cleanup:** * Removed unused `using` statements from several interface files for clarity and maintainability. [[1]](diffhunk://#diff-14fa0b69b47a0ea7de5d351a09d989c6f1725c7c23145f269a73b70a40312513L1-L5) [[2]](diffhunk://#diff-18d4b090367c70b89766d89c953aedf0dce8eb04edc10724a6d4b2c2f1fc384dL1-L5) [[3]](diffhunk://#diff-bf531b4bca29ffb50a7baa5fa171d4dc636eb26edc1a013f2ba089e826cf2e15L1-L4) * Added or improved XML documentation comments on interfaces for better code documentation. [[1]](diffhunk://#diff-9bbe6cc05b779d408640f0ef6cd92063a47c2967f35614249f8a2ca92730f0c8L1-R8) [[2]](diffhunk://#diff-5820099353f1719a29f19a82fd07602bf7ee6c4c447716e0941d3a709a322845R1-R18) These changes collectively modernize the columnizer infrastructure to be more memory-efficient and extensible, paving the way for future performance improvements and better support for large log files.
2 parents eaf2dcc + 4e3bd3f commit 88ff2f0

File tree

134 files changed

+8340
-1660
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+8340
-1660
lines changed

src/ColumnizerLib.UnitTests/ColumnTests.cs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public void Column_TruncatesAtConfiguredDisplayLength ()
4444
Column.SetMaxDisplayLength(10_000);
4545

4646
// Create a line longer than the display max length
47-
var longValue = new StringBuilder().Append('X', 15_000).ToString();
47+
var longValue = new StringBuilder().Append('X', 15_000).ToString().AsMemory();
4848

4949
Column column = new()
5050
{
@@ -57,8 +57,8 @@ public void Column_TruncatesAtConfiguredDisplayLength ()
5757

5858
// DisplayValue should be truncated at 10,000 with "..." appended
5959
Assert.That(column.DisplayValue.Length, Is.EqualTo(10_003)); // 10000 + "..."
60-
Assert.That(column.DisplayValue.EndsWith("...", StringComparison.OrdinalIgnoreCase), Is.True);
61-
Assert.That(column.DisplayValue.StartsWith("XXX", StringComparison.OrdinalIgnoreCase), Is.True);
60+
Assert.That(column.DisplayValue.ToString().EndsWith("...", StringComparison.OrdinalIgnoreCase), Is.True);
61+
Assert.That(column.DisplayValue.ToString().StartsWith("XXX", StringComparison.OrdinalIgnoreCase), Is.True);
6262

6363
// Reset for other tests
6464
Column.SetMaxDisplayLength(20_000);
@@ -69,7 +69,7 @@ public void Column_NoTruncationWhenBelowLimit ()
6969
{
7070
Column.SetMaxDisplayLength(20_000);
7171

72-
var normalValue = new StringBuilder().Append('Y', 5_000).ToString();
72+
var normalValue = new StringBuilder().Append('Y', 5_000).ToString().AsMemory();
7373
Column column = new()
7474
{
7575
FullValue = normalValue
@@ -80,37 +80,28 @@ public void Column_NoTruncationWhenBelowLimit ()
8080
}
8181

8282
[Test]
83+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
8384
public void Column_NullCharReplacement ()
8485
{
8586
Column column = new()
8687
{
87-
FullValue = "asdf\0"
88+
FullValue = "asdf\0".AsMemory()
8889
};
8990

90-
//Switch between the different implementation for the windows versions
91-
//Not that great solution but currently I'm out of ideas, I know that currently
92-
//only one implementation depending on the windows version is executed
93-
if (Environment.Version >= Version.Parse("6.2"))
94-
{
95-
Assert.That(column.DisplayValue, Is.EqualTo("asdf␀"));
96-
}
97-
else
98-
{
99-
Assert.That(column.DisplayValue, Is.EqualTo("asdf "));
100-
}
101-
102-
Assert.That(column.FullValue, Is.EqualTo("asdf\0"));
91+
Assert.That(column.DisplayValue.ToString(), Is.EqualTo("asdf "));
92+
Assert.That(column.FullValue.ToString(), Is.EqualTo("asdf\0"));
10393
}
10494

10595
[Test]
96+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Test")]
10697
public void Column_TabReplacement ()
10798
{
10899
Column column = new()
109100
{
110-
FullValue = "asdf\t"
101+
FullValue = "asdf\t".AsMemory()
111102
};
112103

113-
Assert.That(column.DisplayValue, Is.EqualTo("asdf "));
114-
Assert.That(column.FullValue, Is.EqualTo("asdf\t"));
104+
Assert.That(column.DisplayValue.ToString(), Is.EqualTo("asdf "));
105+
Assert.That(column.FullValue.ToString(), Is.EqualTo("asdf\t"));
115106
}
116107
}

src/ColumnizerLib/Column.cs

Lines changed: 143 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
namespace ColumnizerLib;
22

3-
public class Column : IColumn
3+
public class Column : IColumnMemory
44
{
5+
//TODO Memory Functions need implementation
56
#region Fields
67

78
private const string REPLACEMENT = "...";
@@ -10,14 +11,14 @@ public class Column : IColumn
1011
// Can be configured via SetMaxDisplayLength()
1112
private static int _maxDisplayLength = 20_000;
1213

13-
private static readonly List<Func<string, string>> _replacements = [
14+
private static readonly List<Func<ReadOnlyMemory<char>, ReadOnlyMemory<char>>> _replacementsMemory = [
1415
//replace tab with 3 spaces, from old coding. Needed???
15-
input => input.Replace("\t", " ", StringComparison.Ordinal),
16+
ReplaceTab,
1617

17-
//shorten string if it exceeds maxLength
18-
input => input.Length > _maxDisplayLength
19-
? string.Concat(input.AsSpan(0, _maxDisplayLength), REPLACEMENT)
20-
: input
18+
//shorten string if it exceeds maxLength
19+
input => input.Length > _maxDisplayLength
20+
? ShortenMemory(input, _maxDisplayLength)
21+
: input
2122
];
2223

2324
#endregion
@@ -26,43 +27,63 @@ public class Column : IColumn
2627

2728
static Column ()
2829
{
29-
if (Environment.Version >= Version.Parse("6.2"))
30-
{
31-
//Win8 or newer support full UTF8 chars with the preinstalled fonts.
32-
//Replace null char with UTF8 Symbol U+2400 (␀)
33-
_replacements.Add(input => input.Replace("\0", "␀", StringComparison.Ordinal));
34-
}
35-
else
36-
{
37-
//Everything below Win8 the installed fonts seems to not to support reliabel
38-
//Replace null char with space
39-
//.net 10 does no longer support windows lower then windows 10
40-
//TODO: remove if with one of the next releases
41-
//https://github.com/dotnet/core/blob/main/release-notes/10.0/supported-os.md
42-
_replacements.Add(input => input.Replace("\0", " ", StringComparison.Ordinal));
43-
}
30+
//.net 10 only supports Windows10+ which has full UTF8-font support
31+
// Replace null char with UTF-8 Symbol U+2400 (␀)
32+
//https://github.com/dotnet/core/blob/main/release-notes/10.0/supported-os.md
33+
_replacementsMemory.Add(input => ReplaceNullChar(input, ' '));
4434

45-
EmptyColumn = new Column { FullValue = string.Empty };
35+
EmptyColumn = new Column { FullValue = ReadOnlyMemory<char>.Empty };
4636
}
4737

4838
#endregion
4939

5040
#region Properties
5141

52-
public static IColumn EmptyColumn { get; }
42+
public static IColumnMemory EmptyColumn { get; }
43+
44+
[Obsolete]
45+
IColumnizedLogLine IColumn.Parent { get; }
46+
47+
[Obsolete]
48+
string IColumn.FullValue
49+
{
50+
get;
51+
//set
52+
//{
53+
// field = value;
54+
55+
// var temp = FullValue.ToString();
56+
57+
// foreach (var replacement in _replacements)
58+
// {
59+
// temp = replacement(temp);
60+
// }
61+
62+
// DisplayValue = temp.AsMemory();
63+
//}
64+
}
65+
66+
[Obsolete("Use the DisplayValue property of IColumnMemory")]
67+
string IColumn.DisplayValue { get; }
68+
69+
[Obsolete("Use Text property of ITextValueMemory")]
70+
string ITextValue.Text => DisplayValue.ToString();
5371

54-
public IColumnizedLogLine Parent { get; set; }
72+
public IColumnizedLogLineMemory Parent
73+
{
74+
get; set => field = value;
75+
}
5576

56-
public string FullValue
77+
public ReadOnlyMemory<char> FullValue
5778
{
5879
get;
5980
set
6081
{
6182
field = value;
6283

63-
var temp = FullValue;
84+
var temp = value;
6485

65-
foreach (var replacement in _replacements)
86+
foreach (var replacement in _replacementsMemory)
6687
{
6788
temp = replacement(temp);
6889
}
@@ -71,9 +92,9 @@ public string FullValue
7192
}
7293
}
7394

74-
public string DisplayValue { get; private set; }
95+
public ReadOnlyMemory<char> DisplayValue { get; private set; }
7596

76-
public string Text => DisplayValue;
97+
public ReadOnlyMemory<char> Text => DisplayValue;
7798

7899
#endregion
79100

@@ -99,12 +120,12 @@ public static void SetMaxDisplayLength (int maxLength)
99120
/// </summary>
100121
public static int GetMaxDisplayLength () => _maxDisplayLength;
101122

102-
public static Column[] CreateColumns (int count, IColumnizedLogLine parent)
123+
public static Column[] CreateColumns (int count, IColumnizedLogLineMemory parent)
103124
{
104-
return CreateColumns(count, parent, string.Empty);
125+
return CreateColumns(count, parent, ReadOnlyMemory<char>.Empty);
105126
}
106127

107-
public static Column[] CreateColumns (int count, IColumnizedLogLine parent, string defaultValue)
128+
public static Column[] CreateColumns (int count, IColumnizedLogLineMemory parent, ReadOnlyMemory<char> defaultValue)
108129
{
109130
var output = new Column[count];
110131

@@ -118,7 +139,95 @@ public static Column[] CreateColumns (int count, IColumnizedLogLine parent, stri
118139

119140
public override string ToString ()
120141
{
121-
return DisplayValue ?? string.Empty;
142+
return DisplayValue.ToString() ?? ReadOnlyMemory<char>.Empty.ToString();
143+
}
144+
145+
#endregion
146+
147+
#region Private Methods
148+
149+
/// <summary>
150+
/// Replaces tab characters with two spaces in the memory buffer.
151+
/// </summary>
152+
private static ReadOnlyMemory<char> ReplaceTab (ReadOnlyMemory<char> input)
153+
{
154+
var span = input.Span;
155+
var tabIndex = span.IndexOf('\t');
156+
157+
if (tabIndex == -1)
158+
{
159+
return input;
160+
}
161+
162+
// Count total tabs to calculate new length
163+
var tabCount = 0;
164+
foreach (var c in span)
165+
{
166+
if (c == '\t')
167+
{
168+
tabCount++;
169+
}
170+
}
171+
172+
// Allocate new buffer: original length + (tabCount * 1) since we replace 1 char with 2
173+
var newLength = input.Length + tabCount;
174+
var buffer = new char[newLength];
175+
var bufferPos = 0;
176+
177+
for (var i = 0; i < span.Length; i++)
178+
{
179+
if (span[i] == '\t')
180+
{
181+
buffer[bufferPos++] = ' ';
182+
buffer[bufferPos++] = ' ';
183+
}
184+
else
185+
{
186+
buffer[bufferPos++] = span[i];
187+
}
188+
}
189+
190+
return buffer;
191+
}
192+
193+
/// <summary>
194+
/// Shortens the memory buffer to the specified maximum length and appends "...".
195+
/// </summary>
196+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Non Localiced Parameter")]
197+
private static ReadOnlyMemory<char> ShortenMemory (ReadOnlyMemory<char> input, int maxLength)
198+
{
199+
var buffer = new char[maxLength + REPLACEMENT.Length];
200+
input.Span[..maxLength].CopyTo(buffer);
201+
REPLACEMENT.AsSpan().CopyTo(buffer.AsSpan(maxLength));
202+
return buffer;
203+
}
204+
205+
/// <summary>
206+
/// Replaces null characters with the specified replacement character.
207+
/// </summary>
208+
private static ReadOnlyMemory<char> ReplaceNullChar (ReadOnlyMemory<char> input, char replacement)
209+
{
210+
var span = input.Span;
211+
var nullIndex = span.IndexOf('\0');
212+
213+
if (nullIndex == -1)
214+
{
215+
return input;
216+
}
217+
218+
// Need to create a new buffer since we're modifying content
219+
var buffer = new char[input.Length];
220+
span.CopyTo(buffer);
221+
222+
for (var i = 0; i < buffer.Length; i++)
223+
{
224+
if (buffer[i] == '\0')
225+
{
226+
buffer[i] = replacement;
227+
}
228+
}
229+
230+
return buffer;
122231
}
123232

124233
#endregion
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
namespace ColumnizerLib;
22

3-
public class ColumnizedLogLine : IColumnizedLogLine
3+
public class ColumnizedLogLine : IColumnizedLogLineMemory
44
{
55
#region Properties
66

7-
public ILogLine LogLine { get; set; }
7+
[Obsolete("Use the Property of IColumnizedLogLineMemory")]
8+
ILogLine IColumnizedLogLine.LogLine { get; }
89

9-
public IColumn[] ColumnValues { get; set; }
10+
[Obsolete("Use the Property of IColumnizedLogLineMemory")]
11+
IColumn[] IColumnizedLogLine.ColumnValues { get; }
12+
13+
public ILogLineMemory LogLine { get; set; }
14+
15+
public IColumnMemory[] ColumnValues { get; set; }
1016

1117
#endregion
1218
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace ColumnizerLib;
2+
3+
public interface IAutoLogLineMemoryColumnizerCallback : IAutoLogLineColumnizerCallback
4+
{
5+
/// <summary>
6+
/// Returns the log line with the given index (zero-based).
7+
/// </summary>
8+
/// <param name="lineNum">Number of the line to be retrieved</param>
9+
/// <returns>A string with line content or null if line number is out of range</returns>
10+
ILogLineMemory GetLogLineMemory (int lineNum);
11+
}

src/ColumnizerLib/IColumn.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
61
namespace ColumnizerLib;
72

83
public interface IColumn : ITextValue

src/ColumnizerLib/IColumnMemory.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace ColumnizerLib;
2+
3+
public interface IColumnMemory : IColumn, ITextValueMemory
4+
{
5+
#region Properties
6+
7+
new IColumnizedLogLineMemory Parent { get; }
8+
9+
new ReadOnlyMemory<char> FullValue { get; }
10+
11+
new ReadOnlyMemory<char> DisplayValue { get; }
12+
13+
#endregion
14+
}

src/ColumnizerLib/IColumnizedLogLine.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
61
namespace ColumnizerLib;
72

83
public interface IColumnizedLogLine
@@ -11,7 +6,6 @@ public interface IColumnizedLogLine
116

127
ILogLine LogLine { get; }
138

14-
159
IColumn[] ColumnValues { get; }
1610

1711
#endregion

0 commit comments

Comments
 (0)