/**
* Copyright (c) 2002-2013 "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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.nioneo.store;
import java.lang.reflect.Array;
import java.util.Arrays;
import org.neo4j.kernel.impl.util.Bits;
public enum ShortArray
{
BOOLEAN( PropertyType.BOOL, 1, Boolean.class )
{
@Override
int getRequiredBits( Object value )
{
return 1;
}
@Override
void put( Object value, Bits bytes, int requiredBits )
{
bytes.put( ((Boolean)value).booleanValue() ? 1 : 0, 1 );
}
@Override
Object createArray( int ofLength )
{
return new boolean[ofLength];
}
@Override
void get( Object array, int position, Bits bits, int requiredBits )
{
Array.setBoolean( array, position, bits.getByte( requiredBits ) != 0 );
}
},
BYTE( PropertyType.BYTE, 8, Byte.class )
{
@Override
int getRequiredBits( Object value )
{
byte v = ((Number)value).byteValue();
int highest = 0;
long mask = 1;
for ( int i = 1; i <= maxBits; i++, mask <<= 1 )
{
if ( (mask&v) != 0 )
{
highest = i;
}
}
return highest;
}
@Override
void put( Object value, Bits bytes, int requiredBits )
{
bytes.put( ((Byte)value).byteValue(), requiredBits );
}
@Override
Object createArray( int ofLength )
{
return new byte[ofLength];
}
@Override
void get( Object array, int position, Bits bits, int requiredBits )
{
Array.setByte( array, position, bits.getByte( requiredBits ) );
}
},
SHORT( PropertyType.SHORT, 16, Short.class )
{
@Override
int getRequiredBits( Object value )
{
short v = ((Number)value).shortValue();
int highest = 0;
long mask = 1;
for ( int i = 1; i <= maxBits; i++, mask <<= 1 )
{
if ( (mask&v) != 0 )
{
highest = i;
}
}
return highest;
}
@Override
void put( Object value, Bits bytes, int requiredBits )
{
bytes.put( ((Short)value).shortValue(), requiredBits );
}
@Override
Object createArray( int ofLength )
{
return new short[ofLength];
}
@Override
void get( Object array, int position, Bits bits, int requiredBits )
{
Array.setShort( array, position, bits.getShort( requiredBits ) );
}
},
CHAR( PropertyType.CHAR, 16, Character.class )
{
@Override
int getRequiredBits( Object value )
{
char v = ((Character)value).charValue();
int highest = 0;
long mask = 1;
for ( int i = 1; i <= maxBits; i++, mask <<= 1 )
{
if ( (mask&v) != 0 )
{
highest = i;
}
}
return highest;
}
@Override
void put( Object value, Bits bytes, int requiredBits )
{
bytes.put( ((Character)value).charValue(), requiredBits );
}
@Override
Object createArray( int ofLength )
{
return new char[ofLength];
}
@Override
void get( Object array, int position, Bits bits, int requiredBits )
{
Array.setChar( array, position, (char)bits.getShort( requiredBits ) );
}
},
INT( PropertyType.INT, 32, Integer.class )
{
@Override
int getRequiredBits( Object value )
{
int v = ((Number)value).intValue();
int highest = 0;
long mask = 1;
for ( int i = 1; i <= maxBits; i++, mask <<= 1 )
{
if ( (mask&v) != 0 )
{
highest = i;
}
}
return highest;
}
@Override
void put( Object value, Bits bytes, int requiredBits )
{
bytes.put( ((Integer)value).intValue(), requiredBits );
}
@Override
Object createArray( int ofLength )
{
return new int[ofLength];
}
@Override
void get( Object array, int position, Bits bits, int requiredBits )
{
Array.setInt( array, position, bits.getInt( requiredBits ) );
}
},
LONG( PropertyType.LONG, 64, Long.class )
{
@Override
int getRequiredBits( Object value )
{
long v = ((Number)value).longValue();
int highest = 0;
long mask = 1;
for ( int i = 1; i <= maxBits; i++, mask <<= 1 )
{
if ( (mask&v) != 0 )
{
highest = i;
}
}
return highest;
}
@Override
void put( Object value, Bits bytes, int requiredBits )
{
bytes.put( ((Long)value).longValue(), requiredBits );
}
@Override
Object createArray( int ofLength )
{
return new long[ofLength];
}
@Override
void get( Object array, int position, Bits bits, int requiredBits )
{
Array.setLong( array, position, bits.getLong( requiredBits ) );
}
},
FLOAT( PropertyType.FLOAT, 32, Float.class )
{
@Override
int getRequiredBits( Object value )
{
int v = Float.floatToIntBits( ((Number)value).floatValue() );
int highest = 0;
long mask = 1;
for ( int i = 1; i <= maxBits; i++, mask <<= 1 )
{
if ( (mask&v) != 0 )
{
highest = i;
}
}
return highest;
}
@Override
void put( Object value, Bits bytes, int requiredBits )
{
bytes.put( Float.floatToIntBits( ((Float)value).floatValue() ), requiredBits );
}
@Override
Object createArray( int ofLength )
{
return new float[ofLength];
}
@Override
void get( Object array, int position, Bits bits, int requiredBits )
{
int value = bits.getInt( requiredBits );
Array.setFloat( array, position, Float.intBitsToFloat( value ) );
}
},
DOUBLE( PropertyType.DOUBLE, 64, Double.class )
{
@Override
int getRequiredBits( Object value )
{
long v = Double.doubleToLongBits( ((Number)value).doubleValue() );
int highest = 0;
long mask = 1;
for ( int i = 1; i <= maxBits; i++, mask <<= 1 )
{
if ( (mask&v) != 0 )
{
highest = i;
}
}
return highest;
}
@Override
void put( Object value, Bits bytes, int requiredBits )
{
bytes.put( Double.doubleToLongBits( ((Double)value).doubleValue() ), requiredBits );
}
@Override
Object createArray( int ofLength )
{
return new double[ofLength];
}
@Override
void get( Object array, int position, Bits bits, int requiredBits )
{
long value = bits.getLong( requiredBits );
Array.setDouble( array, position, Double.longBitsToDouble( value ) );
}
};
final int maxBits;
private final Class<?> boxedClass;
private final PropertyType type;
private ShortArray( PropertyType type, int maxBits, Class<?> boxedClass )
{
this.type = type;
this.maxBits = maxBits;
this.boxedClass = boxedClass;
}
public int intValue()
{
return type.intValue();
}
abstract int getRequiredBits( Object value );
abstract void put( Object value, Bits bits, int requiredBits );
abstract void get( Object array, int position, Bits bits, int requiredBits );
abstract Object createArray( int ofLength );
boolean matches( Class<?> cls )
{
return boxedClass.equals( cls );
}
public static boolean encode( int keyId, Object array,
PropertyBlock target, int payloadSizeInBytes )
{
ShortArray type = typeOf( array );
if ( type == null )
{
return false;
}
/*
* If the array is huge, we don't have to check anything else.
* So do the length check first.
*/
int arrayLength = Array.getLength( array );
if ( arrayLength > 63 )/*because we only use 6 bits for length*/
{
return false;
}
int requiredBits = type.calculateRequiredBitsForArray( array );
if ( !willFit( requiredBits, arrayLength, payloadSizeInBytes ) )
{
// Too big array
return false;
}
Bits result = Bits.bits( calculateNumberOfBlocksUsed( arrayLength, requiredBits )*8 );
if ( result.getLongs().length > PropertyType.getPayloadSizeLongs() )
{
return false;
}
// [][][ ,bbbb][bbll,llll][yyyy,tttt][kkkk,kkkk][kkkk,kkkk][kkkk,kkkk]
result.put( keyId, 24 );
result.put( PropertyType.SHORT_ARRAY.intValue(), 4 );
result.put( type.type.intValue(), 4 );
result.put( arrayLength, 6 );
result.put( requiredBits, 6 );
for ( int i = 0; i < arrayLength; i++ )
{
type.put( Array.get( array, i ), result, requiredBits );
}
target.setValueBlocks( result.getLongs() );
return true;
}
public static Object decode( PropertyBlock block )
{
Bits bits = Bits.bitsFromLongs( Arrays.copyOf( block.getValueBlocks(), block.getValueBlocks().length ) );
// [][][ ,bbbb][bbll,llll][yyyy,tttt][kkkk,kkkk][kkkk,kkkk][kkkk,kkkk]
bits.getInt( 24 ); // Get rid of key
bits.getByte( 4 ); // Get rid of short array type
int typeId = bits.getByte( 4 );
int arrayLength = bits.getByte( 6 );
int requiredBits = bits.getByte( 6 );
/*
* So, it can be the case that values require 64 bits to store. However, you cannot encode this
* value with 6 bits. calculateRequiredBitsForArray never returns 0, because even for an array of
* all 0s one bit is required for every value. So when writing, we let it overflow and write out
* 0. When we are reading back, we just have to make sure that reading in 0 means 64.
*/
if ( requiredBits == 0 )
{
requiredBits = 64;
}
ShortArray type = typeOf( (byte)typeId );
Object array = type.createArray( arrayLength );
for ( int i = 0; i < arrayLength; i++ )
{
type.get( array, i, bits, requiredBits );
}
return array;
}
private static boolean willFit( int requiredBits, int arrayLength, int payloadSizeInBytes )
{
int totalBitsRequired = requiredBits*arrayLength;
int maxBits = payloadSizeInBytes * 8 - 24 - 4 - 4 - 6 - 6;
return totalBitsRequired <= maxBits;
}
public int calculateRequiredBitsForArray( Object array )
{
int arrayLength = Array.getLength( array );
if ( arrayLength == 0 )
{
return 0;
}
int highest = 1;
for ( int i = 0; i < arrayLength; i++ )
{
Object value = Array.get( array, i );
highest = Math.max( highest, getRequiredBits( value ) );
}
return highest;
}
public static ShortArray typeOf( byte typeId )
{
return ShortArray.values()[typeId-1];
}
public static ShortArray typeOf( Object array )
{
Class<?> componentType = array.getClass().getComponentType();
if ( componentType.isPrimitive() )
{
String name = componentType.getSimpleName();
return valueOf( name.toUpperCase() );
}
else
{
for ( ShortArray type : values() )
{
if ( type.matches( componentType ) )
{
return type;
}
}
}
return null;
}
public static int calculateNumberOfBlocksUsed( long firstBlock )
{
Bits bits = Bits.bitsFromLongs( new long[] {firstBlock} );
// bbbb][bbll,llll][yyyy,tttt][kkkk,kkkk][kkkk,kkkk][kkkk,kkkk]
bits.getInt( 24 ); // Get rid of key
bits.getByte( 4 ); // Get rid of short array type
bits.getByte( 4 ); // Get rid of the type
int arrayLength = bits.getByte( 6 );
int requiredBits = bits.getByte( 6 );
if ( requiredBits == 0 )
{
requiredBits = 64;
}
return calculateNumberOfBlocksUsed( arrayLength, requiredBits );
}
public static int calculateNumberOfBlocksUsed( int arrayLength, int requiredBits )
{
int bitsForItems = arrayLength*requiredBits;
/*
* Key, Property Type (ARRAY), Array Type, Array Length, Bits Per Member, Data
*/
int totalBits = 24 + 4 + 4 + 6 + 6 + bitsForItems;
int result = ( totalBits - 1 ) / 64 + 1;
return result;
}
}