diff --git a/src/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs b/src/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs index 8fa8b40975..9fe7978496 100644 --- a/src/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs +++ b/src/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs @@ -37,7 +37,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache // therefore it doesn't make any difference what type of serialization is used. // To make things simpler, we are using BinaryReader and BinaryWriter since // BinaryFormatter is not implemented in .NET Standard 1.x. - internal class CharBlockArray : ICharSequence + internal class CharBlockArray : IAppendable, ICharSequence { private const long serialVersionUID = 1L; @@ -130,10 +130,7 @@ internal virtual int IndexInBlock(int index) return index % blockSize; } - public virtual CharBlockArray Append(ICharSequence chars) - { - return Append(chars, 0, chars.Length); - } +#nullable enable public virtual CharBlockArray Append(char c) { @@ -147,19 +144,96 @@ public virtual CharBlockArray Append(char c) return this; } - public virtual CharBlockArray Append(ICharSequence chars, int start, int length) + public virtual CharBlockArray Append(ICharSequence? value) { - int end = start + length; - for (int i = start; i < end; i++) + if (value is null) // needed for Appendable compliance + { + return this; // No-op + } + + return Append(value, 0, value.Length); + } + + public virtual CharBlockArray Append(ICharSequence? value, int startIndex, int length) + { + // LUCENENET: Changed semantics to be the same as the StringBuilder in .NET + if (startIndex < 0) + throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} must not be negative."); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(length)} must not be negative."); + + if (value is null) { - Append(chars[i]); + if (startIndex == 0 && length == 0) + return this; + throw new ArgumentNullException(nameof(value)); + } + if (length == 0) + return this; + if (startIndex > value.Length - length) + throw new ArgumentOutOfRangeException(nameof(startIndex), $"Index and length must refer to a location within the string. For example {nameof(startIndex)} + {nameof(length)} <= {nameof(Length)}."); + + + int end = startIndex + length; + for (int i = startIndex; i < end; i++) + { + Append(value[i]); + } + return this; + } + + public virtual CharBlockArray Append(char[]? value) + { + if (value is null) // needed for Appendable compliance + { + return this; // No-op + } + + int remain = value.Length; + int offset = 0; + while (remain > 0) + { + if (this.current.length == this.blockSize) + { + AddBlock(); + } + int toCopy = remain; + int remainingInBlock = this.blockSize - this.current.length; + if (remainingInBlock < toCopy) + { + toCopy = remainingInBlock; + } + Arrays.Copy(value, offset, this.current.chars, this.current.length, toCopy); + offset += toCopy; + remain -= toCopy; + this.current.length += toCopy; } + + this.length += value.Length; return this; } - public virtual CharBlockArray Append(char[] chars, int start, int length) + public virtual CharBlockArray Append(char[]? value, int startIndex, int length) { - int offset = start; + // LUCENENET: Changed semantics to be the same as the StringBuilder in .NET + if (startIndex < 0) + throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} must not be negative."); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(length)} must not be negative."); + + if (value is null) + { + if (startIndex == 0 && length == 0) + return this; + throw new ArgumentNullException(nameof(value)); + } + if (length == 0) + return this; + if (startIndex > value.Length - length) + throw new ArgumentOutOfRangeException(nameof(startIndex), $"Index and length must refer to a location within the string. For example {nameof(startIndex)} + {nameof(length)} <= {nameof(Length)}."); + + + int offset = startIndex; int remain = length; while (remain > 0) { @@ -173,7 +247,7 @@ public virtual CharBlockArray Append(char[] chars, int start, int length) { toCopy = remainingInBlock; } - Arrays.Copy(chars, offset, this.current.chars, this.current.length, toCopy); + Arrays.Copy(value, offset, this.current.chars, this.current.length, toCopy); offset += toCopy; remain -= toCopy; this.current.length += toCopy; @@ -183,9 +257,14 @@ public virtual CharBlockArray Append(char[] chars, int start, int length) return this; } - public virtual CharBlockArray Append(string s) + public virtual CharBlockArray Append(string? value) { - int remain = s.Length; + if (value is null) // needed for Appendable compliance + { + return this; // No-op + } + + int remain = value.Length; int offset = 0; while (remain > 0) { @@ -199,16 +278,159 @@ public virtual CharBlockArray Append(string s) { toCopy = remainingInBlock; } - s.CopyTo(offset, this.current.chars, this.current.length, toCopy); + value.CopyTo(offset, this.current.chars, this.current.length, toCopy); offset += toCopy; remain -= toCopy; this.current.length += toCopy; } - this.length += s.Length; + this.length += value.Length; return this; } + public virtual CharBlockArray Append(string? value, int startIndex, int length) + { + // LUCENENET: Changed semantics to be the same as the StringBuilder in .NET + if (startIndex < 0) + throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} must not be negative."); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(length)} must not be negative."); + + if (value is null) + { + if (startIndex == 0 && length == 0) + return this; + throw new ArgumentNullException(nameof(value)); + } + if (length == 0) + return this; + if (startIndex > value.Length - length) + throw new ArgumentOutOfRangeException(nameof(startIndex), $"Index and length must refer to a location within the string. For example {nameof(startIndex)} + {nameof(length)} <= {nameof(Length)}."); + + + int offset = startIndex; + int remain = length; + while (remain > 0) + { + if (this.current.length == this.blockSize) + { + AddBlock(); + } + int toCopy = remain; + int remainingInBlock = this.blockSize - this.current.length; + if (remainingInBlock < toCopy) + { + toCopy = remainingInBlock; + } + value.CopyTo(offset, this.current.chars, this.current.length, toCopy); + offset += toCopy; + remain -= toCopy; + this.current.length += toCopy; + } + + this.length += length; + return this; + } + + public virtual CharBlockArray Append(StringBuilder? value) + { + if (value is null) // needed for Appendable compliance + { + return this; // No-op + } + + int remain = value.Length; + int offset = 0; + while (remain > 0) + { + if (this.current.length == this.blockSize) + { + AddBlock(); + } + int toCopy = remain; + int remainingInBlock = this.blockSize - this.current.length; + if (remainingInBlock < toCopy) + { + toCopy = remainingInBlock; + } + value.CopyTo(offset, this.current.chars, this.current.length, toCopy); + offset += toCopy; + remain -= toCopy; + this.current.length += toCopy; + } + + this.length += value.Length; + return this; + } + + public virtual CharBlockArray Append(StringBuilder? value, int startIndex, int length) + { + // LUCENENET: Changed semantics to be the same as the StringBuilder in .NET + if (startIndex < 0) + throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} must not be negative."); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(length)} must not be negative."); + + if (value is null) + { + if (startIndex == 0 && length == 0) + return this; + throw new ArgumentNullException(nameof(value)); + } + if (length == 0) + return this; + if (startIndex > value.Length - length) + throw new ArgumentOutOfRangeException(nameof(startIndex), $"Index and length must refer to a location within the string. For example {nameof(startIndex)} + {nameof(length)} <= {nameof(Length)}."); + + int offset = startIndex; + int remain = length; + while (remain > 0) + { + if (this.current.length == this.blockSize) + { + AddBlock(); + } + int toCopy = remain; + int remainingInBlock = this.blockSize - this.current.length; + if (remainingInBlock < toCopy) + { + toCopy = remainingInBlock; + } + value.CopyTo(offset, this.current.chars, this.current.length, toCopy); + offset += toCopy; + remain -= toCopy; + this.current.length += toCopy; + } + + this.length += length; + return this; + } + +#nullable restore + + #region IAppendable Members + + IAppendable IAppendable.Append(char value) => Append(value); + + IAppendable IAppendable.Append(string value) => Append(value); + + IAppendable IAppendable.Append(string value, int startIndex, int count) => Append(value, startIndex, count); + + IAppendable IAppendable.Append(StringBuilder value) => Append(value); + + IAppendable IAppendable.Append(StringBuilder value, int startIndex, int count) => Append(value, startIndex, count); + + IAppendable IAppendable.Append(char[] value) => Append(value); + + IAppendable IAppendable.Append(char[] value, int startIndex, int count) => Append(value, startIndex, count); + + IAppendable IAppendable.Append(ICharSequence value) => Append(value); + + IAppendable IAppendable.Append(ICharSequence value, int startIndex, int count) => Append(value, startIndex, count); + + + #endregion + // LUCENENET specific - replaced with this[index] //public virtual char CharAt(int index) //{ diff --git a/src/Lucene.Net.Tests.Facet/Taxonomy/WriterCache/TestCharBlockArray.cs b/src/Lucene.Net.Tests.Facet/Taxonomy/WriterCache/TestCharBlockArray.cs index e268fadc3c..8b2c667714 100644 --- a/src/Lucene.Net.Tests.Facet/Taxonomy/WriterCache/TestCharBlockArray.cs +++ b/src/Lucene.Net.Tests.Facet/Taxonomy/WriterCache/TestCharBlockArray.cs @@ -1,5 +1,9 @@ // Lucene version compatibility level 4.8.1 +using J2N.IO; +using J2N.Text; +using Lucene.Net.Attributes; using NUnit.Framework; +using System; using System.IO; using System.Text; using Assert = Lucene.Net.TestFramework.Assert; @@ -119,5 +123,200 @@ private static void AssertEqualsInternal(string msg, StringBuilder expected, Cha Assert.AreEqual(expected2[i], actual[i], msg); } } + + // LUCENENET: Borrowed this test from TestCharTermAttributeImpl + [Test, LuceneNetSpecific] + public virtual void TestAppendableInterface() + { + CharBlockArray t = new CharBlockArray(); + //Formatter formatter = new Formatter(t, Locale.ROOT); + //formatter.format("%d", 1234); + //Assert.AreEqual("1234", t.ToString()); + //formatter.format("%d", 5678); + // LUCENENET: We don't have a formatter in .NET, so continue from here + t.Append("12345678"); // LUCENENET specific overload that accepts string + Assert.AreEqual("12345678", t.ToString()); + t = new CharBlockArray(); + t.Append("12345678".ToCharArray()); // LUCENENET specific overload that accepts char[] + Assert.AreEqual("12345678", t.ToString()); + t.Append('9'); + Assert.AreEqual("123456789", t.ToString()); + t.Append("0".AsCharSequence()); + Assert.AreEqual("1234567890", t.ToString()); + t.Append("0123456789".AsCharSequence(), 1, 3 - 1); // LUCENENET: Corrected 3rd parameter + Assert.AreEqual("123456789012", t.ToString()); + //t.Append((ICharSequence) CharBuffer.wrap("0123456789".ToCharArray()), 3, 5); + t.Append("0123456789".ToCharArray(), 3, 5 - 3); // LUCENENET: no CharBuffer in .NET, so we test char[], start, end overload // LUCENENET: Corrected 3rd parameter + Assert.AreEqual("12345678901234", t.ToString()); + t.Append((ICharSequence)t); + Assert.AreEqual("1234567890123412345678901234", t.ToString()); + t.Append(/*(ICharSequence)*/ new StringBuilder("0123456789"), 5, 7 - 5); // LUCENENET: StringBuilder doesn't implement ICharSequence, corrected 3rd argument + Assert.AreEqual("123456789012341234567890123456", t.ToString()); + t.Append(/*(ICharSequence)*/ new StringBuilder(t.ToString())); // LUCENENET: StringBuilder doesn't implement ICharSequence + Assert.AreEqual("123456789012341234567890123456123456789012341234567890123456", t.ToString()); + // very wierd, to test if a subSlice is wrapped correct :) + CharBuffer buf = CharBuffer.Wrap("0123456789".ToCharArray(), 3, 5); + Assert.AreEqual("34567", buf.ToString()); + t = new CharBlockArray(); + t.Append((ICharSequence)buf, 1, 2 - 1); // LUCENENET: Corrected 3rd parameter + Assert.AreEqual("4", t.ToString()); + CharBlockArray t2 = new CharBlockArray(); + t2.Append("test"); + t.Append((ICharSequence)t2); + Assert.AreEqual("4test", t.ToString()); + t.Append((ICharSequence)t2, 1, 2 - 1); // LUCENENET: Corrected 3rd parameter + Assert.AreEqual("4teste", t.ToString()); + + try + { + t.Append((ICharSequence)t2, 1, 5 - 1); // LUCENENET: Corrected 3rd parameter + Assert.Fail("Should throw ArgumentOutOfRangeException"); + } +#pragma warning disable 168 + catch (ArgumentOutOfRangeException iobe) +#pragma warning restore 168 + { + } + + try + { + t.Append((ICharSequence)t2, 1, 0 - 1); // LUCENENET: Corrected 3rd parameter + Assert.Fail("Should throw ArgumentOutOfRangeException"); + } +#pragma warning disable 168 + catch (ArgumentOutOfRangeException iobe) +#pragma warning restore 168 + { + } + + string expected = t.ToString(); + t.Append((ICharSequence)null); // No-op + Assert.AreEqual(expected, t.ToString()); + + + // LUCENENET specific - test string overloads + try + { + t.Append((string)t2.ToString(), 1, 5 - 1); // LUCENENET: Corrected 3rd parameter + Assert.Fail("Should throw ArgumentOutOfRangeException"); + } +#pragma warning disable 168 + catch (ArgumentOutOfRangeException iobe) +#pragma warning restore 168 + { + } + + try + { + t.Append((string)t2.ToString(), 1, 0 - 1); // LUCENENET: Corrected 3rd parameter + Assert.Fail("Should throw ArgumentOutOfRangeException"); + } +#pragma warning disable 168 + catch (ArgumentOutOfRangeException iobe) +#pragma warning restore 168 + { + } + + expected = t.ToString(); + t.Append((string)null); // No-op + Assert.AreEqual(expected, t.ToString()); + + // LUCENENET specific - test char[] overloads + try + { + t.Append((char[])t2.ToString().ToCharArray(), 1, 5 - 1); // LUCENENET: Corrected 3rd parameter + Assert.Fail("Should throw ArgumentOutOfRangeException"); + } +#pragma warning disable 168 + catch (ArgumentOutOfRangeException iobe) +#pragma warning restore 168 + { + } + + try + { + t.Append((char[])t2.ToString().ToCharArray(), 1, 0 - 1); // LUCENENET: Corrected 3rd parameter + Assert.Fail("Should throw ArgumentOutOfRangeException"); + } +#pragma warning disable 168 + catch (ArgumentOutOfRangeException iobe) +#pragma warning restore 168 + { + } + + expected = t.ToString(); + t.Append((char[])null); // No-op + Assert.AreEqual(expected, t.ToString()); + } + + // LUCENENET: Borrowed this test from TestCharTermAttributeImpl + [Test, LuceneNetSpecific] + public virtual void TestAppendableInterfaceWithLongSequences() + { + CharBlockArray t = new CharBlockArray(); + t.Append("01234567890123456789012345678901234567890123456789"); // LUCENENET specific overload that accepts string + assertEquals("01234567890123456789012345678901234567890123456789", t.ToString()); + t.Append("01234567890123456789012345678901234567890123456789", 3, 50 - 3); // LUCENENET specific overload that accepts string, startIndex, charCount + Assert.AreEqual("0123456789012345678901234567890123456789012345678934567890123456789012345678901234567890123456789", t.ToString()); + t = new CharBlockArray(); + t.Append("01234567890123456789012345678901234567890123456789".ToCharArray()); // LUCENENET specific overload that accepts char[] + assertEquals("01234567890123456789012345678901234567890123456789", t.ToString()); + t.Append("01234567890123456789012345678901234567890123456789".ToCharArray(), 3, 50 - 3); // LUCENENET specific overload that accepts char[], startIndex, charCount + Assert.AreEqual("0123456789012345678901234567890123456789012345678934567890123456789012345678901234567890123456789", t.ToString()); + t = new CharBlockArray(); + t.Append(new StringCharSequence("01234567890123456789012345678901234567890123456789")); + //t.Append((ICharSequence) CharBuffer.wrap("01234567890123456789012345678901234567890123456789".ToCharArray()), 3, 50); // LUCENENET: No CharBuffer in .NET + t.Append("01234567890123456789012345678901234567890123456789".ToCharArray(), 3, 50 - 3); // LUCENENET specific overload that accepts char[], startIndex, charCount + // "01234567890123456789012345678901234567890123456789" + Assert.AreEqual("0123456789012345678901234567890123456789012345678934567890123456789012345678901234567890123456789", t.ToString()); + t = new CharBlockArray(); + t.Append(/*(ICharSequence)*/ new StringBuilder("01234567890123456789"), 5, 17 - 5); // LUCENENET: StringBuilder doesn't implement ICharSequence + Assert.AreEqual((ICharSequence)new StringCharSequence("567890123456"), t /*.ToString()*/); + t.Append(new StringBuilder(t.ToString())); + Assert.AreEqual((ICharSequence)new StringCharSequence("567890123456567890123456"), t /*.ToString()*/); + // very wierd, to test if a subSlice is wrapped correct :) + CharBuffer buf = CharBuffer.Wrap("012345678901234567890123456789".ToCharArray(), 3, 15); + Assert.AreEqual("345678901234567", buf.ToString()); + t = new CharBlockArray(); + t.Append(buf, 1, 14 - 1); + Assert.AreEqual("4567890123456", t.ToString()); + + // finally use a completely custom ICharSequence that is not catched by instanceof checks + const string longTestString = "012345678901234567890123456789"; + t.Append(new CharSequenceAnonymousClass(longTestString)); + Assert.AreEqual("4567890123456" + longTestString, t.ToString()); + } + + private sealed class CharSequenceAnonymousClass : ICharSequence + { + private string longTestString; + + public CharSequenceAnonymousClass(string longTestString) + { + this.longTestString = longTestString; + } + + bool ICharSequence.HasValue => longTestString != null; // LUCENENET specific (implementation of ICharSequence) + + public char CharAt(int i) + { + return longTestString[i]; + } + + // LUCENENET specific - Added to .NETify + public char this[int i] => longTestString[i]; + + public int Length => longTestString.Length; + + public ICharSequence Subsequence(int startIndex, int length) // LUCENENET: Changed semantics to startIndex/length to match .NET + { + return new StringCharSequence(longTestString.Substring(startIndex, length)); + } + + public override string ToString() + { + return longTestString; + } + } } } \ No newline at end of file