/*!
  @header FTPersistentSet
  @abstract Module of FT
 
  @availability OS X, GNUstep
  @copyright 2004, 2005, 2006 Free Software Foundation, Inc.
  
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA

  <pre>
  -------------------------------------------------------------------------
  Modification history

  30.03.06 ola     initial version
  23.08.06 ola     license changed
  -------------------------------------------------------------------------
  </pre>
*/

#include <FT/FTDefines.h>
#include <FTPersistentSetImpl.h>
#include <FT/FTLogging.h>
#include <FT/FTTransactionImpl.h>
#include <FT/FTSessionImpl.h>


@implementation FTPersistentSetImpl

- init {
  self = [super init];
  
  self->recordNrToObject = nil;
  self->currentInsertionChunk = nil;
  self->lock = [[NSLock alloc] init];
  self->maxEntriesPerChunk = PS_DEFAULT_MAX_ENTRIES_PER_CHUNCK;
  
  return self;
}


- createDBUsingDataFile: (NSString *) aFilename {
  BDBDatabaseConfig *dbconfig;
  
  if( [[NSFileManager defaultManager] 
    fileExistsAtPath: aFilename] ) {
    [[FTLogging coreLog]
      error: @"FTPersistentSetImpl::initAndCreateDBUsingDataFile: "\
        "File already exists: %@",aFilename ];
	[[[ECAlreadyExistsException alloc] 
	  initWithResourceInformation: 
        [NSString stringWithFormat:
          @"FTPersistentSetImpl::initAndCreateDBUsingDataFile: "\
          "File already exists: %@", aFilename ]]
      raise];
  }
  
  dbconfig = [self dbConfig];
  [dbconfig setAllowCreate: YES];
  
  self->recordNrToObject = [BDBDatabase
    initWithFilename: aFilename
    databaseName: nil
    databaseConfig: dbconfig];
  
  if( [[FTLogging coreLog] isDebugEnabled] ) {
    [[FTLogging coreLog] debug: 
      @"FTPersistentSetImpl::initAndCreateDBUsingDataFile: Database created!"];
  }
  
  return self;
}


- openDBUsingDataFile: (NSString *) aFilename {
  BDBDatabaseConfig *dbconfig;
  
  self = [self init];
  
  if( ![[NSFileManager defaultManager] 
    fileExistsAtPath: aFilename] ) {
    [[FTLogging coreLog]
      error: @"FTPersistentSetImpl::initAndSetupDBUsingDataFile: "\
        "File does not exist: %@",aFilename ];
	[[[ECIllegalStateException alloc] 
	  initWithIllegalStateInfo: 
        [NSString stringWithFormat:
          @"FTPersistentSetImpl::initAndSetupDBUsingDataFile: "\
          "File does not exist: %@", aFilename ]]
      raise];
  }
  
  dbconfig = [self dbConfig];
  [dbconfig setAllowCreate: NO];
  
  self->recordNrToObject = [BDBDatabase
    initWithFilename: aFilename
    databaseName: nil
    databaseConfig: dbconfig];
  
  if( [[FTLogging coreLog] isDebugEnabled] ) {
    [[FTLogging coreLog] debug: 
      @"FTPersistentSetImpl::initAndSetupDBUsingDataFile: Database opened!"];
  }
  
  return self;
}


- (void) closeDB {
  [self->recordNrToObject close];
  [self->recordNrToObject release];
  self->recordNrToObject = nil;

  [self->currentInsertionChunk release];
  self->currentInsertionChunk = nil;
}


- (void) dealloc {
  if( nil != self->recordNrToObject ) {
    [self->recordNrToObject close];
    [self->recordNrToObject release];
  }
  
  if( nil != self->currentInsertionChunk ) {
    [self->currentInsertionChunk store];
    [self->currentInsertionChunk release];
  }
  
  [self->lock release];
  
  [super dealloc];
}


