/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.imagepipeline.cache;
import javax.annotation.concurrent.GuardedBy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.logging.FLog;
import com.facebook.common.references.CloseableReference;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.memory.PooledByteBuffer;
import com.facebook.cache.common.CacheKey;
/**
* This is class encapsulates Map that maps ImageCacheKeys to EncodedImages pointing to
* PooledByteBuffers. It is used by SimpleImageCache to store values that are being written
* to disk cache, so that they can be returned by parallel cache get operations.
*/
public class StagingArea {
private static final Class<?> TAG = StagingArea.class;
@GuardedBy("this")
private Map<CacheKey, EncodedImage> mMap;
private StagingArea() {
mMap = new HashMap<>();
}
public static StagingArea getInstance() {
return new StagingArea();
}
/**
* Stores key-value in this StagingArea. This call overrides previous value
* of stored reference if
* @param key
* @param encodedImage EncodedImage to be associated with key
*/
public synchronized void put(final CacheKey key, final EncodedImage encodedImage) {
Preconditions.checkNotNull(key);
Preconditions.checkArgument(EncodedImage.isValid(encodedImage));
// we're making a 'copy' of this reference - so duplicate it
final EncodedImage oldEntry = mMap.put(key, EncodedImage.cloneOrNull(encodedImage));
EncodedImage.closeSafely(oldEntry);
logStats();
}
/**
* Removes all items from the StagingArea.
*/
public void clearAll() {
final List<EncodedImage> old;
synchronized (this) {
old = new ArrayList<>(mMap.values());
mMap.clear();
}
for (int i = 0; i < old.size(); i++) {
EncodedImage encodedImage = old.get(i);
if (encodedImage != null) {
encodedImage.close();
}
}
}
/**
* Removes item from the StagingArea.
* @param key
* @return true if item was removed
*/
public boolean remove(final CacheKey key) {
Preconditions.checkNotNull(key);
final EncodedImage encodedImage;
synchronized (this) {
encodedImage = mMap.remove(key);
}
if (encodedImage == null) {
return false;
}
try {
return encodedImage.isValid();
} finally {
encodedImage.close();
}
}
/**
* Removes key-value from the StagingArea. Both key and value must match.
* @param key
* @param encodedImage value corresponding to key
* @return true if item was removed
*/
public synchronized boolean remove(final CacheKey key, final EncodedImage encodedImage) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(encodedImage);
Preconditions.checkArgument(EncodedImage.isValid(encodedImage));
final EncodedImage oldValue = mMap.get(key);
if (oldValue == null) {
return false;
}
CloseableReference<PooledByteBuffer> oldRef = oldValue.getByteBufferRef();
CloseableReference<PooledByteBuffer> ref = encodedImage.getByteBufferRef();
try {
if (oldRef == null || ref == null || oldRef.get() != ref.get()) {
return false;
}
mMap.remove(key);
} finally {
CloseableReference.closeSafely(ref);
CloseableReference.closeSafely(oldRef);
EncodedImage.closeSafely(oldValue);
}
logStats();
return true;
}
/**
* @param key
* @return value associated with given key or null if no value is associated
*/
public synchronized EncodedImage get(final CacheKey key) {
Preconditions.checkNotNull(key);
EncodedImage storedEncodedImage = mMap.get(key);
if (storedEncodedImage != null) {
synchronized (storedEncodedImage) {
if (!EncodedImage.isValid(storedEncodedImage)) {
// Reference is not valid, this means that someone cleared reference while it was still in
// use. Log error
// TODO: 3697790
mMap.remove(key);
FLog.w(
TAG,
"Found closed reference %d for key %s (%d)",
System.identityHashCode(storedEncodedImage),
key.toString(),
System.identityHashCode(key));
return null;
}
storedEncodedImage = EncodedImage.cloneOrNull(storedEncodedImage);
}
}
return storedEncodedImage;
}
/**
* Simple 'debug' logging of stats.
*/
private synchronized void logStats() {
FLog.v(TAG, "Count = %d", mMap.size());
}
}