/* * 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. */ package org.apache.geode.cache.query.internal.index; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; /** * A wrapper around an object array for storing values in index data structure with minimal set of * operations supported and the maximum size of 128 elements * * @since GemFire 7.0 */ public class IndexElemArray implements Iterable, Collection { private Object[] elementData; private volatile byte size; /* lock for making size and data changes atomically. */ private Object lock = new Object(); public IndexElemArray(int initialCapacity) { if (initialCapacity < 0) { throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } this.elementData = new Object[initialCapacity]; } /** * Constructs an empty list with an initial capacity of ten. */ public IndexElemArray() { this(IndexManager.INDEX_ELEMARRAY_SIZE); } /** * Increases the capacity of this <tt>ArrayList</tt> instance, if necessary, to ensure that it can * hold at least the number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void ensureCapacity(int minCapacity) { int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { int newCapacity = oldCapacity + 5; if (newCapacity < minCapacity) { newCapacity = minCapacity; } // minCapacity is usually close to size, so this is a win: Object[] newElementData = new Object[newCapacity]; System.arraycopy(this.elementData, 0, newElementData, 0, this.elementData.length); elementData = newElementData; } } /** * Returns the number of elements in this list. (Warning: May not return correct size always, as * remove operation is not atomic) * * @return the number of elements in this list */ public int size() { return size; } /** * Returns <tt>true</tt> if this list contains no elements. * * @return <tt>true</tt> if this list contains no elements */ public boolean isEmpty() { return size == 0; } /** * Returns <tt>true</tt> if this list contains the specified element. More formally, returns * <tt>true</tt> if and only if this list contains at least one element <tt>e</tt> such that * <tt>(o==null ? e==null : o.equals(e))</tt>. * * @param o element whose presence in this list is to be tested * @return <tt>true</tt> if this list contains the specified element */ public boolean contains(Object o) { return indexOf(o) >= 0; } /** * Returns the index of the first occurrence of the specified element in this list, or -1 if this * list does not contain the element. More formally, returns the lowest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, or -1 if there is no * such index. */ public int indexOf(Object o) { synchronized (lock) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i] == null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } } return -1; } /** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException * */ public Object get(int index) { synchronized (lock) { RangeCheck(index); return elementData[index]; } } /** * Replaces the element at the specified position in this list with the specified element. * * @param index index of the element to replace * @param element element to be stored at the specified position * @return the element previously at the specified position * @throws IndexOutOfBoundsException * */ public Object set(int index, Object element) { synchronized (lock) { RangeCheck(index); Object oldValue = (Object) elementData[index]; elementData[index] = element; return oldValue; } } /** * Appends the specified element to the end of this array. If the array is full, creates a new * array with new capacity = old capacity + 5 * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) * @throws ArrayIndexOutOfBoundsException */ public boolean add(Object e) { synchronized (lock) { ensureCapacity(size + 1); elementData[size] = e; ++size; } return true; } /** * Removes the first occurrence of the specified element from this list, if it is present. If the * list does not contain the element, it is unchanged. More formally, removes the element with the * lowest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> (if such an element * exists). Returns <tt>true</tt> if this list contained the specified element (or equivalently, * if this list changed as a result of the call). * * @param o element to be removed from this list, if present * @return <tt>true</tt> if this list contained the specified element */ public boolean remove(Object o) { synchronized (lock) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } } return false; } /* * Private remove method that skips bounds checking and does not return the value removed. */ private void fastRemove(int index) { int len = elementData.length; Object[] newArray = new Object[len - 1]; System.arraycopy(elementData, 0, newArray, 0, index); int numMoved = len - index - 1; if (numMoved > 0) { System.arraycopy(elementData, index + 1, newArray, index, numMoved); } elementData = newArray; --size; } /** * Removes all of the elements from this list. The list will be empty after this call returns. */ public void clear() { // Let gc do its work synchronized (lock) { for (int i = 0; i < size; i++) { elementData[i] = null; } size = 0; } } /** * Checks if the given index is in range. If not, throws an appropriate runtime exception. This * method does *not* check if the index is negative: It is always used immediately prior to an * array access, which throws an ArrayIndexOutOfBoundsException if index is negative. */ private void RangeCheck(int index) { if (index >= size) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } } @Override public boolean addAll(Collection c) { Object[] a = c.toArray(); int numNew = a.length; synchronized (lock) { ensureCapacity(size + numNew); System.arraycopy(a, 0, elementData, size, numNew); size += numNew; } return numNew != 0; } @Override public Object[] toArray() { return Arrays.copyOf(elementData, size); } @Override public Iterator iterator() { return new IndexArrayListIterator(); } private class IndexArrayListIterator implements Iterator { private byte current; private Object currentEntry; private Object[] elements; private int len; IndexArrayListIterator() { synchronized (lock) { elements = elementData; len = size; } } /** * Checks if the array has next element, stores reference to the current element and increments * cursor. This is required since an element may be removed between hasNext() and next() method * calls * */ @Override public boolean hasNext() { return current < len; } /** * Returns next element. But does not increment the cursor. Always use hasNext() before this * method call */ @Override public Object next() { try { currentEntry = elements[current++]; } catch (IndexOutOfBoundsException e) { // We should not be coming here as element-data and // size are updated atomically. throw new NoSuchElementException(); // return null; } return currentEntry; } @Override public void remove() { throw new UnsupportedOperationException("remove() method is not supported"); } } @Override public Object[] toArray(Object[] a) { throw new UnsupportedOperationException("toArray(Object[] a) method is not supported"); } @Override public boolean containsAll(Collection c) { throw new UnsupportedOperationException("containsAll() method is not supported"); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException("removeAll() method is not supported"); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException("retainAll() method is not supported"); } // for internal testing only public Object[] getElementData() { return elementData; } }