- addObject: (id) anObject {
  if( [[FTLogging coreLog] isTraceEnabled] ) {
    [[FTLogging coreLog] trace: @"FTPersistentSetImpl::addObject:..." ];
  }
  [self validateDBIsOpen];
  
  if( (![anObject conformsToProtocol: @protocol(NSObject)])
    || (![anObject conformsToProtocol:@protocol(NSCoding)]) ) {
    [[FTLogging coreLog] critical: @"FTPersistentSetImpl::adObject: Given instance "\
      "does not conform to NSObject or NSCoding. Object=%@", anObject];
      
    [[[ECIllegalArgumentException alloc] 
      initWithArgumentInfo: @"FTPersistentSetImpl::adObject: Given instance "\
      "does not conform to NSObject or NSCoding."]
      raise];
  }
  
  [self->lock lock];
  
  NS_DURING
    if( nil == self->currentInsertionChunk ) {
      // no chunk available -> read/create one:
      [self newChunk];
    } else {   
      if( self->maxEntriesPerChunk <= [self->currentInsertionChunk count] ) {
        // no free space left in current chunk
        [self newChunk];
      }
    }
        
    if( nil == self->currentInsertionChunk ) {
      [[FTLogging coreLog]
        fatal: @"FTPersistentSetImpl::addObject: Unable to get free chunk!"];
      [[[ECIllegalStateException alloc]
        initWithIllegalStateInfo: 
          @"FTPersistentSetImpl::addObject: Unable to get free chunk!"]
        raise];
    }
    
    [self->currentInsertionChunk addObject: anObject];

    /* easy solution without transactions so far: */
    [_FTPersistentSetTransactionStep
      addTransactionStepForChunk: self->currentInsertionChunk
      forSet: self];
    
  NS_HANDLER
    [self->lock unlock];
    
    [localException raise];
  NS_ENDHANDLER
  
  [self->lock unlock];

  return self;
}


- (_FTPersistentSetChunk *) chunkAtRecordNumber: (unsigned) recNr {  
  return [_FTPersistentSetChunk 
    readFromDatabase: self->recordNrToObject
    atRecordNumber: recNr];
}


- (_FTPersistentSetChunk *) chunkContainingObject: (id) anObject {
  id <ECIterator> chunkIterator;
  
  chunkIterator = [self chunkIterator];
  
  while( [chunkIterator hasNext] ) {
    _FTPersistentSetChunk *currentChunk = [chunkIterator next];
    if( [currentChunk containsObject: anObject] ) {
      return currentChunk;
    }
  }
  
  return nil;
}

- (BOOL) containsObject: (id) anObject {
  id chunk;
  [self validateDBIsOpen];
  
  [self->lock lock];
  NS_DURING
    chunk = [self chunkContainingObject: anObject];
  NS_HANDLER
    [self->lock unlock];
    [localException raise];
  NS_ENDHANDLER
  
  [self->lock unlock];
  
  return nil != chunk;
}


- (id <ECIterator>) chunkIterator {
  return [[_FTPersistentSetChunkIterator alloc] 
    initWithPersistentSet: self];
}


- (unsigned) count {
  unsigned nr_elements = 0;
  
  [self validateDBIsOpen];
  
  [self->lock lock];
  NS_DURING
    id <ECIterator> iterator = [self chunkIterator];
  
    while( [iterator hasNext] ) {
      _FTPersistentSetChunk *current = [iterator next];
      nr_elements += [current count];
    }
  NS_HANDLER
    [self->lock unlock];
    [localException raise];
  NS_ENDHANDLER
  
  [self->lock unlock];
  return nr_elements;
}


- (BDBDatabaseConfig *) dbConfig {
  BDBDatabaseConfig * dbConfig = [[BDBDatabaseConfig alloc] init];

  [dbConfig setDatabaseType: BDB_BTREE];
  [dbConfig setBTreeRecordNumbering: YES];
  [dbConfig setAllowDuplicates: NO];
  
  return dbConfig;
}


- (id <ECIterator>) iterator {
  id toReturn;
  
  [self validateDBIsOpen];
  
  [self->lock lock];
  NS_DURING
    toReturn = [[_FTPersistentSetIterator alloc]
      initWithPersistentSet: self];
  NS_HANDLER
    [self->lock unlock];
    [localException raise];
  NS_ENDHANDLER
  
  [self->lock unlock];
  
  return toReturn;
}



- removeObject: (id) anObject {
  _FTPersistentSetChunk *chunk;
  
  [self validateDBIsOpen];
  
  [self->lock lock];
  NS_DURING
    chunk = [self chunkContainingObject: anObject];
  
    if( nil != chunk ) {
      [chunk removeObject: anObject];
      [_FTPersistentSetTransactionStep 
        addTransactionStepForChunk: chunk
        forSet: self];
    }
  NS_HANDLER
    [self->lock unlock];
    [localException raise];
  NS_ENDHANDLER
  
  [self->lock unlock];
  
  return self;
}


