/**
*
*/
package net.varkhan.base.containers.list;
import net.varkhan.base.containers.Index;
import net.varkhan.base.containers.Indexed;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Field;
import java.security.PrivilegedActionException;
import java.util.NoSuchElementException;
/**
* <b>Abstract IndexedList using sparse segments</b>.
* <p/>
* This abstract list framework is used as a common base for type-specific
* concrete implementations, that store objects in arbitrary positions (their
* index, in the sense of {@link Indexed}).
* <p/>
* The entries are kept in a resizable array of storage segments. Each segment
* maintains occupancy statistics in a bit array, and a compact array of actual
* elements.
* <p/>
* The segment structure bypasses the normal size limits implied by the {@code int}
* subscript, and limits reallocation costs.
* <p/>
*
* @author varkhan
* @date Mar 12, 2009
* @time 6:16:09 AM
*/
abstract class AbstractSparseIndexedList {
/**
* The segment size 2-logarithm
*/
protected final int blockshift;
/**
* The segment size
*/
protected final int blocksize;
/**
* The segment subscript mask ({@code blocksize - 1})
*/
protected final int blockmask;
/**
* The segment header size ({@code blocksize/8})
*/
protected final int blockhead;
/**
* The list growth factor > 1.0
*/
protected final double growthfact;
/**
* The number of elements contained in the list
*/
protected long size=0;
/**
* The index of the last slot used + 1
*/
protected long head=0;
/**
* The segment occupancy storage
*/
protected byte[][] bits=null;
/**********************************************************************************
** List constructors
**/
/**
* Creates a new SparseIndexedList, specifying the reallocation strategy.
*
* @param blockshift the node reference storage block size 2-logarithm
* @param growthfact the node reference storage growth factor
*/
public AbstractSparseIndexedList(int blockshift, double growthfact) {
if(blockshift<=3) blockshift=3;
if(growthfact<=1) growthfact=1.5;
this.growthfact=growthfact;
this.blockshift=blockshift;
this.blocksize=(1<<blockshift);
this.blockmask=(1<<blockshift)-1;
this.blockhead=1<<(blockshift-3);
clear();
}
/**********************************************************************************
** List statistics accessors
**/
/**
* Returns the number of elements in this list.
*
* @return the number of entries (elements and related indexes) stored in this list
*/
public final long size() {
return size;
}
/**
* Indicates whether this list is empty.
*
* @return {@literal true} if this list contains no entry,
* {@literal false} otherwise
*/
public final boolean isEmpty() {
return size<=0;
}
/**
* Returns the smallest position higher that any valid index in this list.
*
* @return the highest valid index plus one
*/
public final long head() {
return head;
}
/**
* Returns an index that has no associated element in this list.
*
* @return the smallest unused index in this list
*/
public final long free() {
if(bits==null) return 0;
if(head==size) return head;
int blockpos=0;
int blockmax=(int) (head>>>blockshift);
while(blockpos<blockmax) {
byte[] mask=bits[blockpos];
if(mask==null) return blockpos<<blockshift;
int pos=getSparseTail(mask);
if(pos<blocksize) return (blockpos<<blockshift)+pos;
blockpos++;
}
return head;
}
/**
* Deletes all elements from this list.
*/
public void clear() {
size=0;
head=0;
bits=null;
}
/**********************************************************************************
** List entries accessors
**/
/**
* Indicates whether an index has an associated entry.
*
* @param index a unique identifier for this entry
*
* @return {@literal true} if an element is associated with this index,
* or {@literal false} if no element is associated with this index
*/
public final boolean has(long index) {
if(index<0||index>=head) return false;
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) return false;
int pos=getSparsePos(mask, (int) (index&blockmask));
return pos>0;
}
/**********************
** Bit mask operators
**/
/**
* All the bit numbers for bytes
*/
private static final byte[] bitNum=new byte[256];
static {
for(int i=0;i<256;i++) {
int b=i;
byte c=0;
while(b!=0) {
c+=b&0x1;
b>>>=1;
}
bitNum[i]=c;
}
}
/**
* All the top set bit positions for bytes
*/
private static final byte[] bitHead=new byte[256];
static {
for(int i=0;i<256;i++) {
int b=i;
byte c=0;
while(b!=0) {
c++;
b>>>=1;
}
bitHead[i]=c;
}
}
/**
* All the bottom unset bit positions for bytes
*/
private static final byte[] bitTail=new byte[256];
static {
for(int i=0;i<256;i++) {
int b=i;
byte c=0;
while((b&0x1)!=0) {
c++;
b>>>=1;
}
bitTail[i]=c;
}
}
/**
* Indicates whether all bytes of a mask are zero.
*
* @param mask the mask
*
* @return {@literal true} if all bytes of {@code mask} are 0
*/
protected static boolean isEmpty(byte[] mask) {
if(mask==null) return true;
for(byte b : mask) if(b!=0) return false;
return true;
}
/**
* Gets the number of set bits in a mask.
*
* @param mask the mask
*
* @return the total number of bits set to 1 in the mask bytes
*/
protected static int getSparseLength(byte[] mask) {
if(mask==null) return 0;
int len=0;
for(byte b : mask) len+=0xFF&bitNum[b&0xFF];
return len;
}
/**
* The number of set bits in a starting segment of a mask.
*
* @param mask the mask
* @param off the end position of the segment
*
* @return the number of bits set to 1 between the bit {@code 0} and the bit {@code off} i the mask,
* as a positive number if the bit {@code off} is set to 1, or as a negative number if it is 0
*/
protected static int getSparsePos(byte[] mask, int off) {
if(mask==null) return 0;
int bitpos=off>>>3;
int objpos=0;
for(int i=0;i<bitpos;i++) {
objpos+=0xFF&bitNum[mask[i]&0xFF];
}
int bitoff=off&0x7;
int bitmsk=1<<bitoff;
byte b=mask[bitpos];
objpos+=0xFF&bitNum[b&(bitmsk-1)];
if((b&bitmsk)==0) return -objpos;
return 1+objpos;
}
/**
* The length of the smallest segment containing all set bits in a mask.
*
* @param mask the mask
*
* @return the number of bits between the start and the highest set bit of a mask
* (or 0 if no bit is set in the mask)
*/
protected static int getSparseHead(byte[] mask) {
if(mask==null) return 0;
int bitpos=mask.length;
while(bitpos>0) {
bitpos--;
if(mask[bitpos]!=0) break;
}
return (bitpos<<3)+(0xFF&bitHead[mask[bitpos]&0xFF]);
}
/**
* The length of the biggest segment whose bits are all set in a mask.
*
* @param mask the mask
*
* @return the number of bits between the start and the highest set bit of a mask
* (or 0 if no bit is set in the mask)
*/
protected static int getSparseTail(byte[] mask) {
if(mask==null) return 0;
int bitpos=0;
while(bitpos<mask.length) {
if(mask[bitpos]!=0xFF) break;
bitpos++;
}
return (bitpos<<3)+(0xFF&bitTail[mask[bitpos]&0xFF]);
}
/**
* Sets a bit in a mask to 1.
*
* @param mask the mask
* @param off the offset of the bit in the mask
*/
protected static void setBitOn(byte[] mask, int off) {
int bitpos=off>>>3;
int bitoff=off&0x7;
mask[bitpos]|=1<<bitoff;
}
/**
* Unsets a bit in a mask to 0.
*
* @param mask the mask
* @param off the offset of the bit in the mask
*/
protected static void setBitOff(byte[] mask, int off) {
int bitpos=off>>>3;
int bitoff=off&0x7;
mask[bitpos]&=~(1<<bitoff);
}
/**********************************************************************************
** List entries iterators
**/
/**
* Iterates over all indexes in the list, using an {@link Index}.
*
* @return an iterator over all the indexes that designate elements in the list
*/
public final Index indexes() {
return new Index() {
private long index=-1;
public long current() {
return index;
}
public boolean hasNext() {
long i=index+1;
while(i<head) {
int blockpos=(int) (i>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
i=(blockpos+1)<<blockshift;
continue;
}
int pos=getSparsePos(mask, (int) (i&blockmask));
if(pos>0) return true;
i++;
}
return false;
}
public long next() {
index++;
while(index<head) {
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
index=(blockpos+1)<<blockshift;
continue;
}
int pos=getSparsePos(mask, (int) (index&blockmask));
if(pos>0) return index;
index++;
}
throw new NoSuchElementException("No element at index "+index);
}
public boolean hasPrevious() {
long i=index-1;
while(i>0) {
int blockpos=(int) (i>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
i=(blockpos<<blockshift)-1;
continue;
}
int pos=getSparsePos(mask, (int) (i&blockmask));
if(pos>0) return true;
i--;
}
return false;
}
public long previous() {
index--;
while(index>0) {
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
index=(blockpos<<blockshift)-1;
continue;
}
int pos=getSparsePos(mask, (int) (index&blockmask));
if(pos>0) return index;
index--;
}
throw new NoSuchElementException("No element at index "+index);
}
};
}
/**
* Iterates over all indexes in the list.
*
* @return an iterable over all the indexes associated to elements in the list
*/
public final java.lang.Iterable<Long> iterateIndexes() {
return new java.lang.Iterable<Long>() {
public java.util.Iterator<Long> iterator() {
return new java.util.Iterator<Long>() {
private long index=-1;
public boolean hasNext() {
long i=index+1;
while(i<head) {
int blockpos=(int) (i>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
i=(blockpos+1)<<blockshift;
continue;
}
int pos=getSparsePos(mask, (int) (i&blockmask));
if(pos>0) return true;
i++;
}
return false;
}
public Long next() {
index++;
while(index<head) {
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
index=(blockpos+1)<<blockshift;
continue;
}
int pos=getSparsePos(mask, (int) (index&blockmask));
if(pos>0) return index;
index++;
}
throw new NoSuchElementException("No element at index "+index);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
/**********************************************************************************
** Externalization
**/
/**
* Write a SparseIndexedList to a stream.
*
* @param out the stream to write the object to
*
* @throws IOException if I/O errors occur
* @serialData <li/> {@code Object defVal} - the default value
* <li/> {@code byte blockshift} - the block size 2-logarithm
* <li/> {@code double growthfact} - the buffer growth factor
* <li/> {@code long size} - the number of set entries
* <li/> {@code long head} - the highest allocated index + 1
* <li/> all the blocks, as a bit mask of (blocksize/8) {@code byte}s,
* followed by the set values (a variable number of {@code float}s, as defined by the mask)
*/
public void writeExternal(ObjectOutput out) throws IOException {
out.writeByte(blockshift);
out.writeDouble(growthfact);
out.writeLong(size);
out.writeLong(head);
}
/**
* Read a SparseIndexedList from a stream.
*
* @param in the stream to read the object from
*
* @throws IOException if I/O errors occur
*/
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int block_shift=in.readByte();
double growth_fact=in.readDouble();
if(block_shift<=3) block_shift=3;
if(growth_fact<=1) growth_fact=1.5;
final Class<?> klass=AbstractSparseIndexedList.class;
final Object target=this;
// Set block* fields, bypassing final protection
try {
final Field field_BS=klass.getDeclaredField("blockshift");
final int value_BS=block_shift;
final Field field_BZ=klass.getDeclaredField("blocksize");
final int value_BZ=1<<block_shift;
final Field field_BM=klass.getDeclaredField("blockmask");
final int value_BM=(1<<block_shift)-1;
final Field field_BH=klass.getDeclaredField("blockhead");
final int value_BH=1<<(block_shift-3);
final Field field_GF=klass.getDeclaredField("growthfact");
final double value_GF=growth_fact;
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<Object>() {
public Object run() throws IllegalAccessException {
field_BS.setAccessible(true);
field_BS.setInt(target, value_BS);
field_BS.setAccessible(false);
field_BZ.setAccessible(true);
field_BZ.setInt(target, value_BZ);
field_BZ.setAccessible(false);
field_BM.setAccessible(true);
field_BM.setInt(target, value_BM);
field_BM.setAccessible(false);
field_BH.setAccessible(true);
field_BH.setInt(target, value_BH);
field_BH.setAccessible(false);
field_GF.setAccessible(true);
field_GF.setDouble(target, value_GF);
field_GF.setAccessible(false);
return null;
}
}
);
}
catch(NoSuchFieldException e) {
// This can never happen
throw new InvalidClassException(klass.getSimpleName(), "Invalid class structure, field "+e.getMessage()+" not found");
}
catch(PrivilegedActionException e) {
InvalidClassException t=new InvalidClassException(
SparseIndexedList.class.getSimpleName(),
"Invalid class structure"
);
t.initCause(e.getCause());
throw t;
}
catch(SecurityException e) {
InvalidClassException t=new InvalidClassException(
SparseIndexedList.class.getSimpleName(),
"Invalid class structure"
);
t.initCause(e.getCause());
throw t;
}
/**
* This is equivalent to:
*
blockshift = in.readByte();
blocksize = 1 << blockshift;
blockmask = (1 << blockshift) - 1;
blockhead = 1 << (blockshift-3);
* but bypasses the final keyword
*/
size=in.readLong();
head=in.readLong();
}
public int hashCode() {
int hash=blockshift;
hash^=size^(size>>>32);
hash^=head^(head>>>32);
return hash;
}
protected AbstractSparseIndexedList clone() throws CloneNotSupportedException {
AbstractSparseIndexedList clone=(AbstractSparseIndexedList) super.clone();
if(this.bits!=null) {
clone.bits=new byte[this.bits.length][];
for(int i=0;i<this.bits.length;i++) {
byte[] mask=this.bits[i];
if(mask!=null) {
byte[] m=new byte[mask.length];
System.arraycopy(mask, 0, m, 0, mask.length);
clone.bits[i]=m;
}
}
}
return clone;
}
}