/**
* 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.core;
import java.util.ArrayList;
import java.util.List;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.kernel.impl.nioneo.store.PropertyData;
import org.neo4j.kernel.impl.transaction.LockType;
import org.neo4j.kernel.impl.util.ArrayMap;
abstract class Primitive
{
// Used for marking that properties have been loaded but there just wasn't any.
// Saves an extra trip down to the store layer.
private static final PropertyData[] NO_PROPERTIES = new PropertyData[0];
private volatile PropertyData[] properties;
protected abstract PropertyData changeProperty( NodeManager nodeManager, PropertyData property, Object value );
protected abstract PropertyData addProperty( NodeManager nodeManager, PropertyIndex index, Object value );
protected abstract void removeProperty( NodeManager nodeManager,
PropertyData property );
protected abstract ArrayMap<Integer, PropertyData> loadProperties(
NodeManager nodeManager, boolean light );
Primitive( boolean newPrimitive )
{
if ( newPrimitive )
{
properties = NO_PROPERTIES;
}
}
public abstract long getId();
@Override
public int hashCode()
{
long id = getId();
return (int) (( id >>> 32 ) ^ id );
}
public Iterable<Object> getPropertyValues( NodeManager nodeManager )
{
ArrayMap<Integer,PropertyData> skipMap =
nodeManager.getCowPropertyRemoveMap( this );
ArrayMap<Integer,PropertyData> addMap =
nodeManager.getCowPropertyAddMap( this );
ensureFullProperties( nodeManager );
List<Object> values = new ArrayList<Object>();
for ( PropertyData property : properties )
{
Integer index = property.getIndex();
if ( skipMap != null && skipMap.get( index ) != null )
{
continue;
}
if ( addMap != null && addMap.get( index ) != null )
{
continue;
}
values.add( property.getValue() );
}
if ( addMap != null )
{
for ( PropertyData property : addMap.values() )
{
values.add( property.getValue() );
}
}
return values;
}
public Iterable<String> getPropertyKeys( NodeManager nodeManager )
{
ArrayMap<Integer,PropertyData> skipMap =
nodeManager.getCowPropertyRemoveMap( this );
ArrayMap<Integer,PropertyData> addMap =
nodeManager.getCowPropertyAddMap( this );
ensureFullProperties( nodeManager );
List<String> keys = new ArrayList<String>();
for ( PropertyData property : properties )
{
Integer index = property.getIndex();
if ( skipMap != null && skipMap.get( index ) != null )
{
continue;
}
if ( addMap != null && addMap.get( index ) != null )
{
continue;
}
keys.add( nodeManager.getIndexFor( index ).getKey() );
}
if ( addMap != null )
{
for ( Integer index : addMap.keySet() )
{
keys.add( nodeManager.getIndexFor( index ).getKey() );
}
}
return keys;
}
public Object getProperty( NodeManager nodeManager, String key ) throws NotFoundException
{
if ( key == null )
{
throw new IllegalArgumentException( "null key" );
}
ArrayMap<Integer,PropertyData> skipMap =
nodeManager.getCowPropertyRemoveMap( this );
ArrayMap<Integer,PropertyData> addMap =
nodeManager.getCowPropertyAddMap( this );
ensureFullProperties( nodeManager );
for ( PropertyIndex index : nodeManager.index( key ) )
{
if ( skipMap != null && skipMap.get( index.getKeyId() ) != null )
{
throw newPropertyNotFoundException( key );
}
if ( addMap != null )
{
PropertyData property = addMap.get( index.getKeyId() );
if ( property != null )
{
return getPropertyValue( nodeManager, property );
}
}
PropertyData property = getPropertyForIndex( index.getKeyId() );
if ( property != null )
{
return getPropertyValue( nodeManager, property );
}
}
PropertyData property = getSlowProperty( nodeManager, addMap, skipMap, key );
if ( property != null )
{
return getPropertyValue( nodeManager, property );
}
throw newPropertyNotFoundException( key );
}
private NotFoundException newPropertyNotFoundException( String key )
{
return new NotFoundException( key +
" property not found for " + this + "." );
}
private PropertyData getSlowProperty( NodeManager nodeManager,
ArrayMap<Integer, PropertyData> addMap,
ArrayMap<Integer,PropertyData> skipMap, String key )
{
if ( nodeManager.hasAllPropertyIndexes() )
{
return null;
}
if ( addMap != null )
{
for ( int keyId : addMap.keySet() )
{
if ( !nodeManager.hasIndexFor( keyId ) )
{
PropertyIndex indexToCheck =
nodeManager.getIndexFor( keyId );
if ( indexToCheck.getKey().equals( key ) )
{
if ( skipMap != null && skipMap.get( keyId ) != null )
{
throw newPropertyNotFoundException( key );
}
PropertyData property =
addMap.get( indexToCheck.getKeyId() );
if ( property != null )
{
return property;
}
}
}
}
}
for ( PropertyData property : properties )
{
int keyId = property.getIndex();
if ( !nodeManager.hasIndexFor( keyId ) )
{
PropertyIndex indexToCheck = nodeManager.getIndexFor( keyId );
if ( indexToCheck.getKey().equals( key ) )
{
if ( skipMap != null && skipMap.get( keyId ) != null )
{
throw newPropertyNotFoundException( key );
}
if ( property != null )
{
return property;
}
}
}
}
return null;
}
public Object getProperty( NodeManager nodeManager, String key, Object defaultValue )
{
if ( key == null )
{
throw new IllegalArgumentException( "null key" );
}
ArrayMap<Integer,PropertyData> skipMap =
nodeManager.getCowPropertyRemoveMap( this );
ArrayMap<Integer,PropertyData> addMap =
nodeManager.getCowPropertyAddMap( this );
ensureFullProperties( nodeManager );
for ( PropertyIndex index : nodeManager.index( key ) )
{
if ( skipMap != null && skipMap.get( index.getKeyId() ) != null )
{
return defaultValue;
}
if ( addMap != null )
{
PropertyData property = addMap.get( index.getKeyId() );
if ( property != null )
{
return getPropertyValue( nodeManager, property );
}
}
PropertyData property = getPropertyForIndex( index.getKeyId() );
if ( property != null )
{
return getPropertyValue( nodeManager, property );
}
}
PropertyData property = getSlowProperty( nodeManager, addMap, skipMap, key );
if ( property != null )
{
return getPropertyValue( nodeManager, property );
}
return defaultValue;
}
public boolean hasProperty( NodeManager nodeManager, String key )
{
if ( key == null )
{
return false;
}
ArrayMap<Integer,PropertyData> skipMap =
nodeManager.getCowPropertyRemoveMap( this );
ArrayMap<Integer,PropertyData> addMap =
nodeManager.getCowPropertyAddMap( this );
ensureFullProperties( nodeManager );
for ( PropertyIndex index : nodeManager.index( key ) )
{
if ( skipMap != null && skipMap.get( index.getKeyId() ) != null )
{
return false;
}
if ( addMap != null )
{
PropertyData property = addMap.get( index.getKeyId() );
if ( property != null )
{
return true;
}
}
PropertyData property = getPropertyForIndex( index.getKeyId() );
if ( property != null )
{
return true;
}
}
PropertyData property = getSlowProperty( nodeManager, addMap, skipMap, key );
if ( property != null )
{
return true;
}
return false;
}
public void setProperty( NodeManager nodeManager, String key, Object value )
{
if ( key == null || value == null )
{
throw new IllegalArgumentException( "Null parameter, " + "key=" +
key + ", " + "value=" + value );
}
nodeManager.acquireLock( this, LockType.WRITE );
boolean success = false;
try
{
ensureFullProperties( nodeManager );
ArrayMap<Integer,PropertyData> addMap =
nodeManager.getCowPropertyAddMap( this, true );
ArrayMap<Integer,PropertyData> skipMap =
nodeManager.getCowPropertyRemoveMap( this );
PropertyIndex index = null;
PropertyData property = null;
boolean foundInSkipMap = false;
for ( PropertyIndex cachedIndex : nodeManager.index( key ) )
{
if ( skipMap != null )
{
if ( skipMap.remove( cachedIndex.getKeyId() ) != null )
{
foundInSkipMap = true;
}
}
index = cachedIndex;
property = addMap.get( cachedIndex.getKeyId() );
if ( property != null )
{
break;
}
property = getPropertyForIndex( cachedIndex.getKeyId() );
if ( property != null )
{
break;
}
}
if ( property == null && !nodeManager.hasAllPropertyIndexes() )
{
for ( int keyId : addMap.keySet() )
{
if ( !nodeManager.hasIndexFor( keyId ) )
{
PropertyIndex indexToCheck = nodeManager
.getIndexFor( keyId );
if ( indexToCheck.getKey().equals( key ) )
{
if ( skipMap != null )
{
skipMap.remove( indexToCheck.getKeyId() );
}
index = indexToCheck;
property = addMap.get( indexToCheck.getKeyId() );
if ( property != null )
{
break;
}
}
}
}
if ( property == null )
{
for ( PropertyData aProperty : properties )
{
int keyId = aProperty.getIndex();
if ( !nodeManager.hasIndexFor( keyId ) )
{
PropertyIndex indexToCheck = nodeManager
.getIndexFor( keyId );
if ( indexToCheck.getKey().equals( key ) )
{
if ( skipMap != null )
{
skipMap.remove( indexToCheck.getKeyId() );
}
index = indexToCheck;
property = getPropertyForIndex( indexToCheck.getKeyId() );
if ( property != null )
{
break;
}
}
}
}
}
}
if ( index == null )
{
index = nodeManager.createPropertyIndex( key );
}
if ( property != null && !foundInSkipMap )
{
property = changeProperty( nodeManager, property, value );
}
else
{
property = addProperty( nodeManager, index, value );
}
addMap.put( index.getKeyId(), property );
success = true;
}
finally
{
nodeManager.releaseLock( this, LockType.WRITE );
if ( !success )
{
nodeManager.setRollbackOnly();
}
}
}
public Object removeProperty( NodeManager nodeManager, String key )
{
if ( key == null )
{
throw new IllegalArgumentException( "Null parameter." );
}
nodeManager.acquireLock( this, LockType.WRITE );
boolean success = false;
try
{
ensureFullProperties( nodeManager );
PropertyData property = null;
ArrayMap<Integer,PropertyData> addMap =
nodeManager.getCowPropertyAddMap( this );
// Don't create the map if it doesn't exist here... but instead when (and if)
// the property is found below.
ArrayMap<Integer,PropertyData> removeMap = nodeManager.getCowPropertyRemoveMap( this, false );
for ( PropertyIndex cachedIndex : nodeManager.index( key ) )
{
if ( addMap != null )
{
property = addMap.remove( cachedIndex.getKeyId() );
if ( property != null )
{
removeMap = removeMap != null ? removeMap : nodeManager.getCowPropertyRemoveMap( this, true );
removeMap.put( cachedIndex.getKeyId(), property );
break;
}
}
if ( removeMap != null && removeMap.get( cachedIndex.getKeyId() ) != null )
{
success = true;
return null;
}
property = getPropertyForIndex( cachedIndex.getKeyId() );
if ( property != null )
{
removeMap = removeMap != null ? removeMap : nodeManager.getCowPropertyRemoveMap( this, true );
removeMap.put( cachedIndex.getKeyId(), property );
break;
}
}
if ( property == null && !nodeManager.hasAllPropertyIndexes() )
{
if ( addMap != null )
{
for ( int keyId : addMap.keySet() )
{
if ( !nodeManager.hasIndexFor( keyId ) )
{
PropertyIndex indexToCheck = nodeManager
.getIndexFor( keyId );
if ( indexToCheck.getKey().equals( key ) )
{
property = addMap.remove( indexToCheck
.getKeyId() );
if ( property != null )
{
removeMap = removeMap != null ? removeMap : nodeManager.getCowPropertyRemoveMap( this, true );
removeMap.put( indexToCheck.getKeyId(),
property );
break;
}
}
}
}
if ( property == null )
{
for ( PropertyData aProperty : properties )
{
int keyId = aProperty.getIndex();
if ( !nodeManager.hasIndexFor( keyId ) )
{
PropertyIndex indexToCheck = nodeManager
.getIndexFor( keyId );
if ( indexToCheck.getKey().equals( key ) )
{
property = getPropertyForIndex( indexToCheck.getKeyId() );
if ( property != null )
{
removeMap = removeMap != null ? removeMap : nodeManager.getCowPropertyRemoveMap( this, true );
removeMap.put( indexToCheck.getKeyId(),
property );
break;
}
}
}
}
}
}
}
if ( property == null )
{
success = true;
return null;
}
removeProperty( nodeManager, property );
success = true;
return getPropertyValue( nodeManager, property );
}
finally
{
nodeManager.releaseLock( this, LockType.WRITE );
if ( !success )
{
nodeManager.setRollbackOnly();
}
}
}
private Object getPropertyValue( NodeManager nodeManager, PropertyData property )
{
Object value = property.getValue();
if ( value == null )
{
/*
* This will only happen for "heavy" property value, such as
* strings/arrays
*/
value = nodeManager.loadPropertyValue( property );
property.setNewValue( value );
}
return value;
}
protected void commitPropertyMaps(
ArrayMap<Integer,PropertyData> cowPropertyAddMap,
ArrayMap<Integer,PropertyData> cowPropertyRemoveMap )
{
if ( properties == null )
{
// we will load full in some other tx
return;
}
PropertyData[] newArray = properties;
/*
* add map will definitely be added in the properties array - all properties
* added and later removed in the same tx are removed from there as well.
* The remove map will not necessarily be removed, since it may hold a prop that was
* added in this tx. So the difference in size is all the keys that are common
* between properties and remove map subtracted by the add map size.
*/
int extraLength = 0;
if (cowPropertyAddMap != null)
{
extraLength += cowPropertyAddMap.size();
}
if ( extraLength > 0 )
{
newArray = new PropertyData[properties.length + extraLength];
System.arraycopy( properties, 0, newArray, 0, properties.length );
}
int newArraySize = properties.length;
if ( cowPropertyRemoveMap != null )
{
for ( Integer keyIndex : cowPropertyRemoveMap.keySet() )
{
for ( int i = 0; i < newArraySize; i++ )
{
PropertyData existingProperty = newArray[i];
if ( existingProperty.getIndex() == keyIndex )
{
int swapWith = --newArraySize;
newArray[i] = newArray[swapWith];
newArray[swapWith] = null;
break;
}
}
}
}
if ( cowPropertyAddMap != null )
{
for ( PropertyData addedProperty : cowPropertyAddMap.values() )
{
for ( int i = 0; i < newArray.length; i++ )
{
PropertyData existingProperty = newArray[i];
if ( existingProperty == null || addedProperty.getIndex() == existingProperty.getIndex() )
{
newArray[i] = addedProperty;
if ( existingProperty == null )
{
newArraySize++;
}
break;
}
}
}
}
if ( newArraySize < newArray.length )
{
PropertyData[] compactedNewArray = new PropertyData[newArraySize];
System.arraycopy( newArray, 0, compactedNewArray, 0, newArraySize );
properties = compactedNewArray;
}
else
{
properties = newArray;
}
}
private PropertyData getPropertyForIndex( int keyId )
{
for ( PropertyData property : properties )
{
if ( property.getIndex() == keyId )
{
return property;
}
}
return null;
}
private boolean ensureFullProperties( NodeManager nodeManager )
{
if ( properties == null )
{
this.properties = toPropertyArray( loadProperties( nodeManager,
false ) );
return true;
}
return false;
}
private PropertyData[] toPropertyArray( ArrayMap<Integer, PropertyData> loadedProperties )
{
if ( loadedProperties == null || loadedProperties.size() == 0 )
{
return NO_PROPERTIES;
}
PropertyData[] result = new PropertyData[loadedProperties.size()];
int i = 0;
for ( PropertyData property : loadedProperties.values() )
{
result[i++] = property;
}
return result;
}
private boolean ensureFullLightProperties( NodeManager nodeManager )
{
if ( properties == null )
{
this.properties = toPropertyArray( loadProperties( nodeManager,
true ) );
return true;
}
return false;
}
protected List<PropertyEventData> getAllCommittedProperties( NodeManager nodeManager )
{
ensureFullLightProperties( nodeManager );
if ( properties == null )
{
return new ArrayList<PropertyEventData>();
}
List<PropertyEventData> props =
new ArrayList<PropertyEventData>( properties.length );
for ( PropertyData property : properties )
{
PropertyIndex index = nodeManager.getIndexFor( property.getIndex() );
Object value = getPropertyValue( nodeManager, property );
props.add( new PropertyEventData( index.getKey(), value ) );
}
return props;
}
protected Object getCommittedPropertyValue( NodeManager nodeManager, String key )
{
ensureFullLightProperties( nodeManager );
for ( PropertyIndex index : nodeManager.index( key ) )
{
PropertyData property = getPropertyForIndex( index.getKeyId() );
if ( property != null )
{
return getPropertyValue( nodeManager, property );
}
}
return null;
}
}