- newChunk {
  unsigned current_rec_nr, highest_rec_nr;
  BOOL found;
  unsigned start_rec_nr;
  _FTPersistentSetChunk *foundChunk;
  
  // examine currently used bdb record number:
  if( nil != self->currentInsertionChunk ) {
    [self->currentInsertionChunk store];
    current_rec_nr = [self->currentInsertionChunk recordNumber];
    [self->currentInsertionChunk release];    
    self->currentInsertionChunk = nil;
  } else {
    current_rec_nr = 1;
  }
  
  // now find an available record number. Todo: the approach implemented here
  // could be a bit better:
 
 EC_AUTORELEASEPOOL_BEGIN
 NS_DURING
   found = NO;
   highest_rec_nr = current_rec_nr;
 
   start_rec_nr = current_rec_nr;
 
    while( !found ) {
      if( [[FTLogging coreLog] isDebugEnabled] ) {
        [[FTLogging coreLog] 
           debug: @"FTPersistentSetImpl::newChunk: Looking for free chunk at %u...",
           current_rec_nr];
      }
    
      foundChunk = [self chunkAtRecordNumber: current_rec_nr];
      
      if( nil != foundChunk ) {
        // does this chunk have free space?     
        if( self->maxEntriesPerChunk < [foundChunk count] ) {
          found = YES;
          self->currentInsertionChunk = [foundChunk retain];
          break;
        } else {
          /* current record has also no availabe space: try next one */
          current_rec_nr++;
        }
      } else {
        /* number of avaiable records exceeded. Start with the beginning: */
        current_rec_nr = 1;
      }

      if( current_rec_nr > highest_rec_nr ) {
        highest_rec_nr = current_rec_nr;
      }
    
      /* have we reached the record number from where we have started the search?
       * If YES, then there is no free chunk and we have to add a new one.
       */
      if( start_rec_nr == current_rec_nr ) {
        /* create new chunk: */
        self->currentInsertionChunk = [_FTPersistentSetChunk 
          createForDatabase: self->recordNrToObject
          atRecordNumber: highest_rec_nr];
          
        found = YES;
        break;
      }
    }
  NS_HANDLER
    [[FTLogging coreLog]
      fatal: @"FTPersistentSetImpl::newChunk: Got exception while examining new "\
        "chunk. Exception = :%@", localException];
    self->currentInsertionChunk = nil;
    [localException raise];
  NS_ENDHANDLER

  EC_AUTORELEASEPOOL_END
  return self;
}


- (void) validateDBIsOpen {
  if( nil == self->recordNrToObject ) {
    [[FTLogging coreLog]
      error: @"FTPersistentSetImpl::validateDBIsOpen: DB is not mounted!"];
      
    [[[ECIllegalStateException alloc]
      initWithIllegalStateInfo: 
        @"FTPersistentSetImpl::validateDBIsOpen: DB is not mounted"]
      raise];
  }
}
@end


/************************************************** _FTPersistentSetChunk */
@implementation _FTPersistentSetChunk

+ (_FTPersistentSetChunk *) createForDatabase: (BDBDatabase *) aDatabase
  atRecordNumber: (unsigned) a_record_nr {
  _FTPersistentSetChunk *result;
  
  result = [[_FTPersistentSetChunk alloc] 
    initForDatabase: aDatabase
    atRecordNumber: a_record_nr
    usingObjects: nil];
    
  result->isModified = YES;
  [result store];
  
  return result;
}
  

+ (_FTPersistentSetChunk *) readFromDatabase: (BDBDatabase *) aDatabase 
  atRecordNumber: (unsigned) record_nr {
  _FTPersistentSetChunk *result = nil;
  
  /* try to access daatabase record first: */
  NS_DURING
    BDBDatabaseRecordNumber *recNo;
    BDBDatabaseEntry *value;
    BDBOperationStatus opStatus;
    
    recNo = [[[BDBDatabaseRecordNumber alloc]
      initWithEntryNumber: record_nr]
      autorelease];
    value = [[[BDBDatabaseEntry alloc] init] 
      autorelease];
      
    opStatus = [aDatabase getEntryWithTransaction: nil
      recordNumber: recNo
      data: value];
      
    if( BDB_STATUS_SUCCESS == opStatus ) {
      result = [[_FTPersistentSetChunk alloc]
        initForDatabase: aDatabase
        atRecordNumber: record_nr
        usingObjects: [value object]];
    } else { 
      if( BDB_STATUS_NOTFOUND != opStatus ) {
        /* an unexpected error occured */
        [[[ECIllegalStateException alloc]
          initWithIllegalStateInfo:
            [NSString stringWithFormat: 
              @"_FTPersistentSetChunk::initForDatabase: Unable to read chunk"\
              " from database record nr=%u", record_nr]]
          raise];
      }
    }
  NS_HANDLER
    [[FTLogging coreLog] 
      error: @"_FTPersistentSetChunk::readFromDatabase: Error while reading"\
        " chunk at record %u from database. Exception: %@",
        record_nr, localException];
  NS_ENDHANDLER

  return result;
}


