/** * 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.storemigration.legacystore; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.LinkedList; import java.util.List; import org.neo4j.helpers.UTF8; import org.neo4j.kernel.impl.nioneo.store.Buffer; import org.neo4j.kernel.impl.nioneo.store.CommonAbstractStore; import org.neo4j.kernel.impl.nioneo.store.InvalidRecordException; import org.neo4j.kernel.impl.nioneo.store.OperationType; import org.neo4j.kernel.impl.nioneo.store.PersistenceWindow; import org.neo4j.kernel.impl.nioneo.store.PersistenceWindowPool; import org.neo4j.kernel.impl.nioneo.store.Record; public class LegacyDynamicStoreReader { public static final String FROM_VERSION_ARRAY = "ArrayPropertyStore v0.9.9"; public static final String FROM_VERSION_STRING = "StringPropertyStore v0.9.9"; private PersistenceWindowPool windowPool; private int blockSize; // in_use(byte)+prev_block(int)+nr_of_bytes(int)+next_block(int) protected static final int BLOCK_HEADER_SIZE = 1 + 4 + 4 + 4; private final FileChannel fileChannel; public LegacyDynamicStoreReader( String fileName, String fromVersionArray ) throws IOException { fileChannel = new RandomAccessFile( fileName, "r" ).getChannel(); long fileSize = fileChannel.size(); String expectedVersion = fromVersionArray; byte version[] = new byte[UTF8.encode( expectedVersion ).length]; ByteBuffer buffer = ByteBuffer.wrap( version ); fileChannel.position( fileSize - version.length ); fileChannel.read( buffer ); buffer = ByteBuffer.allocate( 4 ); fileChannel.position( 0 ); fileChannel.read( buffer ); buffer.flip(); blockSize = buffer.getInt(); windowPool = new PersistenceWindowPool( fileName, blockSize, fileChannel, CommonAbstractStore.calculateMappedMemory( null, fileName ), true, true ); } public List<LegacyDynamicRecord> getPropertyChain( long startBlockId ) { List<LegacyDynamicRecord> recordList = new LinkedList<LegacyDynamicRecord>(); long blockId = startBlockId; while ( blockId != Record.NO_NEXT_BLOCK.intValue() ) { PersistenceWindow window = windowPool.acquire( blockId, OperationType.READ ); try { LegacyDynamicRecord record = getRecord( blockId, window ); recordList.add( record ); blockId = record.getNextBlock(); } finally { windowPool.release( window ); } } return recordList; } private LegacyDynamicRecord getRecord( long blockId, PersistenceWindow window ) { LegacyDynamicRecord record = new LegacyDynamicRecord( blockId ); Buffer buffer = window.getOffsettedBuffer( blockId ); // [ , x] in use // [xxxx, ] high bits for prev block long inUseByte = buffer.get(); boolean inUse = (inUseByte & 0x1) == Record.IN_USE.intValue(); if ( !inUse ) { throw new InvalidRecordException( "Not in use, blockId[" + blockId + "]" ); } long prevBlock = buffer.getUnsignedInt(); long prevModifier = (inUseByte & 0xF0L) << 28; int dataSize = blockSize - BLOCK_HEADER_SIZE; // [ , ][xxxx,xxxx][xxxx,xxxx][xxxx,xxxx] number of bytes // [ ,xxxx][ , ][ , ][ , ] higher bits for next block long nrOfBytesInt = buffer.getInt(); int nrOfBytes = (int) (nrOfBytesInt & 0xFFFFFF); long nextBlock = buffer.getUnsignedInt(); long nextModifier = (nrOfBytesInt & 0xF000000L) << 8; long longNextBlock = LegacyStore.longFromIntAndMod( nextBlock, nextModifier ); if ( longNextBlock != Record.NO_NEXT_BLOCK.intValue() && nrOfBytes < dataSize || nrOfBytes > dataSize ) { throw new InvalidRecordException( "Next block set[" + nextBlock + "] current block illegal size[" + nrOfBytes + "/" + dataSize + "]" ); } record.setInUse( true ); record.setLength( nrOfBytes ); record.setPrevBlock( LegacyStore.longFromIntAndMod( prevBlock, prevModifier ) ); record.setNextBlock( longNextBlock ); byte byteArrayElement[] = new byte[nrOfBytes]; buffer.get( byteArrayElement ); record.setData( byteArrayElement ); return record; } 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 static 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 void close() throws IOException { fileChannel.close(); } }