blob: 5a5b3550e1dec65d7a6c10a2bf6e1d2c56c3030e [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.
*/
package org.apache.phoenix.schema.tuple;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.phoenix.query.QueryConstants.ENCODED_CQ_COUNTER_INITIAL_VALUE;
import static org.apache.phoenix.query.QueryConstants.ENCODED_EMPTY_COLUMN_NAME;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import javax.annotation.concurrent.NotThreadSafe;
import org.apache.hadoop.hbase.Cell;
import org.apache.phoenix.schema.PTable.ImmutableStorageScheme;
import org.apache.phoenix.schema.PTable.QualifierEncodingScheme;
/**
* List implementation that provides indexed based look up when the cell column qualifiers are positive numbers.
* These qualifiers are generated by using one of the column qualifier encoding schemes specified in {@link ImmutableStorageScheme}.
* The api methods in this list assume that the caller wants to see
* and add only non null elements in the list.
* <p>
* Please note that this implementation doesn't implement all the optional methods of the
* {@link List} interface. Such unsupported methods could violate the basic invariance of the list that every cell with
* an encoded column qualifier has a fixed position in the list.
* </p>
* <p>
* An important performance characteristic of this list is that doing look up on the basis of index via {@link #get(int)}
* is an O(n) operation. This makes iterating through the list using {@link #get(int)} an O(n^2) operation.
* Instead, for iterating through the list, one should use the iterators created through {@link #iterator()} or
* {@link #listIterator()}. Do note that getting an element using {@link #getCellForColumnQualifier(int)} is an O(1) operation
* and should generally be the way for accessing elements in the list.
* </p>
*/
@NotThreadSafe
public class EncodedColumnQualiferCellsList implements List<Cell> {
private int minQualifier;
private int maxQualifier;
private int nonReservedRangeOffset;
private final Cell[] array;
private int numNonNullElements;
private int firstNonNullElementIdx = -1;
private static final int RESERVED_RANGE_SIZE = ENCODED_CQ_COUNTER_INITIAL_VALUE - ENCODED_EMPTY_COLUMN_NAME;
// Used by iterators to figure out if the list was structurally modified.
private int modCount = 0;
private final QualifierEncodingScheme encodingScheme;
public EncodedColumnQualiferCellsList(int minQ, int maxQ, QualifierEncodingScheme encodingScheme) {
checkArgument(minQ <= maxQ, "Invalid arguments. Min: " + minQ
+ ". Max: " + maxQ);
this.minQualifier = minQ;
this.maxQualifier = maxQ;
int size = 0;
if (maxQ < ENCODED_CQ_COUNTER_INITIAL_VALUE) {
size = RESERVED_RANGE_SIZE;
} else if (minQ < ENCODED_CQ_COUNTER_INITIAL_VALUE) {
size = (maxQ - minQ + 1);
} else {
size = RESERVED_RANGE_SIZE + (maxQ - minQ + 1);
}
this.array = new Cell[size];
this.nonReservedRangeOffset = minQ > ENCODED_CQ_COUNTER_INITIAL_VALUE ? minQ - ENCODED_CQ_COUNTER_INITIAL_VALUE : 0;
this.encodingScheme = encodingScheme;
}
@Override
public int size() {
return numNonNullElements;
}
@Override
public boolean isEmpty() {
return numNonNullElements == 0;
}
@Override
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
@Override
public Object[] toArray() {
Object[] toReturn = new Object[numNonNullElements];
int counter = 0;
if (numNonNullElements > 0) {
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
toReturn[counter++] = array[i];
}
}
}
return toReturn;
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
T[] toReturn =
(T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(),
numNonNullElements);
int counter = 0;
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
toReturn[counter++] = (T) array[i];
}
}
return toReturn;
}
@Override
public boolean add(Cell e) {
if (e == null) {
throw new NullPointerException();
}
int columnQualifier = encodingScheme.decode(e.getQualifierArray(), e.getQualifierOffset(), e.getQualifierLength());
checkQualifierRange(columnQualifier);
int idx = getArrayIndex(columnQualifier);
if (array[idx] == null) {
numNonNullElements++;
}
array[idx] = e;
if (firstNonNullElementIdx == -1) {
firstNonNullElementIdx = idx;
} else if (idx < firstNonNullElementIdx) {
firstNonNullElementIdx = idx;
}
modCount++;
/*
* Note that we don't care about equality of the element being added with the element
* already present at the index.
*/
return true;
}
@Override
public boolean remove(Object o) {
if (o == null) {
return false;
}
Cell e = (Cell) o;
int i = 0;
while (i < array.length) {
if (array[i] != null && array[i].equals(e)) {
array[i] = null;
numNonNullElements--;
if (numNonNullElements == 0) {
firstNonNullElementIdx = -1;
} else if (firstNonNullElementIdx == i) {
// the element being removed was the first non-null element we knew
while (i < array.length && (array[i]) == null) {
i++;
}
if (i < array.length) {
firstNonNullElementIdx = i;
} else {
firstNonNullElementIdx = -1;
}
}
modCount++;
return true;
}
i++;
}
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
boolean containsAll = true;
Iterator<?> itr = c.iterator();
while (itr.hasNext()) {
containsAll &= (indexOf(itr.next()) >= 0);
}
return containsAll;
}
@Override
public boolean addAll(Collection<? extends Cell> c) {
boolean changed = false;
for (Cell cell : c) {
if (c == null) {
throw new NullPointerException();
}
changed |= add(cell);
}
return changed;
}
@Override
public boolean addAll(int index, Collection<? extends Cell> c) {
throwGenericUnsupportedOperationException();
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
Iterator<?> itr = c.iterator();
boolean changed = false;
while (itr.hasNext()) {
changed |= remove(itr.next());
}
return changed;
}
@Override
public boolean retainAll(Collection<?> collection) {
boolean changed = false;
// Optimize if the passed collection is an instance of EncodedColumnQualiferCellsList
if (collection instanceof EncodedColumnQualiferCellsList) {
EncodedColumnQualiferCellsList list = (EncodedColumnQualiferCellsList) collection;
ListIterator<Cell> listItr = this.listIterator();
while (listItr.hasNext()) {
Cell cellInThis = listItr.next();
int qualifier = encodingScheme.decode(cellInThis.getQualifierArray(),
cellInThis.getQualifierOffset(), cellInThis.getQualifierLength());
try {
Cell cellInParam = list.getCellForColumnQualifier(qualifier);
if (cellInParam != null && cellInParam.equals(cellInThis)) {
continue;
}
listItr.remove();
changed = true;
} catch (IndexOutOfBoundsException expected) {
// this could happen when the qualifier of cellInParam lies out of
// the range of this list.
listItr.remove();
changed = true;
}
}
} else {
throw new UnsupportedOperationException(
"Operation only supported for collections of type EncodedColumnQualiferCellsList");
}
return changed;
}
@Override
public void clear() {
for (int i = 0; i < array.length; i++) {
array[i] = null;
}
firstNonNullElementIdx = -1;
numNonNullElements = 0;
modCount++;
}
@Override
public Cell get(int index) {
rangeCheck(index);
int numNonNullElementsFound = 0;
for (int i = firstNonNullElementIdx; i < array.length; i++) {
if (array[i] != null) {
numNonNullElementsFound++;
if (numNonNullElementsFound == index + 1) {
return array[i];
}
}
}
throw new IllegalStateException("There was no element present in the list at index "
+ index + " even though number of elements in the list are " + size());
}
@Override
public Cell set(int index, Cell e) {
throwGenericUnsupportedOperationException();
return null;
}
@Override
public void add(int index, Cell element) {
throwGenericUnsupportedOperationException();
}
@Override
public Cell remove(int index) {
throwGenericUnsupportedOperationException();
return null;
}
@Override
public int indexOf(Object o) {
if (o == null || isEmpty()) {
return -1;
} else {
int numNonNull = -1;
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
numNonNull++;
}
if (o.equals(array[i])) {
return numNonNull;
}
}
}
return -1;
}
@Override
public int lastIndexOf(Object o) {
if (o == null || isEmpty()) {
return -1;
}
int lastIndex = numNonNullElements;
for (int i = array.length - 1; i >= 0; i--) {
if (array[i] != null) {
lastIndex--;
}
if (o.equals(array[i])) {
return lastIndex;
}
}
return -1;
}
@Override
public ListIterator<Cell> listIterator() {
return new ListItr();
}
@Override
public ListIterator<Cell> listIterator(int index) {
throwGenericUnsupportedOperationException();
return null;
}
@Override
public List<Cell> subList(int fromIndex, int toIndex) {
throwGenericUnsupportedOperationException();
return null;
}
@Override
public Iterator<Cell> iterator() {
return new Itr();
}
public Cell getCellForColumnQualifier(byte[] qualifierBytes) {
int columnQualifier = encodingScheme.decode(qualifierBytes);
return getCellForColumnQualifier(columnQualifier);
}
public Cell getCellForColumnQualifier(byte[] qualifierBytes, int offset, int length) {
int columnQualifier = encodingScheme.decode(qualifierBytes, offset, length);
return getCellForColumnQualifier(columnQualifier);
}
private Cell getCellForColumnQualifier(int columnQualifier) {
checkQualifierRange(columnQualifier);
int idx = getArrayIndex(columnQualifier);
Cell c = array[idx];
return c;
}
public Cell getFirstCell() {
if (firstNonNullElementIdx == -1) {
throw new NoSuchElementException("No elements present in the list");
}
return array[firstNonNullElementIdx];
}
private void checkQualifierRange(int qualifier) {
if (qualifier < ENCODED_CQ_COUNTER_INITIAL_VALUE) {
return; // space in the array for reserved range is always allocated.
}
if (qualifier < minQualifier || qualifier > maxQualifier) {
throw new IndexOutOfBoundsException("Qualifier " + qualifier
+ " is out of the valid range - (" + minQualifier + ", " + maxQualifier + ")");
}
}
private void rangeCheck(int index) {
if (index < 0 || index >= size()) {
throw new IndexOutOfBoundsException();
}
}
private int getArrayIndex(int columnQualifier) {
checkArgument(columnQualifier >= ENCODED_EMPTY_COLUMN_NAME);
if (columnQualifier < ENCODED_CQ_COUNTER_INITIAL_VALUE) {
return columnQualifier;
}
return columnQualifier - nonReservedRangeOffset;
}
private void throwGenericUnsupportedOperationException() {
throw new UnsupportedOperationException(
"Operation cannot be supported because it potentially violates the invariance contract of this list implementation");
}
private class Itr implements Iterator<Cell> {
protected int nextIndex = 0;
protected int lastRet = -1;
protected int expectedModCount = modCount;
private Itr() {
moveForward(true);
}
@Override
public boolean hasNext() {
return nextIndex != -1;
}
@Override
public Cell next() {
checkForCoModification();
if (!hasNext()) {
throw new NoSuchElementException();
}
Cell next = array[nextIndex];
lastRet = nextIndex;
moveForward(false);
modCount++;
expectedModCount = modCount;
return next;
}
@Override
public void remove() {
if (lastRet < 0) {
throw new IllegalStateException();
}
checkForCoModification();
array[lastRet] = null;
lastRet = -1;
numNonNullElements--;
modCount++;
expectedModCount = modCount;
}
protected void moveForward(boolean init) {
int i = init ? 0 : nextIndex + 1;
while (i < array.length && (array[i]) == null) {
i++;
}
if (i < array.length) {
nextIndex = i;
} else {
nextIndex = -1;
}
}
protected void checkForCoModification() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
private class ListItr extends Itr implements ListIterator<Cell> {
private int previousIndex = -1;
private ListItr() {
moveForward(true);
}
@Override
public boolean hasNext() {
return nextIndex != -1;
}
@Override
public boolean hasPrevious() {
return previousIndex != -1;
}
@Override
public Cell previous() {
if (previousIndex == -1) {
throw new NoSuchElementException();
}
checkForCoModification();
lastRet = previousIndex;
movePointersBackward();
return array[lastRet];
}
@Override
public int nextIndex() {
return nextIndex;
}
@Override
public int previousIndex() {
return previousIndex;
}
@Override
public void remove() {
if (lastRet == nextIndex) {
moveNextPointer(nextIndex);
}
super.remove();
expectedModCount = modCount;
}
@Override
public void set(Cell e) {
if (lastRet == -1) {
throw new IllegalStateException();
}
int columnQualifier = encodingScheme.decode(e.getQualifierArray(), e.getQualifierOffset(), e.getQualifierLength());
int idx = getArrayIndex(columnQualifier);
if (idx != lastRet) {
throw new IllegalArgumentException("Cell " + e + " with column qualifier "
+ columnQualifier + " belongs at index " + idx
+ ". It cannot be added at the position " + lastRet
+ " to which the previous next() or previous() was pointing to.");
}
EncodedColumnQualiferCellsList.this.add(e);
expectedModCount = modCount;
}
@Override
public void add(Cell e) {
throwGenericUnsupportedOperationException();
}
@Override
protected void moveForward(boolean init) {
if (!init) {
previousIndex = nextIndex;
}
int i = init ? 0 : nextIndex + 1;
moveNextPointer(i);
}
private void moveNextPointer(int i) {
while (i < array.length && (array[i]) == null) {
i++;
}
if (i < array.length) {
nextIndex = i;
} else {
nextIndex = -1;
}
}
private void movePointersBackward() {
nextIndex = previousIndex;
int i = previousIndex - 1;
movePreviousPointer(i);
}
private void movePreviousPointer(int i) {
for (; i >= 0; i--) {
if (array[i] != null) {
previousIndex = i;
break;
}
}
if (i < 0) {
previousIndex = -1;
}
}
}
}