- initForDatabase: (BDBDatabase *) aDatabase
  atRecordNumber: (unsigned) a_record_nr
  usingObjects: (NSArray *) givenObjects {
  
  self = [super init];
  
  self->database = [aDatabase retain];
  self->bdb_record_nr = a_record_nr;
  self->chunkInTransaction = NO;
  self->lock = [[NSLock alloc] init];
  
  if( nil != givenObjects ) {
    self->objects = [givenObjects retain];
  } else {
    self->objects = [[NSMutableArray alloc] init];
  }
  
  return self;
}


- addObject: (id) anObject {
  if( [[FTLogging coreLog] isTraceEnabled] ) {
    [[FTLogging coreLog]
      trace: @"_FTPersistentSetChunk::addObject: Adding object \"%@\" to "\
             "chunk \"%@\"", anObject, self];
  }
  [self->objects addObject: anObject];
  self->isModified = YES;
  
  return self;
}


- (BOOL) containsObject: (id) anObject {
  return [self->objects containsObject: anObject];
}


- (unsigned) count {
  return [self->objects count];
}


- (BOOL) isChunkInTransaction {
  return self->chunkInTransaction;
}


- (NSArray *) objects {
  return [NSArray arrayWithArray: self->objects];
}


- loadChunk {
  EC_AUTORELEASEPOOL_BEGIN
  
  NS_DURING
    BDBDatabaseRecordNumber *recNo;
    BDBDatabaseEntry *value;
    BDBOperationStatus opStatus;
    
    recNo = [[[BDBDatabaseRecordNumber alloc]
      initWithEntryNumber: self->bdb_record_nr]
      autorelease];
    value = [[[BDBDatabaseEntry alloc] init] 
      autorelease];
      
    opStatus = [database getEntryWithTransaction: nil
      recordNumber: recNo
      data: value];
      
    if( BDB_STATUS_SUCCESS != opStatus ) {
      [[[ECIllegalStateException alloc]
        initWithIllegalStateInfo:
          [NSString stringWithFormat: 
            @"_FTPersistentSetChunk::initForDatabase: Unable to read chunk"\
            " from database record nr=%u", self->bdb_record_nr]]
        raise];
    }
    
    if( nil != value ) {
      self->objects = [[value object] retain];
    } else {
      self->objects = [[NSMutableArray alloc] init];
    }
  NS_HANDLER
    [[FTLogging coreLog]
       fatal: @"_FTPersistentSetChunk::initForDatabase: Unable to read chunk"\
       " from database record nr=%u", self->bdb_record_nr];
       
    [localException raise];
  NS_ENDHANDLER
  
  EC_AUTORELEASEPOOL_END
  
  return self;
}


- (void) dealloc {
  [self->objects release];
  [self->database release];
  [self->lock release];
  
  [super dealloc];
}


- (unsigned) recordNumber {
  return self->bdb_record_nr;
}


- (void) remove {
  BDBDatabaseRecordNumber *recNo;
  BDBOperationStatus opStatus= BDB_STATUS_UNKNOWN;
  recNo = [[[BDBDatabaseRecordNumber alloc]
    initWithEntryNumber: self->bdb_record_nr]
    autorelease];
    
  NS_DURING    
    opStatus = [self->database deleteEntryWithTransaction: nil
      key: recNo];
      
    if( BDB_STATUS_SUCCESS != opStatus ) {
      [[[ECIllegalStateException alloc]
        initWithIllegalStateInfo:
          [NSString stringWithFormat: 
            @"_FTPersistentSetChunk::remove: Unable to remove chunk"\
            " from database at record nr=%u", self->bdb_record_nr]]
        raise];
    }
  NS_HANDLER
    [[FTLogging coreLog]
       fatal: @"_FTPersistentSetChunk::remove: Unable to remove chunk"\
            " from database at record nr=%u. BDB error code=%u", 
            self->bdb_record_nr, opStatus ];
       
    [localException raise];
  NS_ENDHANDLER
  
  self->isModified = NO;
}


