/**
* Copyright (c) 2002-2012 "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.consistency.store.paging;
public class Cart implements PageReplacementStrategy, TemporalUtilityCounter
{
private final int capacity;
private int p = 0;
private int q = 0;
private int shortTermUtilityPageCount = 0;
private int longTermUtilityPageCount = 0;
private CachedPageList recencyCache = new CachedPageList();
private CachedPageList recencyHistory = new CachedPageList();
private CachedPageList frequencyCache = new CachedPageList();
private CachedPageList frequencyHistory = new CachedPageList();
public Cart( int capacity )
{
this.capacity = capacity;
}
@Override
public <PAYLOAD,PAGE extends Page<PAYLOAD>> PAYLOAD acquire( PAGE page, Storage<PAYLOAD,PAGE> storage )
throws PageLoadFailureException
{
if ( page.currentList == recencyCache || page.currentList == frequencyCache )
{
page.setReferenced();
page.hit();
return page.payload;
}
if ( recencyCache.size() + frequencyCache.size() == capacity )
{
// cache full
replace();
// history replace
if ( page.currentList != recencyHistory && page.currentList != frequencyHistory
&& recencyHistory.size() + frequencyHistory.size() == capacity + 1 )
{
if ( recencyHistory.size() > max( 0, q ) || frequencyHistory.size() == 0 )
{
recencyHistory.removeHead().setUtility( this, TemporalUtility.UNKNOWN );
}
else
{
frequencyHistory.removeHead().setUtility( this, TemporalUtility.UNKNOWN );
}
}
}
if ( page.currentList == recencyHistory )
{
p = min( p + max( 1, shortTermUtilityPageCount / recencyHistory.size() ), capacity );
page.clearReference().moveToTailOf( recencyCache ).setUtility( this, TemporalUtility.LONG_TERM );
}
else if ( page.currentList == frequencyHistory )
{
p = max( p - max( 1, longTermUtilityPageCount / frequencyHistory.size() ), 0 );
page.clearReference().moveToTailOf( recencyCache ).setUtility( this, TemporalUtility.LONG_TERM );
if ( frequencyCache.size() + frequencyHistory.size() + recencyCache.size() - shortTermUtilityPageCount >= capacity )
{
q = min( q + 1, 2 * capacity - recencyCache.size() );
}
}
else
{
page.moveToTailOf( recencyCache ).setUtility( this, TemporalUtility.SHORT_TERM );
}
return page.payload = storage.load( page );
}
@Override
public <PAYLOAD> void forceEvict( Page<PAYLOAD> page )
{
page.clearReference().setUtility( this, TemporalUtility.UNKNOWN ).moveToTailOf( null ).evict();
}
private void replace()
{
while ( frequencyCache.size() > 0 && frequencyCache.head.referenced )
{
frequencyCache.head.clearReference().moveToTailOf( recencyCache );
if ( frequencyCache.size() + frequencyHistory.size() + recencyHistory.size() - shortTermUtilityPageCount >= capacity )
{
q = min( q + 1, 2 * capacity - recencyCache.size() );
}
}
while ( recencyCache.size() > 0 && (recencyCache.head.utility == TemporalUtility.LONG_TERM || recencyCache.head.referenced) )
{
if ( recencyCache.head.referenced )
{
Page page = recencyCache.head.clearReference().moveToTailOf( recencyCache );
if ( recencyCache.size() > min( p + 1, recencyHistory.size() ) && page.utility == TemporalUtility.SHORT_TERM )
{
page.setUtility( this, TemporalUtility.LONG_TERM );
}
}
else
{
recencyCache.head.clearReference().moveToTailOf( frequencyCache );
q = max( q - 1, capacity - recencyCache.size() );
}
}
if ( recencyCache.size() >= max( 1, p ) )
{
recencyCache.head.evict();
recencyCache.head.moveToTailOf( recencyHistory ).setUtility( this, TemporalUtility.LONG_TERM );
}
else
{
frequencyCache.head.evict();
frequencyCache.head.moveToTailOf( frequencyHistory ).setUtility( this, TemporalUtility.SHORT_TERM );
}
}
private static int min( int i1, int i2 )
{
return i1 < i2 ? i1 : i2;
}
private static int max( int i1, int i2 )
{
return i1 > i2 ? i1 : i2;
}
@Override
public void increment( TemporalUtility utility )
{
switch ( utility )
{
case SHORT_TERM:
shortTermUtilityPageCount++;
break;
case LONG_TERM:
longTermUtilityPageCount++;
break;
}
}
@Override
public void decrement( TemporalUtility utility )
{
switch ( utility )
{
case SHORT_TERM:
shortTermUtilityPageCount--;
break;
case LONG_TERM:
longTermUtilityPageCount--;
break;
}
}
}