/*
 * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */
 
 
#include "EntryCache.h"
 
#include <new>
 
 
static const int32 kEntriesPerGeneration = 1024;
 
static const int32 kEntryNotInArray = -1;
static const int32 kEntryRemoved = -2;
 
 
// #pragma mark - EntryCacheGeneration
 
 
EntryCacheGeneration::EntryCacheGeneration()
	:
	next_index(0),
	entries(NULL)
{
}
 
 
EntryCacheGeneration::~EntryCacheGeneration()
{
	delete[] entries;
}
 
 
status_t
EntryCacheGeneration::Init()
{
	entries = new(std::nothrow) EntryCacheEntry*[kEntriesPerGeneration];
	if (entries == NULL)
		return B_NO_MEMORY;
 
	memset(entries, 0, sizeof(EntryCacheEntry*) * kEntriesPerGeneration);
 
	return B_OK;
}
 
 
// #pragma mark - EntryCache
 
 
EntryCache::EntryCache()
	:
	fCurrentGeneration(0)
{
	rw_lock_init(&fLock, "entry cache");
 
	new(&fEntries) EntryTable;
}
 
 
EntryCache::~EntryCache()
{
	// delete entries
	EntryCacheEntry* entry = fEntries.Clear(true);
	while (entry != NULL) {
		EntryCacheEntry* next = entry->hash_link;
		free(entry);
		entry = next;
	}
 
	rw_lock_destroy(&fLock);
}
 
 
status_t
EntryCache::Init()
{
	status_t error = fEntries.Init();
	if (error != B_OK)
		return error;
 
	for (int32 i = 0; i < kGenerationCount; i++) {
		error = fGenerations[i].Init();
		if (error != B_OK)
			return error;
	}
 
	return B_OK;
}
 
 
status_t
EntryCache::Add(ino_t dirID, const char* name, ino_t nodeID, bool missing)
{
	EntryCacheKey key(dirID, name);
 
	WriteLocker _(fLock);
 
	EntryCacheEntry* entry = fEntries.Lookup(key);
	if (entry != NULL) {
		entry->node_id = nodeID;
		entry->missing = missing;
		if (entry->generation != fCurrentGeneration) {
			if (entry->index >= 0) {
				fGenerations[entry->generation].entries[entry->index] = NULL;
				_AddEntryToCurrentGeneration(entry);
			}
		}
		return B_OK;
	}
 
	entry = (EntryCacheEntry*)malloc(sizeof(EntryCacheEntry) + strlen(name));
	if (entry == NULL)
		return B_NO_MEMORY;
 
	entry->node_id = nodeID;
	entry->dir_id = dirID;
	entry->missing = missing;
	entry->generation = fCurrentGeneration;
	entry->index = kEntryNotInArray;
	strcpy(entry->name, name);
 
	fEntries.Insert(entry);
 
	_AddEntryToCurrentGeneration(entry);
 
	return B_OK;
}
 
 
status_t
EntryCache::Remove(ino_t dirID, const char* name)
{
	EntryCacheKey key(dirID, name);
 
	WriteLocker writeLocker(fLock);
 
	EntryCacheEntry* entry = fEntries.Lookup(key);
	if (entry == NULL)
		return B_ENTRY_NOT_FOUND;
 
	fEntries.Remove(entry);
 
	if (entry->index >= 0) {
		// remove the entry from its generation and delete it
		fGenerations[entry->generation].entries[entry->index] = NULL;
		free(entry);
	} else {
		// We can't free it, since another thread is about to try to move it
		// to another generation. We mark it removed and the other thread will
		// take care of deleting it.
		entry->index = kEntryRemoved;
	}
 
	return B_OK;
}
 
 
bool
EntryCache::Lookup(ino_t dirID, const char* name, ino_t& _nodeID,
	bool& _missing)
{
	EntryCacheKey key(dirID, name);
 
	ReadLocker readLocker(fLock);
 
	EntryCacheEntry* entry = fEntries.Lookup(key);
	if (entry == NULL)
		return false;
 
	int32 oldGeneration = atomic_get_and_set(&entry->generation,
			fCurrentGeneration);
	if (oldGeneration == fCurrentGeneration || entry->index < 0) {
		// The entry is already in the current generation or is being moved to
		// it by another thread.
		_nodeID = entry->node_id;
		_missing = entry->missing;
		return true;
	}
 
	// remove from old generation array
	fGenerations[oldGeneration].entries[entry->index] = NULL;
	entry->index = kEntryNotInArray;
 
	// add to the current generation
	int32 index = atomic_add(&fGenerations[oldGeneration].next_index, 1);
	if (index < kEntriesPerGeneration) {
		fGenerations[fCurrentGeneration].entries[index] = entry;
		entry->index = index;
		_nodeID = entry->node_id;
		_missing = entry->missing;
		return true;
	}
 
	// The current generation is full, so we probably need to clear the oldest
	// one to make room. We need the write lock for that.
	readLocker.Unlock();
	WriteLocker writeLocker(fLock);
 
	if (entry->index == kEntryRemoved) {
		// the entry has been removed in the meantime
		free(entry);
		return false;
	}
 
	_AddEntryToCurrentGeneration(entry);
 
	_nodeID = entry->node_id;
	_missing = entry->missing;
	return true;
}
 
 
const char*
EntryCache::DebugReverseLookup(ino_t nodeID, ino_t& _dirID)
{
	for (EntryTable::Iterator it = fEntries.GetIterator();
			EntryCacheEntry* entry = it.Next();) {
		if (nodeID == entry->node_id && strcmp(entry->name, ".") != 0
				&& strcmp(entry->name, "..") != 0) {
			_dirID = entry->dir_id;
			return entry->name;
		}
	}
 
	return NULL;
}
 
 
void
EntryCache::_AddEntryToCurrentGeneration(EntryCacheEntry* entry)
{
	// the generation might not be full yet
	int32 index = fGenerations[fCurrentGeneration].next_index++;
	if (index < kEntriesPerGeneration) {
		fGenerations[fCurrentGeneration].entries[index] = entry;
		entry->generation = fCurrentGeneration;
		entry->index = index;
		return;
	}
 
	// we have to clear the oldest generation
	int32 newGeneration = (fCurrentGeneration + 1) % kGenerationCount;
	for (int32 i = 0; i < kEntriesPerGeneration; i++) {
		EntryCacheEntry* otherEntry = fGenerations[newGeneration].entries[i];
		if (otherEntry == NULL)
			continue;
 
		fGenerations[newGeneration].entries[i] = NULL;
		fEntries.Remove(otherEntry);
		free(otherEntry);
	}
 
	// set the new generation and add the entry
	fCurrentGeneration = newGeneration;
	fGenerations[newGeneration].next_index = 1;
	fGenerations[newGeneration].entries[0] = entry;
	entry->generation = newGeneration;
	entry->index = 0;
}

V547 Expression 'entry->index == kEntryRemoved' is always false.