- removeObject: (id) anObject {
  [self->objects removeObject: anObject];
  self->isModified = YES;
  
  return self;
}


- setChunkInTransaction: (BOOL) itIs {
  [self->lock lock];
  if( NO == self->chunkInTransaction ) {
    self->chunkInTransaction = YES;
  }
  [self->lock unlock];
  return self;
}


- store {
  BDBDatabaseRecordNumber *recNo;
  BDBDatabaseEntry *value;
  BDBOperationStatus opStatus;

  if( !self->isModified ) {
    return self;
  }
  
  
  recNo = [[[BDBDatabaseRecordNumber alloc]
    initWithEntryNumber: self->bdb_record_nr]
    autorelease];
    
  value = [[[BDBDatabaseEntry alloc] 
    initWithObject: self->objects]
    autorelease];
    
  NS_DURING    
    if( [[FTLogging coreLog] isDebugEnabled] ) {
      [[FTLogging coreLog] 
        debug: @"_FTPersistentSetChunk::store: Writing %u number of entries",
          [self->objects count]];
    }
    opStatus = [self->database putEntryWithTransaction: nil
      key: recNo
      value: value];
      
    if( BDB_STATUS_SUCCESS != opStatus ) {
      [[[ECIllegalStateException alloc]
        initWithIllegalStateInfo:
          [NSString stringWithFormat: 
            @"_FTPersistentSetChunk::store: Unable to write chunk"\
            " to database at record nr=%u", self->bdb_record_nr]]
        raise];
    }
  NS_HANDLER
    [[FTLogging coreLog]
       fatal: @"_FTPersistentSetChunk::store: Unable to write chunk"\
            " to database at record nr=%u", self->bdb_record_nr];
       
    [localException raise];
  NS_ENDHANDLER
  
  self->isModified = NO;
  return self;
}


- (void) update {
  if( 0 < [self->objects count] ) {
    [self store];
  } else {
    [self remove];
  }
}
@end



/* ----------------------------------------- _FTPersistentSetChunkIterator */
@implementation _FTPersistentSetChunkIterator

- initWithPersistentSet: (FTPersistentSetImpl *) aPersistentSet {
  self = [super init];
  self->current_rec_number = 1;
  self->persistentSet = [aPersistentSet retain];
  self->fetchedBefore = NO;
  self->fetchedChunk = nil;
  
  return self;
}


- (void) dealloc {
  [self->persistentSet release];
  [self->fetchedChunk release];
  
  [super dealloc];
}


- (BOOL) hasNext {
  if( !self->fetchedBefore ) {
    [self fetchChunk];
    self->fetchedBefore = YES;
  }
  
  return (nil != self->fetchedChunk);
}


- next {
  if( !self->fetchedBefore ) {
    [self fetchChunk];
    self->fetchedBefore = YES;
  }
  
  self->fetchedBefore = NO;
  
  if( nil != self->fetchedChunk ) {
    self->current_rec_number++;
  }
  
  return self->fetchedChunk;
}


- fetchChunk {
  if( nil != self->fetchedChunk ) {
    [self->fetchedChunk release];
    self->fetchedChunk = nil;
  }
  
  NS_DURING
    self->fetchedChunk = [[self->persistentSet 
      chunkAtRecordNumber: self->current_rec_number] retain];
  NS_HANDLER
   [[FTLogging coreLog]
     fatal: @"_FTPersistentSetChunkIterator::fetchChunk: Unable to read "\
       "chunk from database record nr=%u", self->current_rec_number];
   [localException raise];
 NS_ENDHANDLER
 
 return self;
}
@end


/* ----------------------------------------- _FTPersistentSetIterator */
@implementation _FTPersistentSetIterator

- initWithPersistentSet: (FTPersistentSetImpl *) aPersistentSet {
  self = [super init];
  
  self->chunkIterator = [[aPersistentSet chunkIterator] retain];
  self->chunkElements = nil;
  self->elementFetched = NO;
  self->nextObject = nil;
  
  return self;
}


- (void) dealloc {
  [self->chunkIterator release];
  [self->chunkElements release];
  [self->nextObject release];
  
  [super dealloc];
}


- fetchNextChunkElements {
  _FTPersistentSetChunk *nextChunk = [self->chunkIterator next];
  
  if( nil == nextChunk ) {
    self->chunkElements = nil;
    self->nextObject = nil;
    
    return self;
  }
  
  self->chunkElements = [[[nextChunk objects] objectEnumerator] retain];
  
  return self;
}


