/**
* Copyright 2005 JBoss Inc
*
* Licensed 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.drools.core.util;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
/**
*
* @author Mark Proctor
*/
public class PrimitiveLongMap
implements
Externalizable {
/**
*
*/
private static final long serialVersionUID = 510l;
private final static Object NULL = new Serializable() {
/**
*
*/
private static final long serialVersionUID = 510l;
};
private int indexIntervals;
private int intervalShifts;
private int midIntervalPoint;
private int tableSize;
private int shifts;
private int doubleShifts;
private Page firstPage;
private Page lastPage;
private int lastPageId;
private long maxKey;
private Page[] pageIndex;
private int totalSize;
public PrimitiveLongMap() {
this( 32,
8 );
}
public PrimitiveLongMap(final int tableSize) {
this( tableSize,
8 );
}
public PrimitiveLongMap(final int tableSize,
final int indexIntervals) {
// determine number of shifts for intervals
int i = 1;
int size = 2;
while ( size < indexIntervals ) {
size <<= 1;
++i;
}
this.indexIntervals = size;
this.intervalShifts = i;
// determine number of shifts for tableSize
i = 1;
size = 2;
while ( size < tableSize ) {
size <<= 1;
++i;
}
this.tableSize = size;
this.shifts = i;
this.doubleShifts = this.shifts << 1;
// determine mid point of an interval
this.midIntervalPoint = ((this.tableSize << this.shifts) << this.intervalShifts) >> 1;
this.lastPageId = 0;
init();
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
indexIntervals = in.readInt();
intervalShifts = in.readInt();
midIntervalPoint = in.readInt();
tableSize = in.readInt();
shifts = in.readInt();
doubleShifts = in.readInt();
firstPage = (Page)in.readObject();
lastPage = (Page)in.readObject();
lastPageId = in.readInt();
maxKey = in.readLong();
pageIndex = (Page[])in.readObject();
totalSize = in.readInt();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(indexIntervals);
out.writeInt(intervalShifts);
out.writeInt(midIntervalPoint);
out.writeInt(tableSize);
out.writeInt(shifts);
out.writeInt(doubleShifts);
out.writeObject(firstPage);
out.writeObject(lastPage);
out.writeInt(lastPageId);
out.writeLong(maxKey);
out.writeObject(pageIndex);
out.writeInt(totalSize);
}
private void init() {
// instantiate the first page
// previous sibling of first page is null
// next sibling of last page is null
this.firstPage = new Page( null,
this.lastPageId,
this.tableSize );
this.maxKey = this.lastPageId + 1 << this.doubleShifts;
// create an index of one
this.pageIndex = new Page[]{this.firstPage};
// our first page is also our last page
this.lastPage = this.firstPage;
}
public void clear() {
init();
}
public boolean isEmpty() {
return this.totalSize == 0;
}
public Object put(final long key,
Object value) {
if ( key < 0 ) {
throw new IllegalArgumentException( "-ve keys not supported: " + key );
}
// NULL is a placeholder to show the key exists
// but contains a null value
if ( value == null ) {
value = PrimitiveLongMap.NULL;
}
final Page page = findPage( key );
final Object oldValue = page.put( key,
value );
if ( oldValue == null ) {
this.totalSize++;
}
return oldValue;
}
public Object remove(final long key) {
if ( key > this.maxKey || key < 0 ) {
return null;
}
final Page page = findPage( key );
final Object oldValue = page.put( key,
null );
if ( this.lastPageId != 0 && this.lastPage.isEmpty() ) {
shrinkPages( this.lastPageId );
}
if ( oldValue != null ) {
this.totalSize--;
}
return oldValue;
}
public Object get(final long key) {
if ( key > this.maxKey || key < 0 ) {
return null;
}
Object value = findPage( key ).get( key );
// NULL means the key exists, so return a real null
if ( value == PrimitiveLongMap.NULL ) {
value = null;
}
return value;
}
/**
* gets the next populated key, after the given key position.
* @param key
* @return
*/
public long getNext(long key) {
final int currentPageId = (int) key >> this.doubleShifts;
final int nextPageId = (int) (key+1) >> this.doubleShifts;
if ( currentPageId != nextPageId ) {
Page page = findPage( key + 1);
while ( page.isEmpty() ) {
page = page.getNextSibling();
}
key = this.doubleShifts << page.getPageId();
} else {
key += 1;
}
while ( !containsKey( key ) && key <= this.maxKey ) {
key++;
}
if ( key > this.maxKey ) {
key -= 1;
}
return key;
}
public int size() {
return this.totalSize;
}
public Collection values() {
final CompositeCollection collection = new CompositeCollection();
Page page = this.firstPage;
while ( page != null && page.getPageId() <= this.lastPageId ) {
collection.addComposited( Arrays.asList( page.getValues() ) );
page = page.getNextSibling();
}
return collection;
}
public boolean containsKey(final long key) {
if ( key < 0 ) {
return false;
}
return get( key ) != null;
}
/**
* Expand index to accomodate given pageId Create empty TopNodes
*/
public Page expandPages(final int toPageId) {
for ( int x = this.lastPageId; x < toPageId; x++ ) {
this.lastPage = new Page( this.lastPage,
++this.lastPageId,
this.tableSize );
// index interval, so expand index
if ( this.lastPage.getPageId() % this.indexIntervals == 0 ) {
final int newSize = this.pageIndex.length + 1;
resizeIndex( newSize );
this.pageIndex[newSize - 1] = this.lastPage;
}
}
this.maxKey = (this.lastPageId + 1 << this.doubleShifts) - 1;
return this.lastPage;
}
/**
* Shrink index to accomodate given pageId
*/
public void shrinkPages(final int toPageId) {
for ( int x = this.lastPageId; x >= toPageId; x-- ) {
// last page is on index so shrink index
if ( (this.lastPageId) % this.indexIntervals == 0 && this.lastPageId != 0 ) {
resizeIndex( this.pageIndex.length - 1 );
}
final Page page = this.lastPage.getPreviousSibling();
page.setNextSibling( null );
this.lastPage.clear();
this.lastPage = page;
this.lastPageId = page.getPageId();
}
}
public void resizeIndex(final int newSize) {
final Page[] newIndex = new Page[newSize];
System.arraycopy( this.pageIndex,
0,
newIndex,
0,
(newSize > this.pageIndex.length) ? this.pageIndex.length : newSize );
this.pageIndex = newIndex;
}
private Page findPage(final long key) {
// determine Page
final int pageId = (int) key >> this.doubleShifts;
Page page;
// if pageId is lastNodeId use lastNode reference
if ( pageId == this.lastPageId ) {
page = this.lastPage;
}
// if pageId is zero use first page reference
else if ( pageId == 0 ) {
page = this.firstPage;
}
// if pageId is greater than lastTopNodeId need to expand
else if ( pageId > this.lastPageId ) {
page = expandPages( pageId );
} else {
// determine offset
final int offset = pageId >> this.intervalShifts;
// are we before or after the halfway point of an index interval
if ( (offset != (this.pageIndex.length - 1)) && ((key - (offset << this.intervalShifts << this.doubleShifts)) > this.midIntervalPoint) ) {
// after so go to next node index and go backwards
page = this.pageIndex[offset + 1];
while ( page.getPageId() != pageId ) {
page = page.getPreviousSibling();
}
} else {
// before so go to node index and go forwards
page = this.pageIndex[offset];
while ( page.getPageId() != pageId ) {
page = page.getNextSibling();
}
}
}
return page;
}
public static class Page
implements
Externalizable {
/**
*
*/
private static final long serialVersionUID = 510l;
private int pageSize;
private int pageId;
private int shifts;
private int tableSize;
private Page nextSibling;
private Page previousSibling;
private Object[][] tables;
private int filledSlots;
public Page() {
}
Page(final Page previousSibling,
final int pageId,
final int tableSize) {
// determine number of shifts
int i = 1;
int size = 2;
while ( size < tableSize ) {
size <<= 1;
++i;
}
// make sure table size is valid
this.tableSize = size;
this.shifts = i;
// create bi-directional link
this.previousSibling = previousSibling;
if ( this.previousSibling != null ) {
this.previousSibling.setNextSibling( this );
}
this.pageId = pageId;
this.pageSize = tableSize << this.shifts;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
pageSize = in.readInt();
pageId = in.readInt();
shifts = in.readInt();
tableSize = in.readInt();
nextSibling = (Page)in.readObject();
previousSibling = (Page)in.readObject();
tables = (Object[][])in.readObject();
filledSlots = in.readInt();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(pageSize);
out.writeInt(pageId);
out.writeInt(shifts);
out.writeInt(tableSize);
out.writeObject(nextSibling);
out.writeObject(previousSibling);
out.writeObject(tables);
out.writeInt(filledSlots);
}
public int getPageId() {
return this.pageId;
}
void setNextSibling(final Page nextSibling) {
this.nextSibling = nextSibling;
}
public Page getNextSibling() {
return this.nextSibling;
}
void setPreviousSibling(final Page previousSibling) {
this.previousSibling = previousSibling;
}
public Page getPreviousSibling() {
return this.previousSibling;
}
public Object get(long key) {
if ( this.tables == null ) {
return null;
}
// normalise key
key -= this.pageSize * this.pageId;
// determine table
final int table = (int) key >> this.shifts;
// determine offset
final int offset = table << this.shifts;
// tables[table][slot]
return this.tables[table][(int) key - offset];
}
public Object put(long key,
final Object newValue) {
if ( this.tables == null ) {
// initiate tree;
this.tables = new Object[this.tableSize][this.tableSize];
}
// normalise key
key -= this.pageSize * this.pageId;
// determine table
final int table = (int) key >> this.shifts;
// determine offset
final int offset = table << this.shifts;
// determine slot
final int slot = (int) key - offset;
// get old value
final Object oldValue = this.tables[table][slot];
this.tables[table][slot] = newValue;
// update number of empty cells for TopNode
if ( oldValue == null && newValue != null ) {
this.filledSlots++;
} else if ( oldValue != null && newValue == null ) {
this.filledSlots--;
}
// if this page contains no values then null the array
// to allow it to be garbage collected
if ( this.filledSlots == 0 ) {
this.tables = null;
}
return oldValue;
}
Object[][] getTables() {
return this.tables;
}
Object[] getValues() {
final Object[] values = new Object[this.filledSlots];
if ( values.length == 0 ) {
return values;
}
int x = 0;
Object value;
for ( int i = 0; i < this.tableSize; i++ ) {
for ( int j = 0; j < this.tableSize; j++ ) {
value = this.tables[i][j];
if ( value != null ) {
// swap NULL out placeholder
// Also filter out InitialFact
if ( value == PrimitiveLongMap.NULL ) {
value = null;
}
values[x] = value;
x++;
}
}
}
return values;
}
public boolean isEmpty() {
return this.filledSlots == 0;
}
void clear() {
this.previousSibling = null;
this.nextSibling = null;
this.tables = null;
}
}
}