@SuppressWarnings("deprecation") public static <K, V> ConcurrentMap<K, V> makeSoftValueComputingMapWithRemoveListenr(MapMaker maker, Function<? super K, ? extends V> computingFunction, final OtterRemovalListener listener) { return maker.softValues().removalListener(new RemovalListener<K, V>() { @Override public void onRemoval(RemovalNotification<K, V> notification) { if (notification == null) { return; } listener.onRemoval(notification.getKey(), notification.getValue()); } }).makeComputingMap(computingFunction); }
/** * Notifies listeners that an entry has been automatically removed due to expiration, eviction, * or eligibility for garbage collection. This should be called every time expireEntries or * evictEntry is called (once the lock is released). */ void processPendingNotifications() { RemovalNotification<K, V> notification; while ((notification = removalNotificationQueue.poll()) != null) { try { removalListener.onRemoval(notification); } catch (Exception e) { logger.log(Level.WARNING, "Exception thrown by removal listener", e); } } }
public void testSetRemovalListener() { RemovalListener<Object, Object> testListener = new RemovalListener<Object, Object>() { @Override public void onRemoval(RemovalNotification<Object, Object> notification) {} }; MapMakerInternalMap<Object, Object> map = makeMap(createMapMaker().removalListener(testListener)); assertSame(testListener, map.removalListener); }
static <K, V> void assertNotified( QueuingRemovalListener<K, V> listener, K key, V value, RemovalCause cause) { RemovalNotification<K, V> notification = listener.remove(); assertSame(key, notification.getKey()); assertSame(value, notification.getValue()); assertSame(cause, notification.getCause()); }
@SuppressWarnings("deprecation") public static <K, V> ConcurrentMap<K, V> makeSoftValueComputingMapWithRemoveListenr(Function<? super K, ? extends V> computingFunction, final OtterRemovalListener<K, V> listener) { return new MapMaker().softValues().removalListener(new RemovalListener<K, V>() { @Override public void onRemoval(RemovalNotification<K, V> notification) { if (notification == null) { return; } listener.onRemoval(notification.getKey(), notification.getValue()); } }).makeComputingMap(computingFunction); }
@Override public void onRemoval(RemovalNotification<Object, Object> notification) {}
/** * Creates a new, empty map with the specified strategy, initial capacity and concurrency level. */ MapMakerInternalMap(MapMaker builder) { concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS); keyStrength = builder.getKeyStrength(); valueStrength = builder.getValueStrength(); keyEquivalence = builder.getKeyEquivalence(); valueEquivalence = valueStrength.defaultEquivalence(); maximumSize = builder.maximumSize; expireAfterAccessNanos = builder.getExpireAfterAccessNanos(); expireAfterWriteNanos = builder.getExpireAfterWriteNanos(); entryFactory = EntryFactory.getFactory(keyStrength, expires(), evictsBySize()); ticker = builder.getTicker(); removalListener = builder.getRemovalListener(); removalNotificationQueue = (removalListener == NullListener.INSTANCE) ? MapMakerInternalMap.<RemovalNotification<K, V>>discardingQueue() : new ConcurrentLinkedQueue<RemovalNotification<K, V>>(); int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); if (evictsBySize()) { initialCapacity = Math.min(initialCapacity, maximumSize); } // Find power-of-two sizes best matching arguments. Constraints: // (segmentCount <= maximumSize) // && (concurrencyLevel > maximumSize || segmentCount > concurrencyLevel) int segmentShift = 0; int segmentCount = 1; while (segmentCount < concurrencyLevel && (!evictsBySize() || segmentCount * 2 <= maximumSize)) { ++segmentShift; segmentCount <<= 1; } this.segmentShift = 32 - segmentShift; segmentMask = segmentCount - 1; this.segments = newSegmentArray(segmentCount); int segmentCapacity = initialCapacity / segmentCount; if (segmentCapacity * segmentCount < initialCapacity) { ++segmentCapacity; } int segmentSize = 1; while (segmentSize < segmentCapacity) { segmentSize <<= 1; } if (evictsBySize()) { // Ensure sum of segment max sizes = overall max size int maximumSegmentSize = maximumSize / segmentCount + 1; int remainder = maximumSize % segmentCount; for (int i = 0; i < this.segments.length; ++i) { if (i == remainder) { maximumSegmentSize--; } this.segments[i] = createSegment(segmentSize, maximumSegmentSize); } } else { for (int i = 0; i < this.segments.length; ++i) { this.segments[i] = createSegment(segmentSize, MapMaker.UNSET_INT); } } }
void enqueueNotification(@Nullable K key, int hash, @Nullable V value, RemovalCause cause) { if (map.removalNotificationQueue != DISCARDING_QUEUE) { RemovalNotification<K, V> notification = new RemovalNotification<K, V>(key, value, cause); map.removalNotificationQueue.offer(notification); } }
@GwtIncompatible("threads") public void testRemovalNotification_clear() throws InterruptedException { // If a clear() happens while a computation is pending, we should not get a removal // notification. final CountDownLatch computingLatch = new CountDownLatch(1); Function<String, String> computingFunction = new DelayingIdentityLoader<String>(computingLatch); QueuingRemovalListener<String, String> listener = new QueuingRemovalListener<String, String>(); @SuppressWarnings("deprecation") // test of deprecated code final ConcurrentMap<String, String> map = new MapMaker() .concurrencyLevel(1) .removalListener(listener) .makeComputingMap(computingFunction); // seed the map, so its segment's count > 0 map.put("a", "a"); final CountDownLatch computationStarted = new CountDownLatch(1); final CountDownLatch computationComplete = new CountDownLatch(1); new Thread(new Runnable() { @Override public void run() { computationStarted.countDown(); map.get("b"); computationComplete.countDown(); } }).start(); // wait for the computingEntry to be created computationStarted.await(); map.clear(); // let the computation proceed computingLatch.countDown(); // don't check map.size() until we know the get("b") call is complete computationComplete.await(); // At this point, the listener should be holding the seed value (a -> a), and the map should // contain the computed value (b -> b), since the clear() happened before the computation // completed. assertEquals(1, listener.size()); RemovalNotification<String, String> notification = listener.remove(); assertEquals("a", notification.getKey()); assertEquals("a", notification.getValue()); assertEquals(1, map.size()); assertEquals("b", map.get("b")); }
private static <K, V> void assertNotificationEnqueued( MapMakerInternalMap<K, V> map, K key, V value) { RemovalNotification<K, V> notification = map.removalNotificationQueue.poll(); assertSame(key, notification.getKey()); assertSame(value, notification.getValue()); }
@Override public void onRemoval(RemovalNotification<K, V> notification) { count.incrementAndGet(); lastKey = notification.getKey(); lastValue = notification.getValue(); }
@Override public void onRemoval(RemovalNotification<K, V> notification) { add(notification); }