/******************************************************************************* * Copyright (c) 2009 Centrum Wiskunde en Informatica (CWI) * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Arnold Lankamp - interfaces and implementation *******************************************************************************/ package org.rascalmpl.value.util; import java.util.Iterator; import java.util.NoSuchElementException; /** * This list implementation is shareable and guarantees that the following operations can be done * in constant time: * -insert (front) * -append (back) * -get (ordered) * -get (random) * -set (random) * -remove (front) * -remove (back) * -reverse * Additionally both cloning this list as the sublist operation are relatively cheap, * (two simple arraycopies). * * @author Arnold Lankamp * * @param <E> * The element type. */ public class ShareableList<E> implements Iterable<E>{ private final static int INITIAL_LOG_SIZE = 2; private int frontCapacity; private E[] frontData; private int frontIndex; private int backCapacity; private E[] backData; private int backIndex; /** * Default constructor */ public ShareableList(){ super(); frontCapacity = 1 << INITIAL_LOG_SIZE; frontData = (E[]) new Object[frontCapacity]; frontIndex = 0; backCapacity = 1 << INITIAL_LOG_SIZE; backData = (E[]) new Object[backCapacity]; backIndex = 0; } /** * Copy constructor. * * @param shareableList * The list to copy. */ public ShareableList(ShareableList<E> shareableList){ super(); frontCapacity = shareableList.frontCapacity; frontData = shareableList.frontData.clone(); frontIndex = shareableList.frontIndex; backCapacity = shareableList.backCapacity; backData = shareableList.backData.clone(); backIndex = shareableList.backIndex; } /** * SubList copy constructor. * * @param shareableList * The list to copy. * @param offset * The start index of the sublist. * @param length * The length of the sublist. */ protected ShareableList(ShareableList<E> shareableList, int offset, int length){ super(); int backStartIndex = shareableList.backIndex - offset; if(backStartIndex <= 0){// Front only backIndex = 0; backCapacity = 2; backData = (E[]) new Object[backCapacity]; int frontStartIndex = -backStartIndex; frontIndex = length; frontCapacity = closestPowerOfTwo(length); frontData = (E[]) new Object[frontCapacity]; System.arraycopy(shareableList.frontData, frontStartIndex, frontData, 0, length); }else{ if((offset + length) <= shareableList.backIndex){ // Back only backIndex = length; backCapacity = closestPowerOfTwo(length); backData = (E[]) new Object[backCapacity]; System.arraycopy(shareableList.backData, backStartIndex - length, backData, 0, length); frontIndex = 0; frontCapacity = 2; frontData = (E[]) new Object[frontCapacity]; }else{ // Front and Back overlap backIndex = backStartIndex; backCapacity = closestPowerOfTwo(backStartIndex); backData = (E[]) new Object[backCapacity]; System.arraycopy(shareableList.backData, 0, backData, 0, backStartIndex); int frontLength = length - backStartIndex; frontIndex = frontLength; frontCapacity = closestPowerOfTwo(frontLength); frontData = (E[]) new Object[frontCapacity]; System.arraycopy(shareableList.frontData, 0, frontData, 0, frontLength); } } } private static int closestPowerOfTwo(int v){ // https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 if(v <= 2){ return 2; } v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return v + 1; } /** * Removes all the elements from this list. */ public void clear(){ frontCapacity = 1 << INITIAL_LOG_SIZE; frontData = (E[]) new Object[frontCapacity]; frontIndex = 0; backCapacity = 1 << INITIAL_LOG_SIZE; backData = (E[]) new Object[backCapacity]; backIndex = 0; } /** * Calling this will guarantee that there is enough space for the next single element insert. */ private void ensureFrontCapacity(){ if(frontCapacity == frontIndex){ frontCapacity <<= 1; E[] newFrontData = (E[]) new Object[frontCapacity]; System.arraycopy(frontData, 0, newFrontData, 0, frontData.length); frontData = newFrontData; } } /** * Calling this will guarantee that there is enough space for the next single element append. */ private void ensureBackCapacity(){ if(backCapacity == backIndex){ backCapacity <<= 1; E[] newBackData = (E[]) new Object[backCapacity]; System.arraycopy(backData, 0, newBackData, 0, backData.length); backData = newBackData; } } /** * Calling this will guarantee that there is enough space for the next batch of inserted elements. * * @param nrOfElements * The amount of free space that is required. */ private void ensureFrontBulkCapacity(int nrOfElements){ int requiredCapacity = frontIndex + nrOfElements; if(frontCapacity <= requiredCapacity){ do{ frontCapacity <<= 1; }while(frontCapacity <= requiredCapacity); E[] newFrontData = (E[]) new Object[frontCapacity]; System.arraycopy(frontData, 0, newFrontData, 0, frontData.length); frontData = newFrontData; } } /** * Calling this will guarantee that there is enough space for the next batch of appended elements. * * @param nrOfElements * The amount of free space that is required. */ private void ensureBackBulkCapacity(int nrOfElements){ int requiredCapacity = backIndex + nrOfElements; if(backCapacity <= requiredCapacity){ do{ backCapacity <<= 1; }while(backCapacity <= requiredCapacity); E[] newBackData = (E[]) new Object[backCapacity]; System.arraycopy(backData, 0, newBackData, 0, backData.length); backData = newBackData; } } /** * Appends the given element to the end of this list. * * @param element * The element to append. */ public void append(E element){ ensureFrontCapacity(); frontData[frontIndex++] = element; } /** * Appends the given batch of element to the end of this list. * * @param elements * The batch of elements to append. */ public void appendAll(E[] elements){ int nrOfElements = elements.length; ensureFrontBulkCapacity(nrOfElements); System.arraycopy(elements, 0, frontData, frontIndex, nrOfElements); frontIndex += nrOfElements; } /** * Appends the indicated range of elements from the given batch of elements to the end of this * list. * * @param elements * The batch that contains the elements to append. * @param offset * The position in the given batch to start at. * @param length * The number of elements from the given batch to append. */ public void appendAllAt(E[] elements, int offset, int length){ if(offset < 0) throw new IllegalArgumentException("Offset must be > 0."); if((offset + length) > elements.length) throw new IllegalArgumentException("(offset + length) must be <= elements.length."); ensureFrontBulkCapacity(length); System.arraycopy(elements, offset, frontData, frontIndex, length); frontIndex += length; } /** * Inserts the given element to the start of this list. * * @param element * The element to insert. */ public void insert(E element){ ensureBackCapacity(); backData[backIndex++] = element; } /** * Inserts the given batch of elements to the start of this list. * * @param elements * The batch of elements to insert. */ public void insertAll(E[] elements){ int nrOfElements = elements.length; ensureBackBulkCapacity(nrOfElements); for(int i = nrOfElements - 1; i >= 0; i--){ backData[backIndex++] = elements[i]; } } /** * Inserts the given element at the indicated position in this list. * * @param index * The index to insert the element at. * @param element * The element to insert. */ public void insertAt(int index, E element){ int realIndex = index - backIndex; if(realIndex >= 0){ ensureFrontCapacity(); if(realIndex > frontIndex) throw new ArrayIndexOutOfBoundsException(index+" > the the current size of the list ("+size()+")"); int elementsToMove = frontIndex - realIndex; System.arraycopy(frontData, realIndex, frontData, realIndex + 1, elementsToMove); frontData[realIndex] = element; frontIndex++; }else{ ensureBackCapacity(); realIndex = 0 - realIndex; if(realIndex > backIndex) throw new ArrayIndexOutOfBoundsException(index+" < 0"); int elementsToMove = backIndex - realIndex; System.arraycopy(backData, realIndex, backData, realIndex + 1, elementsToMove); backData[realIndex] = element; backIndex++; } } /** * Replaces the element at the indicated position in this list by the given element. * * @param index * The index to replace the element at. * @param element * The element to place at the indicated position. * @return The element that was located at the indicated position prior to the execution of this * operation. */ public E set(int index, E element){ int realIndex = index - backIndex; if(realIndex >= 0){ if(realIndex >= frontIndex) throw new ArrayIndexOutOfBoundsException(index+" >= the current size of the list ("+size()+")"); E oldElement = frontData[realIndex]; frontData[realIndex] = element; return oldElement; } realIndex = -1 - realIndex; if(realIndex >= backIndex) throw new ArrayIndexOutOfBoundsException(index+" < 0"); E oldElement = backData[realIndex]; backData[realIndex] = element; return oldElement; } /** * Retrieves the element at the indicated position from this list. * * @param index * The position to retrieve the element from. * @return The retrieved element. */ public E get(int index){ int realIndex = index - backIndex; if(realIndex >= 0){ if(realIndex >= frontIndex) { throw new ArrayIndexOutOfBoundsException(index+" >= the current size of the list ("+size()+")"); } return frontData[realIndex]; } realIndex = -1 - realIndex; if(realIndex >= backIndex) throw new ArrayIndexOutOfBoundsException(index+" < 0"); return backData[realIndex]; } /** * Removes the element at the indicated position from this list. * * @param index * The position to remove the element from. * @return The element that was located at the indicated position prior to the execution of * this operation. */ public E remove(int index){ int realIndex = index - backIndex; if(realIndex >= 0){ if(realIndex >= frontIndex) throw new ArrayIndexOutOfBoundsException(index+" >= the current size of the list ("+size()+")"); E oldElement = frontData[realIndex]; int elementsToMove = --frontIndex - realIndex; if(elementsToMove > 0) System.arraycopy(frontData, realIndex + 1, frontData, realIndex, elementsToMove); frontData[frontIndex] = null; // Remove the 'old' reference at the end of the array. return oldElement; } realIndex = -1 - realIndex; if(realIndex >= backIndex) throw new ArrayIndexOutOfBoundsException(index+" < 0"); E oldElement = backData[realIndex]; int elementsToMove = --backIndex - realIndex; if(elementsToMove > 0) System.arraycopy(backData, realIndex + 1, backData, realIndex, elementsToMove); backData[backIndex] = null; // Remove the 'old' reference at the end of the array. return oldElement; } /** * Constructs a sublist of this list. * * @param offset * The offset to start at. * @param length * The number of elements. * @return The constructed sublist. */ public ShareableList<E> subList(int offset, int length){ if(offset < 0) throw new IndexOutOfBoundsException("Offset may not be smaller then 0."); if(length < 0) throw new IndexOutOfBoundsException("Length may not be smaller then 0."); if((offset + length) > size()) throw new IndexOutOfBoundsException("'offset + length' may not be larger then 'list.size()'"); return new ShareableList<>(this, offset, length); } /** * Reverses the order of the elements in this list. * * @return A reference to this list. */ public ShareableList<E> reverse(){ int tempCapacity = frontCapacity; E[] tempData = frontData; int tempIndex = frontIndex; frontCapacity = backCapacity; frontData = backData; frontIndex = backIndex; backCapacity = tempCapacity; backData = tempData; backIndex = tempIndex; return this; } /** * Returns the number of elements that are currently present in this list. * * @return The number of elements that are currently present in this list. */ public int size(){ return (frontIndex + backIndex); } /** * Checks whether or not this list is empty. * * @return True if this list is empty; false otherwise. */ public boolean isEmpty(){ return (size() == 0); } /** * Constructs an iterator for this list. * * @return An iterator for this list. * * @see java.lang.Iterable#iterator() */ public Iterator<E> iterator(){ return new ListIterator<>(this); } /** * Computes the current hash code of this list. * * @return The current hash code of this list. * * @see java.lang.Object#hashCode() */ public int hashCode(){ int hash = 0; Iterator<E> iterator = iterator(); while(iterator.hasNext()){ E element = iterator.next(); hash = (hash << 1) ^ element.hashCode(); } return hash; } /** * Check whether or not the current content of this list is equal to that of the given object / list. * * @return True if the content of this list is equal to the given object / list. * * @see java.lang.Object#equals(Object) */ public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ ShareableList<?> other = (ShareableList<?>) o; if(other.size() == size()){ if(isEmpty()) return true; // No need to check if the lists are empty. Iterator<E> thisIterator = iterator(); Iterator<?> otherIterator = other.iterator(); while(thisIterator.hasNext()){ if(!thisIterator.next().equals(otherIterator.next())) return false; } return true; } } return false; } /** * Prints the internal representation of this list to a string. * * @see java.lang.Object#toString() */ public String toString(){ StringBuilder buffer = new StringBuilder(); buffer.append('['); Iterator<E> iterator = iterator(); if(iterator.hasNext()){ E element = iterator.next(); buffer.append(element); while(iterator.hasNext()){ element = iterator.next(); buffer.append(','); buffer.append(element); } } buffer.append(']'); return buffer.toString(); } /** * Iterator for this list. * * @author Arnold Lankamp * * @param <E> * The element type. */ private static class ListIterator<E> implements Iterator<E>{ private final ShareableList<E> shareableList; private int currentIndex; private boolean front; /** * Constructor. * * @param shareableList * The list to iterator over. */ public ListIterator(ShareableList<E> shareableList){ super(); this.shareableList = shareableList; currentIndex = shareableList.backIndex - 1; front = false; if(currentIndex < 0){ currentIndex = 0; front = true; } } /** * Check whether or not there are more elements in this iteration. * * @return True if there are more element in this iteration. * * @see java.util.Iterator#hasNext() */ public boolean hasNext(){ return front ? (currentIndex < shareableList.frontIndex) : (currentIndex >= 0); } /** * Returns the next element in this iteration. * * @return The next element in this iteration. * @throws java.util.NoSuchElementException * Thrown when there are no more elements in this iteration when calling this * method. * * @see java.util.Iterator#next() */ public E next(){ if(!hasNext()) throw new NoSuchElementException("There are no more elements in this iteration."); E element; if(front){ element = shareableList.frontData[currentIndex++]; }else{ element = shareableList.backData[currentIndex--]; if(currentIndex == -1){ front = true; currentIndex = 0; } } return element; } /** * This iterator does not support removal. * * @throws java.lang.UnsupportedOperationException * * @see java.util.Iterator#remove() */ public void remove(){ throw new UnsupportedOperationException("This iterator doesn't support removal."); } } }