content: /*
   * Copyright (C) 2013 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.bitmap;
  
  import android.util.Log;
  import android.util.LruCache;
  
  import com.android.bitmap.util.Trace;
  
  import java.util.LinkedHashMap;
  import java.util.Map;
  import java.util.concurrent.LinkedBlockingQueue;
  
  /**
   * An alternative implementation of a pool+cache. This implementation only counts
   * unreferenced objects in its size calculation. Internally, it never evicts from
   * its cache, and instead {@link #poll()} is allowed to return unreferenced cache
   * entries.
   * <p>
   * You would only use this kind of cache if your objects are interchangeable and
   * have significant allocation cost, and if your memory footprint is somewhat
   * flexible.
   * <p>
   * Because this class only counts unreferenced objects toward targetSize,
   * it will have a total memory footprint of:
   * <code>(targetSize) + (# of threads concurrently writing to cache) +
   * (total size of still-referenced entries)</code>
   *
   */
  public class UnrefedPooledCache<K, V extends Poolable> implements PooledCache<K, V> {
  
      private final LinkedHashMap<K, V> mCache;
      private final LinkedBlockingQueue<V> mPool;
      private final int mTargetSize;
      private final LruCache<K, V> mNonPooledCache;
  
      private static final boolean DEBUG = DecodeTask.DEBUG;
      private static final String TAG = UnrefedPooledCache.class.getSimpleName();
  
      /**
       * @param targetSize not exactly a max size in practice
       * @param nonPooledFraction the fractional portion in the range [0.0,1.0] of targetSize to
       * dedicate to non-poolable entries
       */
      public UnrefedPooledCache(int targetSize, float nonPooledFraction) {
          mCache = new LinkedHashMap<K, V>(0, 0.75f, true);
          mPool = new LinkedBlockingQueue<V>();
          final int nonPooledSize = Math.round(targetSize * nonPooledFraction);
          if (nonPooledSize > 0) {
              mNonPooledCache = new NonPooledCache(nonPooledSize);
          } else {
              mNonPooledCache = null;
          }
          mTargetSize = targetSize - nonPooledSize;
      }
  
      @Override
      public V get(K key, boolean incrementRefCount) {
          Trace.beginSection("cache get");
          synchronized (mCache) {
              V result = mCache.get(key);
              if (result == null && mNonPooledCache != null) {
                  result = mNonPooledCache.get(key);
              }
              if (incrementRefCount && result != null) {
                  result.acquireReference();
              }
              Trace.endSection();
              return result;
          }
      }
  
      @Override
      public V put(K key, V value) {
          Trace.beginSection("cache put");
          // Null values not supported.
          if (value == null) {
              Trace.endSection();
              return null;
          }
          synchronized (mCache) {
              final V prev;
              if (value.isEligibleForPooling()) {
                  prev = mCache.put(key, value);
              } else if (mNonPooledCache != null) {
                  prev = mNonPooledCache.put(key, value);
              } else {
                  prev = null;
              }
              Trace.endSection();
              return prev;
          }
      }
  
      @Override
      public void offer(V value) {
          Trace.beginSection("pool offer");
          if (value.getRefCount() != 0 || !value.isEligibleForPooling()) {
              Trace.endSection();
              throw new IllegalArgumentException("unexpected offer of an invalid object: " + value);
          }
          mPool.offer(value);
          Trace.endSection();
      }
  
      @Override
      public V poll() {
          Trace.beginSection("pool poll");
          final V pooled = mPool.poll();
          if (pooled != null) {
              Trace.endSection();
              return pooled;
          }
  
          synchronized (mCache) {
              int unrefSize = 0;
              Map.Entry<K, V> eldestUnref = null;
              for (Map.Entry<K, V> entry : mCache.entrySet()) {
                  final V value = entry.getValue();
                  if (value.getRefCount() > 0 || !value.isEligibleForPooling()) {
                      continue;
                  }
                  if (eldestUnref == null) {
                      eldestUnref = entry;
                  }
                  unrefSize += sizeOf(value);
                  if (unrefSize > mTargetSize) {
                      break;
                  }
              }
              // only return a scavenged cache entry if the cache has enough
              // eligible (unreferenced) items
              if (unrefSize <= mTargetSize) {
                  if (DEBUG) {
                      Log.e(TAG, "POOL SCAVENGE FAILED, cache not fully warm yet. szDelta="
                              + (mTargetSize-unrefSize));
                  }
                  Trace.endSection();
                  return null;
              } else {
                  mCache.remove(eldestUnref.getKey());
                  if (DEBUG) {
                      Log.e(TAG, "POOL SCAVENGE SUCCESS, oldKey=" + eldestUnref.getKey());
                  }
                  Trace.endSection();
                  return eldestUnref.getValue();
              }
          }
      }
  
      protected int sizeOf(V value) {
          return 1;
      }
  
      @Override
      public String toDebugString() {
          if (DEBUG) {
              final StringBuilder sb = new StringBuilder("[");
              sb.append(super.toString());
              int size = 0;
              synchronized (mCache) {
                  sb.append(" poolCount=");
                  sb.append(mPool.size());
                  sb.append(" cacheSize=");
                  sb.append(mCache.size());
                  if (mNonPooledCache != null) {
                      sb.append(" nonPooledCacheSize=");
                      sb.append(mNonPooledCache.size());
                  }
                  sb.append("\n---------------------");
                  for (V val : mPool) {
                      size += sizeOf(val);
                      sb.append("\n\tpool item: ");
                      sb.append(val);
                  }
                  sb.append("\n---------------------");
                  for (Map.Entry<K, V> item : mCache.entrySet()) {
                      final V val = item.getValue();
                      sb.append("\n\tcache key=");
                      sb.append(item.getKey());
                      sb.append(" val=");
                      sb.append(val);
                      size += sizeOf(val);
                  }
                  sb.append("\n---------------------");
                  if (mNonPooledCache != null) {
                      for (Map.Entry<K, V> item : mNonPooledCache.snapshot().entrySet()) {
                          final V val = item.getValue();
                          sb.append("\n\tnon-pooled cache key=");
                          sb.append(item.getKey());
                          sb.append(" val=");
                          sb.append(val);
                          size += sizeOf(val);
                      }
                      sb.append("\n---------------------");
                  }
                  sb.append("\nTOTAL SIZE=" + size);
              }
              sb.append("]");
              return sb.toString();
          } else {
              return null;
          }
      }
  
      private class NonPooledCache extends LruCache<K, V> {
  
          public NonPooledCache(int maxSize) {
              super(maxSize);
          }
  
          @Override
          protected int sizeOf(K key, V value) {
              return UnrefedPooledCache.this.sizeOf(value);
          }
  
      }
  
      @Override
      public void clear() {
          mCache.clear();
          mPool.clear();
      }
  }
  