/*
* Copyright (c) 2002-2009 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.nioneo.store;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Map;
/**
* Dynamic store that stores strings.
*/
class DynamicArrayStore extends AbstractDynamicStore
{
// store version, each store ends with this string (byte encoded)
private static final String VERSION = "ArrayPropertyStore v0.9.5";
private static enum ArrayType
{
ILLEGAL( 0 ),
INT( 1 ),
STRING( 2 ),
BOOL( 3 ),
DOUBLE( 4 ),
FLOAT( 5 ),
LONG( 6 ),
BYTE( 7 ),
CHAR( 8 ),
SHORT( 10 );
private int type;
ArrayType( int type )
{
this.type = type;
}
public byte byteValue()
{
return (byte) type;
}
}
public DynamicArrayStore( String fileName, Map<?,?> config )
{
super( fileName, config );
}
public DynamicArrayStore( String fileName )
{
super( fileName );
}
public String getTypeAndVersionDescriptor()
{
return VERSION;
}
public static void createStore( String fileName, int blockSize )
{
createEmptyStore( fileName, blockSize, VERSION );
}
private Collection<DynamicRecord> allocateFromInt( int startBlock,
int[] array )
{
int size = array.length * 4 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.INT.byteValue() );
for ( int i : array )
{
buf.putInt( i );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromInt( int startBlock,
Integer[] array )
{
int size = array.length * 4 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.INT.byteValue() );
for ( int i : array )
{
buf.putInt( i );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromShort( int startBlock,
short[] array )
{
int size = array.length * 2 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.SHORT.byteValue() );
for ( short i : array )
{
buf.putShort( i );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromShort( int startBlock,
Short[] array )
{
int size = array.length * 2 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.SHORT.byteValue() );
for ( short i : array )
{
buf.putShort( i );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromString( int startBlock,
String[] array )
{
int size = 5;
for ( String str : array )
{
size += 4 + str.length() * 2;
}
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.STRING.byteValue() );
buf.putInt( array.length );
for ( String str : array )
{
int length = str.length();
char[] chars = new char[length];
str.getChars( 0, length, chars, 0 );
buf.putInt( length * 2 );
for ( char c : chars )
{
buf.putChar( c );
}
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromBool( int startBlock,
boolean[] array )
{
int size = 5 + array.length / 8;
if ( array.length % 8 > 0 )
{
size++;
}
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.BOOL.byteValue() );
buf.putInt( array.length );
byte currentValue = 0;
int byteItr = 0;
for ( boolean b : array )
{
if ( b )
{
currentValue += 1 << byteItr;
}
byteItr++;
if ( byteItr == 8 )
{
buf.put( currentValue );
byteItr = 0;
currentValue = 0;
}
}
if ( byteItr != 0 )
{
buf.put( currentValue );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromBool( int startBlock,
Boolean[] array )
{
int size = 5 + array.length / 8;
if ( array.length % 8 > 0 )
{
size++;
}
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.BOOL.byteValue() );
buf.putInt( array.length );
byte currentValue = 0;
int byteItr = 0;
for ( Boolean b : array )
{
if ( b )
{
currentValue += 1 << byteItr;
}
byteItr++;
if ( byteItr == 8 )
{
buf.put( currentValue );
byteItr = 0;
currentValue = 0;
}
}
if ( byteItr != 0 )
{
buf.put( currentValue );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromDouble( int startBlock,
double[] array )
{
int size = array.length * 8 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.DOUBLE.byteValue() );
for ( double d : array )
{
buf.putDouble( d );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromDouble( int startBlock,
Double[] array )
{
int size = array.length * 8 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.DOUBLE.byteValue() );
for ( double d : array )
{
buf.putDouble( d );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromFloat( int startBlock,
float[] array )
{
int size = array.length * 4 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.FLOAT.byteValue() );
for ( float f : array )
{
buf.putFloat( f );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromFloat( int startBlock,
Float[] array )
{
int size = array.length * 4 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.FLOAT.byteValue() );
for ( float f : array )
{
buf.putFloat( f );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromLong( int startBlock,
long[] array )
{
int size = array.length * 8 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.LONG.byteValue() );
for ( long l : array )
{
buf.putLong( l );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromLong( int startBlock,
Long[] array )
{
int size = array.length * 8 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.LONG.byteValue() );
for ( long l : array )
{
buf.putLong( l );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromByte( int startBlock,
byte[] array )
{
int size = array.length + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.BYTE.byteValue() );
buf.put( array );
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromByte( int startBlock,
Byte[] array )
{
int size = array.length + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.BYTE.byteValue() );
for ( byte b : array )
{
buf.put( b );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromChar( int startBlock,
char[] array )
{
int size = array.length * 2 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.CHAR.byteValue() );
for ( char c : array )
{
buf.putChar( c );
}
return allocateRecords( startBlock, buf.array() );
}
private Collection<DynamicRecord> allocateFromChar( int startBlock,
Character[] array )
{
int size = array.length * 2 + 1;
ByteBuffer buf = ByteBuffer.allocate( size );
buf.put( ArrayType.CHAR.byteValue() );
for ( char c : array )
{
buf.putChar( c );
}
return allocateRecords( startBlock, buf.array() );
}
public Collection<DynamicRecord> allocateRecords( int startBlock,
Object array )
{
if ( array instanceof int[] )
{
return allocateFromInt( startBlock, (int[]) array );
}
if ( array instanceof Integer[] )
{
return allocateFromInt( startBlock, (Integer[]) array );
}
if ( array instanceof String[] )
{
return allocateFromString( startBlock, (String[]) array );
}
if ( array instanceof boolean[] )
{
return allocateFromBool( startBlock, (boolean[]) array );
}
if ( array instanceof Boolean[] )
{
return allocateFromBool( startBlock, (Boolean[]) array );
}
if ( array instanceof double[] )
{
return allocateFromDouble( startBlock, (double[]) array );
}
if ( array instanceof Double[] )
{
return allocateFromDouble( startBlock, (Double[]) array );
}
if ( array instanceof float[] )
{
return allocateFromFloat( startBlock, (float[]) array );
}
if ( array instanceof Float[] )
{
return allocateFromFloat( startBlock, (Float[]) array );
}
if ( array instanceof long[] )
{
return allocateFromLong( startBlock, (long[]) array );
}
if ( array instanceof Long[] )
{
return allocateFromLong( startBlock, (Long[]) array );
}
if ( array instanceof byte[] )
{
return allocateFromByte( startBlock, (byte[]) array );
}
if ( array instanceof Byte[] )
{
return allocateFromByte( startBlock, (Byte[]) array );
}
if ( array instanceof char[] )
{
return allocateFromChar( startBlock, (char[]) array );
}
if ( array instanceof Character[] )
{
return allocateFromChar( startBlock, (Character[]) array );
}
if ( array instanceof short[] )
{
return allocateFromShort( startBlock, (short[]) array );
}
if ( array instanceof Short[] )
{
return allocateFromShort( startBlock, (Short[]) array );
}
throw new IllegalArgumentException( array +
" not a valid array type." );
}
public Object getRightArray( byte[] bArray )
{
ByteBuffer buf = ByteBuffer.wrap( bArray );
byte type = buf.get();
if ( type == ArrayType.INT.byteValue() )
{
int size = (bArray.length - 1) / 4;
assert (bArray.length - 1) % 4 == 0;
int[] array = new int[size];
for ( int i = 0; i < size; i++ )
{
array[i] = buf.getInt();
}
return array;
}
if ( type == ArrayType.STRING.byteValue() )
{
String[] array = new String[buf.getInt()];
for ( int i = 0; i < array.length; i++ )
{
int charLength = buf.getInt() / 2;
char charBuffer[] = new char[charLength];
for ( int j = 0; j < charLength; j++ )
{
charBuffer[j] = buf.getChar();
}
array[i] = new String( charBuffer );
}
return array;
}
if ( type == ArrayType.BOOL.byteValue() )
{
boolean[] array = new boolean[buf.getInt()];
int byteItr = 1;
byte currentValue = buf.get();
for ( int i = 0; i < array.length; i++ )
{
array[i] = (currentValue & byteItr) > 0 ? true : false;
byteItr *= 2;
if ( byteItr == 256 )
{
byteItr = 0;
currentValue = buf.get();
}
}
return array;
}
if ( type == ArrayType.DOUBLE.byteValue() )
{
int size = (bArray.length - 1) / 8;
assert (bArray.length - 1) % 8 == 0;
double[] array = new double[size];
for ( int i = 0; i < size; i++ )
{
array[i] = buf.getDouble();
}
return array;
}
if ( type == ArrayType.FLOAT.byteValue() )
{
int size = (bArray.length - 1) / 4;
assert (bArray.length - 1) % 4 == 0;
float[] array = new float[size];
for ( int i = 0; i < size; i++ )
{
array[i] = buf.getFloat();
}
return array;
}
if ( type == ArrayType.LONG.byteValue() )
{
int size = (bArray.length - 1) / 8;
assert (bArray.length - 1) % 8 == 0;
long[] array = new long[size];
for ( int i = 0; i < size; i++ )
{
array[i] = buf.getLong();
}
return array;
}
if ( type == ArrayType.BYTE.byteValue() )
{
int size = (bArray.length - 1);
byte[] array = new byte[size];
buf.get( array );
return array;
}
if ( type == ArrayType.CHAR.byteValue() )
{
int size = (bArray.length - 1) / 2;
assert (bArray.length - 1) % 2 == 0;
char[] array = new char[size];
for ( int i = 0; i < size; i++ )
{
array[i] = buf.getChar();
}
return array;
}
if ( type == ArrayType.SHORT.byteValue() )
{
int size = (bArray.length - 1) / 2;
assert (bArray.length - 1) % 2 == 0;
short[] array = new short[size];
for ( short i = 0; i < size; i++ )
{
array[i] = buf.getShort();
}
return array;
}
throw new InvalidRecordException( "Unknown array type[" + type + "]" );
}
public Object getArray( int blockId )
{
byte bArray[] = get( blockId );
return getRightArray( bArray );
}
@Override
protected boolean versionFound( String version )
{
if ( !version.startsWith( "ArrayPropertyStore" ) )
{
// non clean shutdown, need to do recover with right neo
return false;
}
if ( version.equals( "ArrayPropertyStore v0.9.3" ) )
{
rebuildIdGenerator();
closeIdGenerator();
return true;
}
throw new IllegalStoreVersionException( "Store version [" + version +
"]. Please make sure you are not running old Neo4j kernel " +
" towards a store that has been created by newer version " +
" of Neo4j." );
}
}