/// <summary> /// Compares the current content of this writer with another one. /// </summary> /// <exception cref="InvalidOperationException">Content is not available, the builder has been linked with another one.</exception> public bool ContentEquals(BlobBuilder other) { if (!IsHead) { Throw.InvalidOperationBuilderAlreadyLinked(); } if (ReferenceEquals(this, other)) { return(true); } if (other == null) { return(false); } if (!other.IsHead) { Throw.InvalidOperationBuilderAlreadyLinked(); } if (Count != other.Count) { return(false); } var leftEnumerator = GetChunks(); var rightEnumerator = other.GetChunks(); int leftStart = 0; int rightStart = 0; bool leftContinues = leftEnumerator.MoveNext(); bool rightContinues = rightEnumerator.MoveNext(); while (leftContinues && rightContinues) { Debug.Assert(leftStart == 0 || rightStart == 0); var left = leftEnumerator.Current; var right = rightEnumerator.Current; int minLength = Math.Min(left.Length - leftStart, right.Length - rightStart); if (!ByteSequenceComparer.Equals(left._buffer, leftStart, right._buffer, rightStart, minLength)) { return(false); } leftStart += minLength; rightStart += minLength; // nothing remains in left chunk to compare: if (leftStart == left.Length) { leftContinues = leftEnumerator.MoveNext(); leftStart = 0; } // nothing remains in left chunk to compare: if (rightStart == right.Length) { rightContinues = rightEnumerator.MoveNext(); rightStart = 0; } } return(leftContinues == rightContinues); }
/// <exception cref="ArgumentNullException"><paramref name="suffix"/> is null.</exception> /// <exception cref="InvalidOperationException">Builder is not writable, it has been linked with another one.</exception> public void LinkSuffix(BlobBuilder suffix) { if (suffix == null) { throw new ArgumentNullException(nameof(suffix)); } // TODO: consider copying data from right to left while there is space if (!IsHead || !suffix.IsHead) { Throw.InvalidOperationBuilderAlreadyLinked(); } // avoid chaining empty chunks: if (suffix.Count == 0) { return; } bool isEmpty = Count == 0; // swap buffers of the heads: var suffixBuffer = suffix._buffer; uint suffixLength = suffix._length; int suffixPreviousLength = suffix.PreviousLength; int oldSuffixLength = suffix.Length; suffix._buffer = _buffer; suffix._length = FrozenLength; // suffix is not a head anymore _buffer = suffixBuffer; _length = suffixLength; PreviousLength += suffix.Length + suffixPreviousLength; // Update the _previousLength of the suffix so that suffix.Count = suffix._previousLength + suffix.Length doesn't change. // Note that the resulting previous length might be negative. // The value is not used, other than for calculating the value of Count property. suffix._previousLengthOrFrozenSuffixLengthDelta = suffixPreviousLength + oldSuffixLength - suffix.Length; if (!isEmpty) { // First and last chunks: // // [First]->[]->[Last] <- [this] [SuffixFirst]->[]->[SuffixLast] <- [suffix] // ^___________| ^_________________| // // Degenerate cases: // this == First == Last and/or suffix == SuffixFirst == SuffixLast. var first = FirstChunk; var suffixFirst = suffix.FirstChunk; var last = _nextOrPrevious; var suffixLast = suffix._nextOrPrevious; // Relink like so: // [First]->[]->[Last] -> [suffix] -> [SuffixFirst]->[]->[SuffixLast] <- [this] // ^_______________________________________________________| _nextOrPrevious = suffixLast; suffix._nextOrPrevious = (suffixFirst != suffix) ? suffixFirst : (first != this) ? first : suffix; if (last != this) { last._nextOrPrevious = suffix; } if (suffixLast != suffix) { suffixLast._nextOrPrevious = (first != this) ? first : suffix; } } CheckInvariants(); suffix.CheckInvariants(); }
// internal for testing internal void ClearChunk() { _length = 0; _previousLengthOrFrozenSuffixLengthDelta = 0; _nextOrPrevious = this; }