// // $Id$ // // SPDataStorage.m // sequel-pro // // Created by Rowan Beentje on January 1, 2009. // Copyright (c) 2009 Rowan Beentje. All rights reserved. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // More info at #import "SPDataStorage.h" @interface SPDataStorage (PrivateAPI) - (void) _ensureCapacityForAdditionalRowCount:(NSUInteger)numExtraRows; - (void) _increaseCapacity; @end @implementation SPDataStorage static inline void SPDataStorageEnsureCapacityForAdditionalRowCount(SPDataStorage* self, NSUInteger numExtraRows) { typedef void (*SPDSEnsureCapacityMethodPtr)(SPDataStorage*, SEL, NSUInteger); static SPDSEnsureCapacityMethodPtr SPDSEnsureCapacity; if (!SPDSEnsureCapacity) SPDSEnsureCapacity = (SPDSEnsureCapacityMethodPtr)[self methodForSelector:@selector(_ensureCapacityForAdditionalRowCount:)]; SPDSEnsureCapacity(self, @selector(_ensureCapacityForAdditionalRowCount:), numExtraRows); } #pragma mark - #pragma mark Retrieving rows and cells /** * Return a mutable array containing the data for a specified row. */ - (NSMutableArray *) rowContentsAtIndex:(NSUInteger)index { // Throw an exception if the index is out of bounds if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)index, (unsigned long long)numRows]; // Construct the NSMutableArray NSMutableArray *rowArray = [NSMutableArray arrayWithCapacity:numColumns]; id *row = dataStorage[index]; NSUInteger i; for (i = 0; i < numColumns; i++) { CFArrayAppendValue((CFMutableArrayRef)rowArray, row[i]); } return rowArray; } /** * Return the data at a specified row and column index. */ - (id) cellDataAtRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex { // Throw an exception if the row or column index is out of bounds if (rowIndex >= numRows || columnIndex >= numColumns) [NSException raise:NSRangeException format:@"Requested storage index (row %llu, col %llu) beyond bounds (%llu, %llu)", (unsigned long long)rowIndex, (unsigned long long)columnIndex, (unsigned long long)numRows, (unsigned long long)numColumns]; // Return the content return dataStorage[rowIndex][columnIndex]; } #pragma mark - #pragma mark Retrieving rows via NSFastEnumeration /** * Implementation of the NSFastEnumeration protocol. * Note that this currently doesn't implement mutation guards. */ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len { // If the start index is out of bounds, return 0 to indicate end of results if (state->state >= numRows) return 0; // Determine how many objects to return - 128, len, or all items remaining NSUInteger itemsToReturn = 128; if (len < 128) itemsToReturn = len; if (numRows - state->state < itemsToReturn) { itemsToReturn = numRows - state->state; } // Construct the arrays to return NSUInteger i, j; NSMutableArray *rowArray; id *row; for (i = 0; i < itemsToReturn; i++) { row = dataStorage[state->state + i]; rowArray = [NSMutableArray arrayWithCapacity:numColumns]; for (j = 0; j < numColumns; j++) { CFArrayAppendValue((CFMutableArrayRef)rowArray, row[j]); } stackbuf[i] = rowArray; } state->state += itemsToReturn; state->itemsPtr = stackbuf; state->mutationsPtr = (unsigned long *)&numRows; return itemsToReturn; } #pragma mark - #pragma mark Adding and amending rows and cells /** * Add a new row to the end of the storage array, supplying an NSArray * of objects. Note that the supplied objects are retained as a reference * rather than copied. */ - (void) addRowWithContents:(NSArray *)row { // Ensure that sufficient capacity is available SPDataStorageEnsureCapacityForAdditionalRowCount(self, 1); // Add an empty row array to the data store id *newRow = (id *)malloc(columnPointerByteSize); dataStorage[numRows] = newRow; numRows++; // Copy over references to the array contents, and retain the objects NSUInteger cellsCopied = 0; for (id cellData in row) { if (cellData) newRow[cellsCopied] = (id)CFRetain(cellData); else newRow[cellsCopied] = nil; if (++cellsCopied == numColumns) break; } // If an array shorter than the row width was added, pad with nils if (cellsCopied < numColumns) { for ( ; cellsCopied <= numColumns; cellsCopied++) newRow[cellsCopied] = nil; } } /** * Insert a new row into the storage array at a specified point, pushing * all later rows the next index. Note that the supplied objects within the * array are retained as a reference rather than copied. */ - (void) insertRowContents:(NSArray *)row atIndex:(NSUInteger)index { // Throw an exception if the index is out of bounds if (index > numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)index, (unsigned long long)numRows]; // If "inserting" at the end of the array just add a row if (index == numRows) return SPDataStorageAddRow(self, row); // Ensure that sufficient capacity is available to hold all the rows SPDataStorageEnsureCapacityForAdditionalRowCount(self, 1); // Renumber the specified index, and all subsequent indices, to create a gap for (NSUInteger j = numRows - 1; j >= index; j--) { dataStorage[j + 1] = dataStorage[j]; } // Add a new instantiated row array to the data store at the specified point id *newRow = (id *)malloc(columnPointerByteSize); dataStorage[index] = newRow; numRows++; // Copy over references to the array contents, and retain the objects NSUInteger cellsCopied = 0; for (id cellData in row) { if (cellData) newRow[cellsCopied] = (id)CFRetain(cellData); else newRow[cellsCopied] = nil; if (++cellsCopied == numColumns) break; } // If an array shorter than the row width was inserted, pad with nils if (cellsCopied < numColumns) { for ( ; cellsCopied <= numColumns; cellsCopied++) newRow[cellsCopied] = nil; } } /** * Replace a row with contents of the supplied NSArray. */ - (void) replaceRowAtIndex:(NSUInteger)index withRowContents:(NSArray *)row { NSUInteger cellsProcessed = 0; // Throw an exception if the index is out of bounds if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)index, (unsigned long long)numRows]; id *storageRow = dataStorage[index]; // Iterate through the row replacing the objects for (id cellData in row) { if (storageRow[cellsProcessed]) CFRelease(storageRow[cellsProcessed]); if (cellData) storageRow[cellsProcessed] = (id)CFRetain(cellData); else storageRow[cellsProcessed] = nil; if (++cellsProcessed == numColumns) break; } // Ensure all cells are correctly updated if an array shorter than the row width was supplied if (cellsProcessed < numColumns) { for ( ; cellsProcessed <= numColumns; cellsProcessed++) { if (storageRow[cellsProcessed]) CFRelease(storageRow[cellsProcessed]); storageRow[cellsProcessed] = nil; } } } /** * Replace the contents of a single cell with a supplied object. */ - (void) replaceObjectInRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex withObject:(id)object { // Throw an exception of either index is out of bounds if (rowIndex >= numRows || columnIndex >= numColumns) [NSException raise:NSRangeException format:@"Requested storage index (row %llu, col %llu) beyond bounds (%llu, %llu)", (unsigned long long)rowIndex, (unsigned long long)columnIndex, (unsigned long long)numRows, (unsigned long long)numColumns]; // Release the old object and retain the new one if (dataStorage[rowIndex][columnIndex]) CFRelease(dataStorage[rowIndex][columnIndex]); if (object) dataStorage[rowIndex][columnIndex] = (id)CFRetain(object); else dataStorage[rowIndex][columnIndex] = nil; } /** * Remove a row, renumbering all elements beyond index. */ - (void) removeRowAtIndex:(NSUInteger)index { // Throw an exception if the index is out of bounds if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)index, (unsigned long long)numRows]; // Free the row NSUInteger j = numColumns; id *row = dataStorage[index]; while (j > 0) { if (row[--j]) CFRelease(row[j]); } free(row); numRows--; // Renumber all subsequent indices to fill the gap for (j = index; j < numRows; j++) { dataStorage[j] = dataStorage[j + 1]; } dataStorage[numRows] = NULL; } /** * Remove all rows in the specified range, renumbering all elements * beyond the end of the range. */ - (void) removeRowsInRange:(NSRange)rangeToRemove { // Throw an exception if the range is out of bounds if (rangeToRemove.location + rangeToRemove.length > numRows) [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)(rangeToRemove.location + rangeToRemove.length), (unsigned long long)numRows]; // Free rows in the range NSUInteger i, j = numColumns; id *row; for (i = rangeToRemove.location; i < rangeToRemove.location + rangeToRemove.length; i++) { row = dataStorage[i]; while (j > 0) { if (row[--j]) CFRelease(row[j]); } free(row); } numRows -= rangeToRemove.length; // Renumber all subsequent indices to fill the gap for (i = rangeToRemove.location + rangeToRemove.length - 1; i < numRows; i++) { dataStorage[i] = dataStorage[i + rangeToRemove.length]; } for (i = numRows; i < numRows + rangeToRemove.length; i++) { dataStorage[i] = NULL; } } /** * Remove all rows from the array, and free their associated memory. */ - (void) removeAllRows { NSUInteger j; id *row; // Free all the data while (numRows > 0) { row = dataStorage[--numRows]; j = numColumns; while (j > 0) { if (row[--j]) CFRelease(row[j]); } free(row); } numRows = 0; } #pragma mark - #pragma mark Basic information /** * Returns the number of rows currently held in data storage. */ - (NSUInteger) count { return numRows; } /** * Set the number of columns represented by the data storage. */ - (void) setColumnCount:(NSUInteger)columnCount { columnPointerByteSize = columnCount * sizeof(id); // If there are rows present in the storage, and the number of // columns has changed, amend the existing rows to match. if (columnCount != numColumns && numRows) { NSUInteger i = numRows, j; id *row; // If the new column count is higher than the old count, iterate through the existing rows // and pad with nils if (columnCount > numColumns) { while (i-- > 0) { dataStorage[i] = (id *)realloc(dataStorage[i], columnPointerByteSize); j = numColumns; while (j < columnCount) { dataStorage[i][j++] = nil; } } // If the new column count is lower than the old count, iterate through the existing rows // freeing any extra objects } else { while (i > 0) { row = dataStorage[--i]; j = numColumns; while (j > columnCount) { if (row[--j]) CFRelease(row[j]); } dataStorage[i] = (id *)realloc(row, columnPointerByteSize); } } } // Update the column count numColumns = columnCount; } /** * Return the number of columns represented by the data storage. */ - (NSUInteger) columnCount { return numColumns; } /** * Setup and teardown */ #pragma mark - - (id) init { if ((self = [super init])) { numColumns = 0; columnPointerByteSize = 0; numRows = 0; // Initialise the array, initially with space for 100 rows numRowsCapacity = 100; dataStorage = (id **)malloc(numRowsCapacity * sizeof(id *)); } return self; } - (void) dealloc { [self removeAllRows]; free(dataStorage); [super dealloc]; } @end @implementation SPDataStorage (PrivateAPI) /** * Private method to ensure the array always has sufficient capacity * to store any additional rows required. */ - (void) _ensureCapacityForAdditionalRowCount:(NSUInteger)numExtraRows { while (numRows + numExtraRows > numRowsCapacity) [self _increaseCapacity]; } /** * Private method to increase the storage available for the array; * currently doubles the capacity as boundaries are reached. */ - (void) _increaseCapacity { numRowsCapacity *= 2; dataStorage = (id **)realloc(dataStorage, numRowsCapacity * sizeof(id *)); } @end