blob: 817282e9c0d2e68ee3b1c401907734e17dce3dc7 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Apache.Arrow.Memory;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Apache.Arrow
{
public partial struct ArrowBuffer
{
/// <summary>
/// The <see cref="Builder{T}"/> class is able to append value-type items, with fluent-style methods, to build
/// up an <see cref="ArrowBuffer"/> of contiguous items.
/// </summary>
/// <remarks>
/// Note that <see cref="bool"/> is not supported as a generic type argument for this class. Please use
/// <see cref="BitmapBuilder"/> instead.
/// </remarks>
/// <typeparam name="T">Value-type of item to build into a buffer.</typeparam>
public class Builder<T>
where T : struct
{
private const int DefaultCapacity = 8;
private readonly int _size;
/// <summary>
/// Gets the number of items that can be contained in the memory allocated by the current instance.
/// </summary>
public int Capacity => Memory.Length / _size;
/// <summary>
/// Gets the number of items currently appended.
/// </summary>
public int Length { get; private set; }
/// <summary>
/// Gets the raw byte memory underpinning the builder.
/// </summary>
public Memory<byte> Memory { get; private set; }
/// <summary>
/// Gets the span of memory underpinning the builder.
/// </summary>
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Memory.Span.CastTo<T>();
}
/// <summary>
/// Creates an instance of the <see cref="Builder{T}"/> class.
/// </summary>
/// <param name="capacity">Number of items of initial capacity to reserve.</param>
public Builder(int capacity = DefaultCapacity)
{
// Using `bool` as the template argument, if used in an unrestricted fashion, would result in a buffer
// with inappropriate contents being produced. Because C# does not support template specialisation,
// and because generic type constraints do not support negation, we will throw a runtime error to
// indicate that such a template type is not supported.
if (typeof(T) == typeof(bool))
{
throw new NotSupportedException(
$"An instance of {nameof(Builder<T>)} cannot be instantiated, as `bool` is not an " +
$"appropriate generic type to use with this class - please use {nameof(BitmapBuilder)} " +
$"instead");
}
_size = Unsafe.SizeOf<T>();
Memory = new byte[capacity * _size];
Length = 0;
}
/// <summary>
/// Append a buffer, assumed to contain items of the same type.
/// </summary>
/// <param name="buffer">Buffer to append.</param>
/// <returns>Returns the builder (for fluent-style composition).</returns>
public Builder<T> Append(ArrowBuffer buffer)
{
Append(buffer.Span.CastTo<T>());
return this;
}
/// <summary>
/// Append a single item.
/// </summary>
/// <param name="value">Item to append.</param>
/// <returns>Returns the builder (for fluent-style composition).</returns>
public Builder<T> Append(T value)
{
EnsureAdditionalCapacity(1);
Span[Length++] = value;
return this;
}
/// <summary>
/// Append a span of items.
/// </summary>
/// <param name="source">Source of item span.</param>
/// <returns>Returns the builder (for fluent-style composition).</returns>
public Builder<T> Append(ReadOnlySpan<T> source)
{
EnsureAdditionalCapacity(source.Length);
source.CopyTo(Span.Slice(Length, source.Length));
Length += source.Length;
return this;
}
/// <summary>
/// Append a number of items.
/// </summary>
/// <param name="values">Items to append.</param>
/// <returns>Returns the builder (for fluent-style composition).</returns>
public Builder<T> AppendRange(IEnumerable<T> values)
{
if (values != null)
{
foreach (T v in values)
{
Append(v);
}
}
return this;
}
/// <summary>
/// Reserve a given number of items' additional capacity.
/// </summary>
/// <param name="additionalCapacity">Number of items of required additional capacity.</param>
/// <returns>Returns the builder (for fluent-style composition).</returns>
public Builder<T> Reserve(int additionalCapacity)
{
if (additionalCapacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(additionalCapacity));
}
EnsureAdditionalCapacity(additionalCapacity);
return this;
}
/// <summary>
/// Resize the buffer to a given size.
/// </summary>
/// <remarks>
/// Note that if the required capacity is larger than the current length of the populated buffer so far,
/// the buffer's contents in the new, expanded region are undefined.
/// </remarks>
/// <remarks>
/// Note that if the required capacity is smaller than the current length of the populated buffer so far,
/// the buffer will be truncated and items at the end of the buffer will be lost.
/// </remarks>
/// <param name="capacity">Number of items of required capacity.</param>
/// <returns>Returns the builder (for fluent-style composition).</returns>
public Builder<T> Resize(int capacity)
{
if (capacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be non-negative");
}
EnsureCapacity(capacity);
Length = capacity;
return this;
}
/// <summary>
/// Clear all contents appended so far.
/// </summary>
/// <returns>Returns the builder (for fluent-style composition).</returns>
public Builder<T> Clear()
{
Span.Fill(default);
Length = 0;
return this;
}
/// <summary>
/// Build an Arrow buffer from the appended contents so far.
/// </summary>
/// <param name="allocator">Optional memory allocator.</param>
/// <returns>Returns an <see cref="ArrowBuffer"/> object.</returns>
public ArrowBuffer Build(MemoryAllocator allocator = default)
{
int currentBytesLength = Length * _size;
int bufferLength = checked((int)BitUtility.RoundUpToMultipleOf64(currentBytesLength));
MemoryAllocator memoryAllocator = allocator ?? MemoryAllocator.Default.Value;
IMemoryOwner<byte> memoryOwner = memoryAllocator.Allocate(bufferLength);
Memory.Slice(0, currentBytesLength).CopyTo(memoryOwner.Memory);
return new ArrowBuffer(memoryOwner);
}
private void EnsureAdditionalCapacity(int additionalCapacity)
{
EnsureCapacity(checked(Length + additionalCapacity));
}
private void EnsureCapacity(int requiredCapacity)
{
if (requiredCapacity > Capacity)
{
// TODO: specifiable growth strategy
// Double the length of the in-memory array, or use the byte count of the capacity, whichever is
// greater.
int capacity = Math.Max(requiredCapacity * _size, Memory.Length * 2);
Reallocate(capacity);
}
}
private void Reallocate(int numBytes)
{
if (numBytes != 0)
{
var memory = new Memory<byte>(new byte[numBytes]);
Memory.CopyTo(memory);
Memory = memory;
}
}
}
}
}