- (BOOL) hasNext {
  return nil != [self currentElement];
}


- currentElement {
  if( self->elementFetched ) {
    /* current element has already been loaded */
    return self->nextObject;
  }
  
  if( nil != self->chunkElements ) {
    /* read current element as next element of the current enumeration */
    self->nextObject = [[self->chunkElements nextObject] retain];
    
    if( nil != self->nextObject ) {
      self->elementFetched = YES;
      return self->nextObject;
    } else {
      /* no more elements in current enumeration --> prepare reading 
       * enumeration of next chunk
       */
     [self->chunkElements release];
      self->chunkElements = nil;
    }
  }
  
  [self fetchNextChunkElements];
  self->elementFetched = YES;
    
  if( nil != self->chunkElements ) {
    self->nextObject = [[self->chunkElements nextObject] retain];
  } else {
    self->nextObject = nil;
  }
  
  return self->nextObject;
}


- next {
  id toReturn;

  toReturn = [[self currentElement] retain];
  
  if( nil != toReturn ) {
    /** remove object marked as "current" from internal state since it is not
     * the "current" object any more after this call
     */
    [self->nextObject release];
    self->nextObject = nil;
    self->elementFetched = NO;
  }
  
  return toReturn;
}

@end


/* ------------------------------------ _FTPersistentSetTransactionStep */
static NSLock *_lockPersistentSetTransactionStep = nil;

/* refers to the chunk instance which has to be updated */
#define _TX_CONTEXT_CHUNK @"__chunkRef"


@implementation _FTPersistentSetTransactionStep

+ (void) initialze {
  if( self == [_FTPersistentSetTransactionStep class] ) {
    _lockPersistentSetTransactionStep = [[NSLock alloc] init];
  }
}


+ (void) addTransactionStepForChunk: (_FTPersistentSetChunk *) aChunk 
  forSet: (FTPersistentSetImpl *) aSet {
  [_lockPersistentSetTransactionStep lock];
  
  NS_DURING
    /* create an identifier unique to the given chunk: */
    NSString *transactionStepId
      = [NSString stringWithFormat: @"%u/%u",
      [aSet hash], [aChunk hash]];
    FTTransactionImpl *currentTransaction;
    FTTransactionContext *context;
    id <FTTransactionStep> transactionStep;
    
    /* if a transaction for this chunk does not exist in the current
     * transaction then add it...: */
    currentTransaction = [[FTSessionImpl currentSession] currentTransaction];
    transactionStep = [currentTransaction 
      transactionStepForKey: transactionStepId];
    
    if( nil == transactionStep ) {
      /* transaction does not exist for the given chunk. */
      context = [currentTransaction createContext];
      transactionStep = [[_FTPersistentSetTransactionStep alloc] 
        initWithChunk: aChunk forContext: context ];

      [currentTransaction 
        addTransactionStep: transactionStep
        withContext: context
        identifiedByKey: transactionStepId];
        
      [context release];
      [transactionStep release];
    }

  NS_HANDLER
    [_lockPersistentSetTransactionStep unlock];
    [localException raise];
  NS_ENDHANDLER
  
  [_lockPersistentSetTransactionStep unlock];
}


- initWithChunk: (_FTPersistentSetChunk *) chunk 
  forContext: (FTTransactionContext *) aContext {
  self = [super init];

  [aContext addObject: chunk forKey: _TX_CONTEXT_CHUNK];
  
  return self;
}


- (BOOL) performAction: (FTTransactionContext *) transactionContext {
  _FTPersistentSetChunk *chunk 
    = (_FTPersistentSetChunk *) [transactionContext 
      objectForKey: _TX_CONTEXT_CHUNK];

  if( [[FTLogging coreLog] isDebugEnabled] ) {
    [[FTLogging coreLog] debug : @"_FTPersistentSetTransactionStep::"\
      "performAction: Using chunk = %@", chunk];
  }
  
  if( nil == chunk ) {
    [[[ECIllegalStateException alloc] 
      initWithIllegalStateInfo: @"_FTPersistentSetTransactionStep::"\
        "performAction: No chunk in current context!"] raise ];
  }
  
  [chunk update];
  
  return YES;
}


- (BOOL) undoAction: (FTTransactionContext *) transactionContext {
  [[[ECNotImplementedException alloc] 
    initWithOperationInformation: 
      @"_FTPersistentSetTransactionStep::undoAction" ]
    raise];

  return NO;
}
@end

