1/*
2******************************************************************************
3* Copyright (C) 2014, International Business Machines Corporation and
4* others. All Rights Reserved.
5******************************************************************************
6*
7* File LRUCACHE.CPP
8******************************************************************************
9*/
10
11#include "lrucache.h"
12#include "uhash.h"
13#include "cstring.h"
14#include "uassert.h"
15
16U_NAMESPACE_BEGIN
17
18// TODO (Travis Keep): Consider building synchronization into this cache
19// instead of leaving synchronization up to the clients.
20
21LRUCache::CacheEntry::CacheEntry()
22    : moreRecent(NULL), lessRecent(NULL), localeId(NULL), cachedData(NULL),
23      status(U_ZERO_ERROR) {
24}
25
26LRUCache::CacheEntry::~CacheEntry() {
27    reset();
28}
29
30void LRUCache::CacheEntry::unlink() {
31    if (moreRecent != NULL) {
32        moreRecent->lessRecent = lessRecent;
33    }
34    if (lessRecent != NULL) {
35        lessRecent->moreRecent = moreRecent;
36    }
37    moreRecent = NULL;
38    lessRecent = NULL;
39}
40
41void LRUCache::CacheEntry::reset() {
42    SharedObject::clearPtr(cachedData);
43    status = U_ZERO_ERROR;
44    uprv_free(localeId);
45    localeId = NULL;
46}
47
48void LRUCache::CacheEntry::init(
49        char *adoptedLocId, SharedObject *dataToAdopt, UErrorCode err) {
50    U_ASSERT(localeId == NULL);
51    localeId = adoptedLocId;
52    SharedObject::copyPtr(dataToAdopt, cachedData);
53    status = err;
54}
55
56void LRUCache::moveToMostRecent(CacheEntry *entry) {
57    if (entry->moreRecent == mostRecentlyUsedMarker) {
58        return;
59    }
60    entry->unlink();
61    entry->moreRecent = mostRecentlyUsedMarker;
62    entry->lessRecent = mostRecentlyUsedMarker->lessRecent;
63    mostRecentlyUsedMarker->lessRecent->moreRecent = entry;
64    mostRecentlyUsedMarker->lessRecent = entry;
65}
66
67void LRUCache::init(char *adoptedLocId, CacheEntry *entry) {
68    UErrorCode status = U_ZERO_ERROR;
69    SharedObject *result = create(adoptedLocId, status);
70    entry->init(adoptedLocId, result, status);
71}
72
73UBool LRUCache::contains(const char *localeId) const {
74    return (uhash_get(localeIdToEntries, localeId) != NULL);
75}
76
77
78const SharedObject *LRUCache::_get(const char *localeId, UErrorCode &status) {
79    // TODO (Travis Keep): Consider stripping irrelevant locale keywords.
80    if (U_FAILURE(status)) {
81        return NULL;
82    }
83    CacheEntry *entry = static_cast<CacheEntry *>(uhash_get(
84            localeIdToEntries, localeId));
85    if (entry == NULL) {
86        // Its a cache miss.
87
88        if (uhash_count(localeIdToEntries) < maxSize) {
89            // Cache not full. There is room for a new entry.
90            entry = new CacheEntry;
91            if (entry == NULL) {
92                status = U_MEMORY_ALLOCATION_ERROR;
93                return NULL;
94            }
95        } else {
96            // Cache full. Must evict an entry and re-use it.
97            entry = leastRecentlyUsedMarker->moreRecent;
98            uhash_remove(localeIdToEntries, entry->localeId);
99            entry->unlink();
100            entry->reset();
101        }
102
103        // entry is an uninitialized, unlinked cache entry
104        char *dupLocaleId = uprv_strdup(localeId);
105        if (dupLocaleId == NULL) {
106            delete entry;
107            status = U_MEMORY_ALLOCATION_ERROR;
108            return NULL;
109        }
110        init(dupLocaleId, entry);
111
112        // Entry is initialized, add to hashtable
113        uhash_put(localeIdToEntries, entry->localeId, entry, &status);
114        if (U_FAILURE(status)) {
115            delete entry;
116            return NULL;
117        }
118    }
119
120    // Re-link entry so that it is the most recent.
121    moveToMostRecent(entry);
122
123    if (U_FAILURE(entry->status)) {
124        status = entry->status;
125        return NULL;
126    }
127    return entry->cachedData;
128}
129
130LRUCache::LRUCache(int32_t size, UErrorCode &status) :
131        mostRecentlyUsedMarker(NULL),
132        leastRecentlyUsedMarker(NULL),
133        localeIdToEntries(NULL),
134        maxSize(size) {
135    if (U_FAILURE(status)) {
136        return;
137    }
138    mostRecentlyUsedMarker = new CacheEntry;
139    leastRecentlyUsedMarker = new CacheEntry;
140    if (mostRecentlyUsedMarker == NULL || leastRecentlyUsedMarker == NULL) {
141        delete mostRecentlyUsedMarker;
142        delete leastRecentlyUsedMarker;
143        mostRecentlyUsedMarker = leastRecentlyUsedMarker = NULL;
144        status = U_MEMORY_ALLOCATION_ERROR;
145        return;
146    }
147    mostRecentlyUsedMarker->moreRecent = NULL;
148    mostRecentlyUsedMarker->lessRecent = leastRecentlyUsedMarker;
149    leastRecentlyUsedMarker->moreRecent = mostRecentlyUsedMarker;
150    leastRecentlyUsedMarker->lessRecent = NULL;
151    localeIdToEntries = uhash_openSize(
152        uhash_hashChars,
153        uhash_compareChars,
154        NULL,
155        maxSize + maxSize / 5,
156        &status);
157    if (U_FAILURE(status)) {
158        return;
159    }
160}
161
162LRUCache::~LRUCache() {
163    uhash_close(localeIdToEntries);
164    for (CacheEntry *i = mostRecentlyUsedMarker; i != NULL;) {
165        CacheEntry *next = i->lessRecent;
166        delete i;
167        i = next;
168    }
169}
170
171SimpleLRUCache::~SimpleLRUCache() {
172}
173
174SharedObject *SimpleLRUCache::create(const char *localeId, UErrorCode &status) {
175    return createFunc(localeId, status);
176}
177
178U_NAMESPACE_END
179