/* 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}"); } } }