/** * 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.index; import static org.neo4j.helpers.collection.MapUtil.reverse; import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.read2bLengthAndString; import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.readByte; import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.write2bLengthAndString; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.PropertyContainer; import org.neo4j.graphdb.Relationship; import org.neo4j.kernel.impl.transaction.xaframework.LogBuffer; import org.neo4j.kernel.impl.transaction.xaframework.XaCommand; /** * A command which have to be first in the transaction. It will map index names * and keys to ids so that all other commands in that transaction only refer * to ids instead of names. This reduced the number of bytes needed for commands * roughly 50% for transaction with more than a couple of commands in it, * depending on the size of the value. * * After this command has been created it will act as a factory for other * commands so that it can spit out correct index name and key ids. */ public class IndexDefineCommand extends XaCommand { private final AtomicInteger nextIndexNameId = new AtomicInteger(); private final AtomicInteger nextKeyId = new AtomicInteger(); private final Map<String, Byte> indexNameIdRange; private final Map<String, Byte> keyIdRange; private final Map<Byte, String> idToIndexName; private final Map<Byte, String> idToKey; public IndexDefineCommand() { indexNameIdRange = new HashMap<String, Byte>(); keyIdRange = new HashMap<String, Byte>(); idToIndexName = new HashMap<Byte, String>(); idToKey = new HashMap<Byte, String>(); } public IndexDefineCommand( Map<String, Byte> indexNames, Map<String, Byte> keys ) { this.indexNameIdRange = indexNames; this.keyIdRange = keys; idToIndexName = reverse( indexNames ); idToKey = reverse( keys ); } private static String getFromMap( Map<Byte, String> map, byte id ) { String result = map.get( id ); if ( result == null ) { throw new IllegalArgumentException( "" + id ); } return result; } public IndexCommand create( String indexName, Class<?> entityType, Map<String, String> config ) { return new IndexCommand.CreateCommand( indexNameId( indexName ), entityTypeId( entityType ), config ); } public IndexCommand add( String indexName, Class<?> entityType, long entityId, String key, Object value ) { return new IndexCommand.AddCommand( indexNameId( indexName ), entityTypeId( entityType ), entityId, keyId( key ), value ); } public IndexCommand addRelationship( String indexName, Class<?> entityType, long entityId, String key, Object value, long startNode, long endNode ) { return new IndexCommand.AddRelationshipCommand( indexNameId( indexName ), entityTypeId( entityType ), entityId, keyId( key ), value, startNode, endNode ); } public IndexCommand remove( String indexName, Class<?> entityType, long entityId, String key, Object value ) { return new IndexCommand.RemoveCommand( indexNameId( indexName ), entityTypeId( entityType ), entityId, key != null ? keyId( key ) : 0, value ); } public IndexCommand delete( String indexName, Class<?> entityType ) { return new IndexCommand.DeleteCommand( indexNameId( indexName ), entityTypeId( entityType ) ); } public String getIndexName( byte id ) { return getFromMap( idToIndexName, id ); } public String getKey( byte id ) { return getFromMap( idToKey, id ); } public static byte entityTypeId( Class<?> entityType ) { return entityType.equals( Relationship.class ) ? IndexCommand.RELATIONSHIP : IndexCommand.NODE; } public static Class<? extends PropertyContainer> entityType( byte id ) { switch ( id ) { case IndexCommand.NODE: return Node.class; case IndexCommand.RELATIONSHIP: return Relationship.class; default: throw new IllegalArgumentException( "" + id ); } } private byte indexNameId( String indexName ) { return id( indexName, indexNameIdRange, nextIndexNameId, idToIndexName ); } private byte keyId( String key ) { return id( key, keyIdRange, nextKeyId, idToKey ); } private byte id( String key, Map<String, Byte> idRange, AtomicInteger nextId, Map<Byte, String> reverse ) { Byte id = idRange.get( key ); if ( id == null ) { id = Byte.valueOf( (byte) nextId.incrementAndGet() ); idRange.put( key, id ); reverse.put( id, key ); } return id; } @Override public void execute() { } @Override public void writeToFile( LogBuffer buffer ) throws IOException { buffer.put( (byte)(IndexCommand.DEFINE_COMMAND << 5) ); buffer.put( (byte)0 ); buffer.put( (byte)0 ); writeMap( indexNameIdRange, buffer ); writeMap( keyIdRange, buffer ); } static Map<String, Byte> readMap( ReadableByteChannel channel, ByteBuffer buffer ) throws IOException { Byte size = readByte( channel, buffer ); if ( size == null ) return null; Map<String, Byte> result = new HashMap<String, Byte>(); for ( int i = 0; i < size; i++ ) { String key = read2bLengthAndString( channel, buffer ); Byte id = readByte( channel, buffer ); if ( key == null || id == null ) return null; result.put( key, id ); } return result; } private static void writeMap( Map<String, Byte> map, LogBuffer buffer ) throws IOException { buffer.put( (byte)map.size() ); for ( Map.Entry<String, Byte> entry : map.entrySet() ) { write2bLengthAndString( buffer, entry.getKey() ); buffer.put( entry.getValue() ); } } @Override public boolean equals( Object obj ) { IndexDefineCommand other = (IndexDefineCommand) obj; return indexNameIdRange.equals( other.indexNameIdRange ) && keyIdRange.equals( other.keyIdRange ); } }