ImmutableItemCache { public ITEM find(ID id, ImmutableItemManager<ID, ITEM> itemManager) throws InterruptedException, IOException { ITEM item; boolean cleanupPlaceholder = false; CacheEntry<ID, ITEM> entry = cache.get(id); if (entry != null && !entry.placeholder()) { entry.recentAccess(true); item = entry.item(); } else { try { synchronized (this) { while ((entry = cache.get(id)) != null && entry.placeholder()) { wait(); } if (entry == null) { assert cacheSize <= cacheCapacity; if (cacheSize == cacheCapacity) { ITEM victim = clock.takeItemToEvict(); assert victim != null; CacheEntry<ID, ITEM> removed = cache.remove(victim.id()); assert removed == victim : String.format("replaced: %s, entry: %s", removed, entry); itemManager.cleanupItemEvictedFromCache(victim); cacheSize--; } assert cacheSize < cacheCapacity; entry = ItemPlaceholder.<ID, ITEM>forCurrentThread(); CacheEntry<ID, ITEM> replaced = cache.put(id, entry); assert replaced == null; cacheSize++; } else { assert !entry.placeholder(); assert entry.id().equals(id); entry.recentAccess(true); } } if (entry.placeholder()) { assert entry.owner() == Thread.currentThread(); item = itemManager.getItemForCache(id); synchronized (this) { CacheEntry<ID, ITEM> replaced = cache.put(id, item); assert replaced == entry : String.format("replaced: %s, entry: %s", replaced, entry); clock.addItem(item); item.recentAccess(true); notifyAll(); } } else { entry.recentAccess(true); item = entry.item(); } } catch (RuntimeException | Error e) { cleanupPlaceholder = true; throw e; } finally { if (cleanupPlaceholder) { synchronized (this) { entry = cache.get(id); if (entry != null && entry.placeholder()) { cache.remove(id); } } } } } assert item.id().equals(id); return item; } ImmutableItemCache(int cacheCapacity);  ImmutableItemCache(int cacheCapacity, CacheEntryList.Observer<ID, ITEM> observer); ITEM find(ID id, ImmutableItemManager<ID, ITEM> itemManager); int size(); void clear();  }
@Test public void testReadMissing() throws InterruptedException, IOException { final int CACHE_SIZE = 10; ImmutableItemCache<Id, Item> cache = new ImmutableItemCache<>(CACHE_SIZE); for (int i = 0; i < CACHE_SIZE; i++) { Id id = id(i); Item item = cache.find(id, ITEM_READER); assertEquals(id, item.id()); } }
@Test public void testReadPresent() throws InterruptedException, IOException { final int CACHE_SIZE = 10; ImmutableItemCache<Id, Item> cache = new ImmutableItemCache<>(CACHE_SIZE); Item[] items = new Item[CACHE_SIZE]; for (int i = 0; i < CACHE_SIZE; i++) { Id id = id(i); Item item = cache.find(id, ITEM_READER); assertEquals(id, item.id()); items[i] = item; } for (int i = 0; i < CACHE_SIZE; i++) { Id id = id(i); Item item = cache.find(id, ITEM_READER); assertSame(items[i], item); } }
@Test public void testUnableToEvict() throws InterruptedException, IOException { final int CACHE_SIZE = 10; ImmutableItemCache<Id, Item> cache = new ImmutableItemCache<>(CACHE_SIZE); int id = 0; while (id < CACHE_SIZE) { Item item = cache.find(id(id), ITEM_READER); item.okToEvict(false); id++; } try { cache.find(id(CACHE_SIZE), ITEM_READER); fail(); } catch (ImmutableItemCacheError e) { } }