content: /*
   *    Copyright (C) 2016 Amit Shekhar
   *    Copyright (C) 2011 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.androidnetworking.cache;
  
  import android.annotation.SuppressLint;
  
  import java.util.LinkedHashMap;
  import java.util.Map;
  
  /**
   * Created by amitshekhar on 28/05/16.
   */
  public class LruCache<K, V> {
      private final LinkedHashMap<K, V> map;
  
      private int size;
      private int maxSize;
  
      private int putCount;
      private int createCount;
      private int evictionCount;
      private int hitCount;
      private int missCount;
  
      public LruCache(int maxSize) {
          if (maxSize <= 0) {
              throw new IllegalArgumentException("maxSize <= 0");
          }
          this.maxSize = maxSize;
          this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
      }
  
      public void resize(int maxSize) {
          if (maxSize <= 0) {
              throw new IllegalArgumentException("maxSize <= 0");
          }
  
          synchronized (this) {
              this.maxSize = maxSize;
          }
          trimToSize(maxSize);
      }
  
      public final V get(K key) {
          if (key == null) {
              throw new NullPointerException("key == null");
          }
  
          V mapValue;
          synchronized (this) {
              mapValue = map.get(key);
              if (mapValue != null) {
                  hitCount++;
                  return mapValue;
              }
              missCount++;
          }
  
          V createdValue = create(key);
          if (createdValue == null) {
              return null;
          }
  
          synchronized (this) {
              createCount++;
              mapValue = map.put(key, createdValue);
  
              if (mapValue != null) {
                  // There was a conflict so undo that last put
                  map.put(key, mapValue);
              } else {
                  size += safeSizeOf(key, createdValue);
              }
          }
  
          if (mapValue != null) {
              entryRemoved(false, key, createdValue, mapValue);
              return mapValue;
          } else {
              trimToSize(maxSize);
              return createdValue;
          }
      }
  
      public final V put(K key, V value) {
          if (key == null || value == null) {
              throw new NullPointerException("key == null || value == null");
          }
  
          V previous;
          synchronized (this) {
              putCount++;
              size += safeSizeOf(key, value);
              previous = map.put(key, value);
              if (previous != null) {
                  size -= safeSizeOf(key, previous);
              }
          }
  
          if (previous != null) {
              entryRemoved(false, key, previous, value);
          }
  
          trimToSize(maxSize);
          return previous;
      }
  
      public void trimToSize(int maxSize) {
          while (true) {
              K key;
              V value;
              synchronized (this) {
                  if (size < 0 || (map.isEmpty() && size != 0)) {
                      throw new IllegalStateException(getClass().getName()
                              + ".sizeOf() is reporting inconsistent results!");
                  }
  
                  if (size <= maxSize || map.isEmpty()) {
                      break;
                  }
  
                  Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                  key = toEvict.getKey();
                  value = toEvict.getValue();
                  map.remove(key);
                  size -= safeSizeOf(key, value);
                  evictionCount++;
              }
  
              entryRemoved(true, key, value, null);
          }
      }
  
      public final V remove(K key) {
          if (key == null) {
              throw new NullPointerException("key == null");
          }
  
          V previous;
          synchronized (this) {
              previous = map.remove(key);
              if (previous != null) {
                  size -= safeSizeOf(key, previous);
              }
          }
  
          if (previous != null) {
              entryRemoved(false, key, previous, null);
          }
  
          return previous;
      }
  
      protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
      }
  
      protected V create(K key) {
          return null;
      }
  
      private int safeSizeOf(K key, V value) {
          int result = sizeOf(key, value);
          if (result < 0) {
              throw new IllegalStateException("Negative size: " + key + "=" + value);
          }
          return result;
      }
  
      protected int sizeOf(K key, V value) {
          return 1;
      }
  
      public final void evictAll() {
          trimToSize(-1);
      }
  
      public synchronized final int size() {
          return size;
      }
  
      public synchronized final int maxSize() {
          return maxSize;
      }
  
      public synchronized final int hitCount() {
          return hitCount;
      }
  
      public synchronized final int missCount() {
          return missCount;
      }
  
      public synchronized final int createCount() {
          return createCount;
      }
  
      public synchronized final int putCount() {
          return putCount;
      }
  
      public synchronized final int evictionCount() {
          return evictionCount;
      }
  
      public synchronized final Map<K, V> snapshot() {
          return new LinkedHashMap<K, V>(map);
      }
  
      @SuppressLint("DefaultLocale")
      @Override
      public synchronized final String toString() {
          int accesses = hitCount + missCount;
          int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
          return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                  maxSize, hitCount, missCount, hitPercent);
      }
  }
  