/* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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 System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace OpenSearch.Client
{
///
/// This class is used by which needs thread safe adding as well as expose
/// an equivalent of . Because operations from OpenSearch are executed in order none of the types in
/// System.Collection.Concurrent can't be used for this. We need to preserve insert order and exposed indexed index because
/// is ordered and lines up with allowing one to zip the two together.
///
///
[ComVisible(false)]
public sealed class BulkOperationsCollection : IList, IList
where TOperation : IBulkOperation
{
private readonly object _lock = new object();
public BulkOperationsCollection() => Items = new List();
public BulkOperationsCollection(IEnumerable operations)
{
Items = new List();
Items.AddRange(operations);
}
public int Count
{
get
{
lock (_lock)
return Items.Count;
}
}
public TOperation this[int index]
{
get
{
lock (_lock)
return Items[index];
}
set
{
lock (_lock)
{
if (index < 0 || index >= Items.Count)
throw new ArgumentOutOfRangeException("index", index, $"value {index} must be in range of {Items.Count}");
Items[index] = value;
}
}
}
bool IList.IsFixedSize => false;
bool ICollection.IsReadOnly => false;
bool IList.IsReadOnly => false;
bool ICollection.IsSynchronized => true;
object IList.this[int index]
{
get => this[index];
set
{
VerifyValueType(value);
this[index] = (TOperation)value;
}
}
private List Items { get; }
object ICollection.SyncRoot => _lock;
void ICollection.CopyTo(Array array, int index)
{
lock (_lock) ((IList)Items).CopyTo(array, index);
}
public void Add(TOperation item)
{
lock (_lock)
Items.Add(item);
}
public void Clear()
{
lock (_lock) Items.Clear();
}
public bool Contains(TOperation item)
{
lock (_lock) return Items.Contains(item);
}
public void CopyTo(TOperation[] array, int index)
{
lock (_lock) Items.CopyTo(array, index);
}
public bool Remove(TOperation item)
{
lock (_lock)
{
var index = InternalIndexOf(item);
if (index < 0)
return false;
RemoveItem(index);
return true;
}
}
IEnumerator IEnumerable.GetEnumerator() => ((IList)Items).GetEnumerator();
public IEnumerator GetEnumerator()
{
lock (_lock)
return Items.GetEnumerator();
}
int IList.Add(object value)
{
VerifyValueType(value);
lock (_lock)
{
Add((TOperation)value);
return Count - 1;
}
}
bool IList.Contains(object value)
{
VerifyValueType(value);
return Contains((TOperation)value);
}
int IList.IndexOf(object value)
{
VerifyValueType(value);
return IndexOf((TOperation)value);
}
void IList.Insert(int index, object value)
{
VerifyValueType(value);
Insert(index, (TOperation)value);
}
void IList.Remove(object value)
{
VerifyValueType(value);
Remove((TOperation)value);
}
public static implicit operator BulkOperationsCollection(List items) => new BulkOperationsCollection(items);
public int IndexOf(TOperation item)
{
lock (_lock) return InternalIndexOf(item);
}
public void Insert(int index, TOperation item)
{
lock (_lock)
{
if (index < 0 || index > Items.Count)
throw new ArgumentOutOfRangeException("index", index, $"value {index} must be in range of {Items.Count}");
InsertItem(index, item);
}
}
public void RemoveAt(int index)
{
lock (_lock)
{
if (index < 0 || index >= Items.Count)
throw new ArgumentOutOfRangeException("index", index, $"value {index} must be in range of {Items.Count}");
RemoveItem(index);
}
}
public void AddRange(IEnumerable items)
{
lock (_lock)
Items.AddRange(items);
}
private int InternalIndexOf(TOperation item)
{
var count = Items.Count;
for (var i = 0; i < count; i++)
{
if (Equals(Items[i], item)) return i;
}
return -1;
}
private void InsertItem(int index, TOperation item) => Items.Insert(index, item);
private void RemoveItem(int index) => Items.RemoveAt(index);
private static void VerifyValueType(object value)
{
if (value == null)
{
if (typeof(TOperation).IsValueType) throw new ArgumentException("value is null and a value type");
}
else if (!(value is TOperation))
throw new ArgumentException($"object is of type {value.GetType().FullName} but collection is of {typeof(TOperation).FullName}");
}
}
}