content: /*
   * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
   *
   * 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.lidroid.xutils.bitmap.core;
  
  import android.graphics.Bitmap;
  import android.graphics.Matrix;
  import android.media.ExifInterface;
  import com.lidroid.xutils.BitmapUtils;
  import com.lidroid.xutils.bitmap.BitmapDisplayConfig;
  import com.lidroid.xutils.bitmap.BitmapGlobalConfig;
  import com.lidroid.xutils.bitmap.factory.BitmapFactory;
  import com.lidroid.xutils.cache.FileNameGenerator;
  import com.lidroid.xutils.cache.LruDiskCache;
  import com.lidroid.xutils.cache.LruMemoryCache;
  import com.lidroid.xutils.util.IOUtils;
  import com.lidroid.xutils.util.LogUtils;
  import com.lidroid.xutils.util.OtherUtils;
  
  import java.io.*;
  
  
  public class BitmapCache {
  
      private final int DISK_CACHE_INDEX = 0;
  
      private LruDiskCache mDiskLruCache;
      private LruMemoryCache<MemoryCacheKey, Bitmap> mMemoryCache;
  
      private final Object mDiskCacheLock = new Object();
  
      private BitmapGlobalConfig globalConfig;
  
      /**
       * Creating a new ImageCache object using the specified parameters.
       *
       * @param globalConfig The cache parameters to use to initialize the cache
       */
      public BitmapCache(BitmapGlobalConfig globalConfig) {
          if (globalConfig == null) throw new IllegalArgumentException("globalConfig may not be null");
          this.globalConfig = globalConfig;
      }
  
  
      /**
       * Initialize the memory cache
       */
      public void initMemoryCache() {
          if (!globalConfig.isMemoryCacheEnabled()) return;
  
          // Set up memory cache
          if (mMemoryCache != null) {
              try {
                  clearMemoryCache();
              } catch (Throwable e) {
              }
          }
          mMemoryCache = new LruMemoryCache<MemoryCacheKey, Bitmap>(globalConfig.getMemoryCacheSize()) {
              /**
               * Measure item size in bytes rather than units which is more practical
               * for a bitmap cache
               */
              @Override
              protected int sizeOf(MemoryCacheKey key, Bitmap bitmap) {
                  if (bitmap == null) return 0;
                  return bitmap.getRowBytes() * bitmap.getHeight();
              }
          };
      }
  
      /**
       * Initializes the disk cache.  Note that this includes disk access so this should not be
       * executed on the main/UI thread. By default an ImageCache does not initialize the disk
       * cache when it is created, instead you should call initDiskCache() to initialize it on a
       * background thread.
       */
      public void initDiskCache() {
          // Set up disk cache
          synchronized (mDiskCacheLock) {
              if (globalConfig.isDiskCacheEnabled() && (mDiskLruCache == null || mDiskLruCache.isClosed())) {
                  File diskCacheDir = new File(globalConfig.getDiskCachePath());
                  if (diskCacheDir.exists() || diskCacheDir.mkdirs()) {
                      long availableSpace = OtherUtils.getAvailableSpace(diskCacheDir);
                      long diskCacheSize = globalConfig.getDiskCacheSize();
                      diskCacheSize = availableSpace > diskCacheSize ? diskCacheSize : availableSpace;
                      try {
                          mDiskLruCache = LruDiskCache.open(diskCacheDir, 1, 1, diskCacheSize);
                          mDiskLruCache.setFileNameGenerator(globalConfig.getFileNameGenerator());
                          LogUtils.d("create disk cache success");
                      } catch (Throwable e) {
                          mDiskLruCache = null;
                          LogUtils.e("create disk cache error", e);
                      }
                  }
              }
          }
      }
  
      public void setMemoryCacheSize(int maxSize) {
          if (mMemoryCache != null) {
              mMemoryCache.setMaxSize(maxSize);
          }
      }
  
      public void setDiskCacheSize(int maxSize) {
          synchronized (mDiskCacheLock) {
              if (mDiskLruCache != null) {
                  mDiskLruCache.setMaxSize(maxSize);
              }
          }
      }
  
      public void setDiskCacheFileNameGenerator(FileNameGenerator fileNameGenerator) {
          synchronized (mDiskCacheLock) {
              if (mDiskLruCache != null && fileNameGenerator != null) {
                  mDiskLruCache.setFileNameGenerator(fileNameGenerator);
              }
          }
      }
  
      /**
       * 下载图片
       * @param uri
       * @param config
       * @param task
       * @return
       */
      public Bitmap downloadBitmap(String uri, BitmapDisplayConfig config, final BitmapUtils.BitmapLoadTask<?> task) {
  
          BitmapMeta bitmapMeta = new BitmapMeta();
  
          OutputStream outputStream = null;
          LruDiskCache.Snapshot snapshot = null;
  
          try {
              Bitmap bitmap = null;
  
              // try download to disk
              if (globalConfig.isDiskCacheEnabled()) {
                  if (mDiskLruCache == null) {
                      initDiskCache();
                  }
  
                  if (mDiskLruCache != null) {
                      try {
                          snapshot = mDiskLruCache.get(uri);
                          if (snapshot == null) {
                          	//先将数据写入闪存中
                              LruDiskCache.Editor editor = mDiskLruCache.edit(uri);
                              if (editor != null) {
                                  outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
                                  bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri, outputStream, task);
                                  if (bitmapMeta.expiryTimestamp < 0) {
                                      editor.abort();
                                      return null;
                                  } else {
                                      editor.setEntryExpiryTimestamp(bitmapMeta.expiryTimestamp);
                                      editor.commit();
                                  }
                                  //存闪存中读出
                                  snapshot = mDiskLruCache.get(uri);
                              }
                          }
                          if (snapshot != null) {
                              bitmapMeta.inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
                              bitmap = decodeBitmapMeta(bitmapMeta, config);
                              if (bitmap == null) {
                                  bitmapMeta.inputStream = null;
                                  mDiskLruCache.remove(uri);
                              }
                          }
                      } catch (Throwable e) {
                          LogUtils.e(e.getMessage(), e);
                      }
                  }
              }
  
              // try download to memory stream
              if (bitmap == null) {
                  outputStream = new ByteArrayOutputStream();
                  bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri, outputStream, task);
                  if (bitmapMeta.expiryTimestamp < 0) {
                      return null;
                  } else {
                      bitmapMeta.data = ((ByteArrayOutputStream) outputStream).toByteArray();
                      bitmap = decodeBitmapMeta(bitmapMeta, config);
                  }
              }
  
              if (bitmap != null) {
                  bitmap = rotateBitmapIfNeeded(uri, config, bitmap);
                  //添加到运行内存的缓存中
                  bitmap = addBitmapToMemoryCache(uri, config, bitmap, bitmapMeta.expiryTimestamp);
              }
              return bitmap;
          } catch (Throwable e) {
              LogUtils.e(e.getMessage(), e);
          } finally {
              IOUtils.closeQuietly(outputStream);
              IOUtils.closeQuietly(snapshot);
          }
  
          return null;
      }
  
      private Bitmap addBitmapToMemoryCache(String uri, BitmapDisplayConfig config, Bitmap bitmap, long expiryTimestamp) throws IOException {
          if (config != null) {
              BitmapFactory bitmapFactory = config.getBitmapFactory();
              if (bitmapFactory != null) {
                  bitmap = bitmapFactory.cloneNew().createBitmap(bitmap);
              }
          }
          if (uri != null && bitmap != null && globalConfig.isMemoryCacheEnabled() && mMemoryCache != null) {
              MemoryCacheKey key = new MemoryCacheKey(uri, config);
              mMemoryCache.put(key, bitmap, expiryTimestamp);
          }
          return bitmap;
      }
  
      /**
       * Get the bitmap from memory cache.
       *
       * @param uri    Unique identifier for which item to get
       * @param config
       * @return The bitmap if found in cache, null otherwise
       */
      public Bitmap getBitmapFromMemCache(String uri, BitmapDisplayConfig config) {
          if (mMemoryCache != null && globalConfig.isMemoryCacheEnabled()) {
              MemoryCacheKey key = new MemoryCacheKey(uri, config);
              return mMemoryCache.get(key);
          }
          return null;
      }
  
      /**
       * Get the bitmap file from disk cache.
       *
       * @param uri Unique identifier for which item to get
       * @return The file if found in cache.
       */
      public File getBitmapFileFromDiskCache(String uri) {
          synchronized (mDiskCacheLock) {
              if (mDiskLruCache != null) {
                  return mDiskLruCache.getCacheFile(uri, DISK_CACHE_INDEX);
              } else {
                  return null;
              }
          }
      }
  
      /**
       * Get the bitmap from disk cache.
       *
       * @param uri
       * @param config
       * @return
       */
      public Bitmap getBitmapFromDiskCache(String uri, BitmapDisplayConfig config) {
          if (uri == null || !globalConfig.isDiskCacheEnabled()) return null;
          if (mDiskLruCache == null) {
              initDiskCache();
          }
          if (mDiskLruCache != null) {
              LruDiskCache.Snapshot snapshot = null;
              try {
                  snapshot = mDiskLruCache.get(uri);
                  if (snapshot != null) {
                      Bitmap bitmap = null;
                      if (config == null || config.isShowOriginal()) {
                          bitmap = BitmapDecoder.decodeFileDescriptor(
                                  snapshot.getInputStream(DISK_CACHE_INDEX).getFD());
                      } else {
                          bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(
                                  snapshot.getInputStream(DISK_CACHE_INDEX).getFD(),
                                  config.getBitmapMaxSize(),
                                  config.getBitmapConfig());
                      }
  
                      bitmap = rotateBitmapIfNeeded(uri, config, bitmap);
                      bitmap = addBitmapToMemoryCache(uri, config, bitmap, mDiskLruCache.getExpiryTimestamp(uri));
                      return bitmap;
                  }
              } catch (Throwable e) {
                  LogUtils.e(e.getMessage(), e);
              } finally {
                  IOUtils.closeQuietly(snapshot);
              }
          }
          return null;
      }
  
      /**
       * Clears both the memory and disk cache associated with this ImageCache object. Note that
       * this includes disk access so this should not be executed on the main/UI thread.
       */
      public void clearCache() {
          clearMemoryCache();
          clearDiskCache();
      }
  
      public void clearMemoryCache() {
          if (mMemoryCache != null) {
              mMemoryCache.evictAll();
          }
      }
  
      public void clearDiskCache() {
          synchronized (mDiskCacheLock) {
              if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
                  try {
                      mDiskLruCache.delete();
                      mDiskLruCache.close();
                  } catch (Throwable e) {
                      LogUtils.e(e.getMessage(), e);
                  }
                  mDiskLruCache = null;
              }
          }
          initDiskCache();
      }
  
  
      public void clearCache(String uri) {
          clearMemoryCache(uri);
          clearDiskCache(uri);
      }
  
      public void clearMemoryCache(String uri) {
          MemoryCacheKey key = new MemoryCacheKey(uri, null);
          if (mMemoryCache != null) {
              while (mMemoryCache.containsKey(key)) {
                  mMemoryCache.remove(key);
              }
          }
      }
  
      public void clearDiskCache(String uri) {
          synchronized (mDiskCacheLock) {
              if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
                  try {
                      mDiskLruCache.remove(uri);
                  } catch (Throwable e) {
                      LogUtils.e(e.getMessage(), e);
                  }
              }
          }
      }
  
      /**
       * Flushes the disk cache associated with this ImageCache object. Note that this includes
       * disk access so this should not be executed on the main/UI thread.
       */
      public void flush() {
          synchronized (mDiskCacheLock) {
              if (mDiskLruCache != null) {
                  try {
                      mDiskLruCache.flush();
                  } catch (Throwable e) {
                      LogUtils.e(e.getMessage(), e);
                  }
              }
          }
      }
  
      /**
       * Closes the disk cache associated with this ImageCache object. Note that this includes
       * disk access so this should not be executed on the main/UI thread.
       */
      public void close() {
          synchronized (mDiskCacheLock) {
              if (mDiskLruCache != null) {
                  try {
                      if (!mDiskLruCache.isClosed()) {
                          mDiskLruCache.close();
                      }
                  } catch (Throwable e) {
                      LogUtils.e(e.getMessage(), e);
                  }
                  mDiskLruCache = null;
              }
          }
      }
  
      /**
       * @author Caij
       * 存放图片数据的包装对象
       */
      private class BitmapMeta {
          public FileInputStream inputStream;
          public byte[] data;
          public long expiryTimestamp;
      }
  
      /**
       * 流转化为bitmap， 而且根据config压缩图片
       * @param bitmapMeta
       * @param config
       * @return
       * @throws IOException
       */
      private Bitmap decodeBitmapMeta(BitmapMeta bitmapMeta, BitmapDisplayConfig config) throws IOException {
          if (bitmapMeta == null) return null;
          Bitmap bitmap = null;
          if (bitmapMeta.inputStream != null) {
          	//BitmapFromDescriptor这一块使用的是这个方法而不是直接用流， 在网上找了一些
          	//资料， 这种转化能避免一些莫名其妙的图片加载不出来的问题
              if (config == null || config.isShowOriginal()) {
              	//原图
                  bitmap = BitmapDecoder.decodeFileDescriptor(bitmapMeta.inputStream.getFD());
              } else {
              	//压缩图
                  bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(
                          bitmapMeta.inputStream.getFD(),
                          config.getBitmapMaxSize(),
                          config.getBitmapConfig());
              }
          } else if (bitmapMeta.data != null) {
              if (config == null || config.isShowOriginal()) {
                  bitmap = BitmapDecoder.decodeByteArray(bitmapMeta.data);
              } else {
                  bitmap = BitmapDecoder.decodeSampledBitmapFromByteArray(
                          bitmapMeta.data,
                          config.getBitmapMaxSize(),
                          config.getBitmapConfig());
              }
          }
          return bitmap;
      }
  
      private synchronized Bitmap rotateBitmapIfNeeded(String uri, BitmapDisplayConfig config, Bitmap bitmap) {
          Bitmap result = bitmap;
          if (config != null && config.isAutoRotation()) {
              File bitmapFile = this.getBitmapFileFromDiskCache(uri);
              if (bitmapFile != null && bitmapFile.exists()) {
                  ExifInterface exif = null;
                  try {
                      exif = new ExifInterface(bitmapFile.getPath());
                  } catch (Throwable e) {
                      return result;
                  }
                  int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
                  int angle = 0;
                  switch (orientation) {
                      case ExifInterface.ORIENTATION_ROTATE_90:
                          angle = 90;
                          break;
                      case ExifInterface.ORIENTATION_ROTATE_180:
                          angle = 180;
                          break;
                      case ExifInterface.ORIENTATION_ROTATE_270:
                          angle = 270;
                          break;
                      default:
                          angle = 0;
                          break;
                  }
                  if (angle != 0) {
                      Matrix m = new Matrix();
                      m.postRotate(angle);
                      result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
                      bitmap.recycle();
                      bitmap = null;
                  }
              }
          }
          return result;
      }
  
      public class MemoryCacheKey {
          private String uri;
          private String subKey;
  
          private MemoryCacheKey(String uri, BitmapDisplayConfig config) {
              this.uri = uri;
              this.subKey = config == null ? null : config.toString();
          }
  
          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
              if (!(o instanceof MemoryCacheKey)) return false;
  
              MemoryCacheKey that = (MemoryCacheKey) o;
  
              if (!uri.equals(that.uri)) return false;
  
              if (subKey != null && that.subKey != null) {
                  return subKey.equals(that.subKey);
              }
  
              return true;
          }
  
          @Override
          public int hashCode() {
              return uri.hashCode();
          }
      }
  }
  