You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
514 lines
14 KiB
514 lines
14 KiB
#import <Foundation/Foundation.h>
|
|
#import "AMutableArray.h"
|
|
#import "LinkedHashMap.h"
|
|
#import "RuntimeException.h"
|
|
|
|
extern NSInteger const DEFAULT_INITIAL_CAPACITY;
|
|
extern float const DEFAULT_LOAD_FACTOR;
|
|
|
|
@implementation LHMEntry
|
|
|
|
@synthesize before;
|
|
@synthesize after;
|
|
@synthesize accessOrder;
|
|
|
|
- (id) newEntry:(NSInteger)aHash key:(NSString *)aKey value:(id)aValue next:(LHMEntry *)aNext
|
|
{
|
|
return [[LHMEntry alloc] init:aHash key:aKey value:aValue next:aNext];
|
|
}
|
|
|
|
- (id) init:(NSInteger)aHash key:(NSString *)aKey value:(id)aValue next:(LHMEntry *)aNext
|
|
{
|
|
self = [super init:aHash key:aKey value:aValue next:aNext];
|
|
if (self) {
|
|
}
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void) dealloc
|
|
{
|
|
[before release];
|
|
[after release];
|
|
[super dealloc];
|
|
}
|
|
|
|
/**
|
|
* Removes this entry from the linked list.
|
|
*/
|
|
- (void) removeEntry
|
|
{
|
|
before.after = after;
|
|
after.before = before;
|
|
}
|
|
|
|
|
|
/**
|
|
* Inserts this entry before the specified existing entry in the list.
|
|
*/
|
|
- (void) addBefore:(LHMEntry *)existingEntry
|
|
{
|
|
after = [existingEntry retain];
|
|
before = [existingEntry.before retain];
|
|
before.after = [self retain];
|
|
after.before = [self retain];
|
|
}
|
|
|
|
|
|
/**
|
|
* This method is invoked by the superclass whenever the value
|
|
* of a pre-existing entry is read by Map.get or modified by Map.set.
|
|
* If the enclosing Map is access-ordered, it moves the entry
|
|
* to the end of the list; otherwise, it does nothing.
|
|
*/
|
|
- (void) recordAccess:(LinkedHashMap *)m
|
|
{
|
|
LinkedHashMap *lhm = (LinkedHashMap *)m;
|
|
if (lhm.accessOrder) {
|
|
lhm.modCount++;
|
|
[self removeEntry];
|
|
[self addBefore:lhm.header];
|
|
}
|
|
}
|
|
|
|
- (void) recordRemoval:(LinkedHashMap *)m
|
|
{
|
|
[self removeEntry];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation LinkedHashIterator
|
|
|
|
@synthesize nextEntry;
|
|
@synthesize lastReturned;
|
|
@synthesize lhm;
|
|
|
|
+ (LinkedHashIterator *) newIterator:(LinkedHashMap *)aLHM
|
|
{
|
|
return [[LinkedHashIterator alloc] init:aLHM];
|
|
}
|
|
|
|
- (id) init:(LinkedHashMap *)aLHM
|
|
{
|
|
self = [super init];
|
|
if ( self ) {
|
|
lhm = aLHM;
|
|
nextEntry = lhm.header.after;
|
|
lastReturned = nil;
|
|
expectedModCount = lhm.modCount;
|
|
/*
|
|
AMutableArray *a = [AMutableArray arrayWithCapacity:lhm.Capacity];
|
|
LHMEntry *tmp = lhm.header.after;
|
|
while ( tmp != lhm.header ) {
|
|
[a addObject:tmp];
|
|
tmp = tmp.after;
|
|
}
|
|
anArray = [NSArray arrayWithArray:a];
|
|
*/
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (BOOL) hasNext
|
|
{
|
|
return nextEntry != lhm.header;
|
|
}
|
|
|
|
- (void) remove
|
|
{
|
|
if (lastReturned == nil)
|
|
@throw [[IllegalStateException newException] autorelease];
|
|
if (lhm.modCount != expectedModCount)
|
|
@throw [[ConcurrentModificationException newException:@"Unexpected modCount"] autorelease];
|
|
[lhm remove:(NSString *)(lastReturned.key)];
|
|
lastReturned = nil;
|
|
expectedModCount = lhm.modCount;
|
|
}
|
|
|
|
- (LHMEntry *) nextEntry
|
|
{
|
|
if (lhm.modCount != expectedModCount)
|
|
@throw [[ConcurrentModificationException newException:@"Unexpected modCount"] autorelease];
|
|
if (nextEntry == lhm.header)
|
|
@throw [[[NoSuchElementException alloc] init] autorelease];
|
|
LHMEntry * e = lastReturned = nextEntry;
|
|
nextEntry = e.after;
|
|
return e;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[nextEntry release];
|
|
[lastReturned release];
|
|
[super dealloc];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation LHMKeyIterator
|
|
+ (LHMKeyIterator *)newIterator:(LinkedHashMap *)aLHM
|
|
{
|
|
return [[LHMKeyIterator alloc] init:aLHM];
|
|
}
|
|
|
|
- (id) init:(LinkedHashMap *)aLHM
|
|
{
|
|
self = [super init:aLHM];
|
|
if ( self ) {
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSString *) next
|
|
{
|
|
return [self nextEntry].key;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation LHMValueIterator
|
|
+ (LHMValueIterator *)newIterator:(LinkedHashMap *)aLHM
|
|
{
|
|
return [[LHMValueIterator alloc] init:aLHM];
|
|
}
|
|
|
|
- (id) init:(LinkedHashMap *)aLHM
|
|
{
|
|
self = [super init:aLHM];
|
|
if ( self ) {
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id) next
|
|
{
|
|
return [self nextEntry].value;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation LHMEntryIterator
|
|
+ (LHMEntryIterator *)newIterator:(LinkedHashMap *)aLHM
|
|
{
|
|
return [[LHMEntryIterator alloc] init:aLHM];
|
|
}
|
|
|
|
- (id) init:(LinkedHashMap *)aLHM
|
|
{
|
|
self = [super init:aLHM];
|
|
if ( self ) {
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (LHMEntry *) next
|
|
{
|
|
return [self nextEntry];
|
|
}
|
|
|
|
@end
|
|
|
|
//long const serialVersionUID = 3801124242820219131L;
|
|
|
|
@implementation LinkedHashMap
|
|
|
|
@synthesize header;
|
|
@synthesize accessOrder;
|
|
|
|
/**
|
|
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
|
|
* with the specified initial capacity and load factor.
|
|
*
|
|
* @param initialCapacity the initial capacity
|
|
* @param loadFactor the load factor
|
|
* @throws IllegalArgumentException if the initial capacity is negative
|
|
* or the load factor is nonpositive
|
|
*/
|
|
+ (id) newLinkedHashMap:(NSInteger)anInitialCapacity
|
|
loadFactor:(float)loadFactor
|
|
accessOrder:(BOOL)anAccessOrder
|
|
{
|
|
return [[LinkedHashMap alloc] init:anInitialCapacity
|
|
loadFactor:loadFactor
|
|
accessOrder:(BOOL)anAccessOrder];
|
|
}
|
|
|
|
+ (id) newLinkedHashMap:(NSInteger)anInitialCapacity loadFactor:(float)loadFactor
|
|
{
|
|
return [[LinkedHashMap alloc] init:anInitialCapacity loadFactor:loadFactor];
|
|
}
|
|
|
|
+ (id) newLinkedHashMap:(NSInteger)anInitialCapacity
|
|
{
|
|
return [[LinkedHashMap alloc] init:anInitialCapacity loadFactor:DEFAULT_LOAD_FACTOR];
|
|
}
|
|
|
|
/**
|
|
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
|
|
* specified initial capacity, load factor and ordering mode.
|
|
*
|
|
* @param initialCapacity the initial capacity
|
|
* @param loadFactor the load factor
|
|
* @param accessOrder the ordering mode - <tt>true</tt> for
|
|
* access-order, <tt>false</tt> for insertion-order
|
|
* @throws IllegalArgumentException if the initial capacity is negative
|
|
* or the load factor is nonpositive
|
|
*/
|
|
- (id) init:(NSInteger)anInitialCapacity loadFactor:(float)aLoadFactor accessOrder:(BOOL)anAccessOrder
|
|
{
|
|
self = [super init:anInitialCapacity loadFactor:aLoadFactor];
|
|
if ( self ) {
|
|
accessOrder = anAccessOrder;
|
|
header = [[[LHMEntry alloc] init:-1 key:nil value:nil next:nil] retain];
|
|
header.before = header.after = header;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id) init:(NSInteger)anInitialCapacity loadFactor:(float)aLoadFactor
|
|
{
|
|
self = [super init:anInitialCapacity loadFactor:aLoadFactor];
|
|
if ( self ) {
|
|
accessOrder = NO;
|
|
header = [[[LHMEntry alloc] init:-1 key:nil value:nil next:nil] retain];
|
|
header.before = header.after = header;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
|
|
* with the specified initial capacity and a default load factor (0.75).
|
|
*
|
|
* @param initialCapacity the initial capacity
|
|
* @throws IllegalArgumentException if the initial capacity is negative
|
|
*/
|
|
- (id) init:(NSInteger)initialCapacity
|
|
{
|
|
self = [super init:initialCapacity loadFactor:DEFAULT_LOAD_FACTOR];
|
|
if ( self ) {
|
|
accessOrder = NO;
|
|
header = [[[LHMEntry alloc] init:-1 key:nil value:nil next:nil] retain];
|
|
header.before = header.after = header;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with
|
|
* the same mappings as the specified map. The <tt>LinkedHashMap</tt>
|
|
* instance is created with a default load factor (0.75) and an initial
|
|
* capacity sufficient to hold the mappings in the specified map.
|
|
*
|
|
* @param m the map whose mappings are to be placed in this map
|
|
* @throws NullPointerException if the specified map is null
|
|
*/
|
|
- (id) initWithM:(LinkedHashMap *)m
|
|
{
|
|
self = [super initWithM:m];
|
|
if ( self ) {
|
|
accessOrder = NO;
|
|
header = [[[LHMEntry alloc] init:-1 key:nil value:nil next:nil] retain];
|
|
header.before = header.after = header;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
|
|
* with the default initial capacity (16) and load factor (0.75).
|
|
*/
|
|
- (id) init
|
|
{
|
|
self = [super init];
|
|
if ( self ) {
|
|
accessOrder = NO;
|
|
header = [[[LHMEntry alloc] init:-1 key:nil value:nil next:nil] retain];
|
|
header.before = header.after = header;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
|
|
/**
|
|
* Transfers all entries to new table array. This method is called
|
|
* by superclass resize. It is overridden for performance, as it is
|
|
* faster to iterate using our linked list.
|
|
*/
|
|
- (void) transfer:(AMutableArray *)newTable
|
|
{
|
|
NSInteger newCapacity = [newTable count];
|
|
|
|
for (LHMEntry * e = header.after; e != header; e = e.after) {
|
|
NSInteger index = [self indexFor:e.hash length:newCapacity];
|
|
e.next = [newTable objectAtIndex:index];
|
|
[newTable replaceObjectAtIndex:index withObject:e];
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns <tt>true</tt> if this map maps one or more keys to the
|
|
* specified value.
|
|
*
|
|
* @param value value whose presence in this map is to be tested
|
|
* @return <tt>true</tt> if this map maps one or more keys to the
|
|
* specified value
|
|
*/
|
|
- (BOOL) containsValue:(id)value
|
|
{
|
|
if (value == nil) {
|
|
|
|
for (LHMEntry * e = header.after; e != header; e = e.after)
|
|
if (e.value == nil)
|
|
return YES;
|
|
|
|
}
|
|
else {
|
|
|
|
for (LHMEntry * e = header.after; e != header; e = e.after)
|
|
if ([value isEqualTo:e.value])
|
|
return YES;
|
|
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
/**
|
|
* Returns the value to which the specified key is mapped,
|
|
* or {@code null} if this map contains no mapping for the key.
|
|
*
|
|
* <p>More formally, if this map contains a mapping from a key
|
|
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
|
|
* key.equals(k))}, then this method returns {@code v}; otherwise
|
|
* it returns {@code null}. (There can be at most one such mapping.)
|
|
*
|
|
* <p>A return value of {@code null} does not <i>necessarily</i>
|
|
* indicate that the map contains no mapping for the key; it's also
|
|
* possible that the map explicitly maps the key to {@code null}.
|
|
* The {@link #containsKey containsKey} operation may be used to
|
|
* distinguish these two cases.
|
|
*/
|
|
- (id) get:(NSString *)aKey
|
|
{
|
|
LHMEntry * e = (LHMEntry *)[self getEntry:aKey];
|
|
if (e == nil)
|
|
return nil;
|
|
[e recordAccess:self];
|
|
return e.value;
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes all of the mappings from this map.
|
|
* The map will be empty after this call returns.
|
|
*/
|
|
- (void) clear
|
|
{
|
|
[super clear];
|
|
header.before = header.after = header;
|
|
}
|
|
|
|
- (void) dealloc {
|
|
[header release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (LHMEntryIterator *) newEntryIterator
|
|
{
|
|
return [LHMEntryIterator newIterator:self];
|
|
}
|
|
|
|
- (LHMKeyIterator *) newKeyIterator
|
|
{
|
|
return [LHMKeyIterator newIterator:self];
|
|
}
|
|
|
|
- (LHMValueIterator *) newValueIterator
|
|
{
|
|
return [LHMValueIterator newIterator:self];
|
|
}
|
|
|
|
|
|
/**
|
|
* This override alters behavior of superclass put method. It causes newly
|
|
* allocated entry to get inserted at the end of the linked list and
|
|
* removes the eldest entry if appropriate.
|
|
*/
|
|
- (void) addEntry:(NSInteger)aHash key:(NSString *)aKey value:(id)aValue bucketIndex:(NSInteger)aBucketIndex
|
|
{
|
|
[self createEntry:aHash key:aKey value:aValue bucketIndex:aBucketIndex];
|
|
LHMEntry * eldest = header.after;
|
|
if ([self removeEldestEntry:eldest]) {
|
|
[self removeEntryForKey:eldest.key];
|
|
}
|
|
else {
|
|
if (count >= threshold)
|
|
[self resize:2 * [buffer length]];
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* This override differs from addEntry in that it doesn't resize the
|
|
* table or remove the eldest entry.
|
|
*/
|
|
- (void) createEntry:(NSInteger)aHash key:(NSString *)aKey value:(id)aValue bucketIndex:(NSInteger)bucketIndex
|
|
{
|
|
LHMEntry *old = (LHMEntry *)ptrBuffer[bucketIndex];
|
|
LHMEntry *e = [[[LHMEntry alloc] init:aHash key:aKey value:aValue next:old] retain];
|
|
ptrBuffer[bucketIndex] = (id)e;
|
|
[e addBefore:header];
|
|
count++;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns <tt>true</tt> if this map should remove its eldest entry.
|
|
* This method is invoked by <tt>put</tt> and <tt>putAll</tt> after
|
|
* inserting a new entry into the map. It provides the implementor
|
|
* with the opportunity to remove the eldest entry each time a new one
|
|
* is added. This is useful if the map represents a cache: it allows
|
|
* the map to reduce memory consumption by deleting stale entries.
|
|
*
|
|
* <p>Sample use: this override will allow the map to grow up to 100
|
|
* entries and then delete the eldest entry each time a new entry is
|
|
* added, maintaining a steady state of 100 entries.
|
|
* <pre>
|
|
* private static final NSInteger MAX_ENTRIES = 100;
|
|
*
|
|
* protected boolean removeEldestEntry(Map.LHMEntry eldest) {
|
|
* return count() > MAX_ENTRIES;
|
|
* }
|
|
* </pre>
|
|
*
|
|
* <p>This method typically does not modify the map in any way,
|
|
* instead allowing the map to modify itself as directed by its
|
|
* return value. It <i>is</i> permitted for this method to modify
|
|
* the map directly, but if it does so, it <i>must</i> return
|
|
* <tt>false</tt> (indicating that the map should not attempt any
|
|
* further modification). The effects of returning <tt>true</tt>
|
|
* after modifying the map from within this method are unspecified.
|
|
*
|
|
* <p>This implementation merely returns <tt>false</tt> (so that this
|
|
* map acts like a normal map - the eldest element is never removed).
|
|
*
|
|
* @param eldest The least recently inserted entry in the map, or if
|
|
* this is an access-ordered map, the least recently accessed
|
|
* entry. This is the entry that will be removed it this
|
|
* method returns <tt>true</tt>. If the map was empty prior
|
|
* to the <tt>put</tt> or <tt>putAll</tt> invocation resulting
|
|
* in this invocation, this will be the entry that was just
|
|
* inserted; in other words, if the map contains a single
|
|
* entry, the eldest entry is also the newest.
|
|
* @return <tt>true</tt> if the eldest entry should be removed
|
|
* from the map; <tt>false</tt> if it should be retained.
|
|
*/
|
|
- (BOOL) removeEldestEntry:(LHMEntry *)eldest
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
@end
|