content: /*
   * Created by LuaView.
   * Copyright (c) 2017, Alibaba Group. All rights reserved.
   *
   * This source code is licensed under the MIT.
   * For the full copyright and license information,please view the LICENSE file in the root directory of this source tree.
   */
  
  package com.taobao.luaview.global;
  
  import android.content.Context;
  import android.content.res.AssetManager;
  import android.graphics.Typeface;
  import android.graphics.drawable.Drawable;
  import android.text.TextUtils;
  
  import com.taobao.luaview.scriptbundle.LuaScriptManager;
  import com.taobao.luaview.scriptbundle.ScriptBundle;
  import com.taobao.luaview.scriptbundle.ScriptFile;
  import com.taobao.luaview.scriptbundle.asynctask.ScriptBundleLoadTask;
  import com.taobao.luaview.scriptbundle.asynctask.SimpleTask1;
  import com.taobao.luaview.scriptbundle.asynctask.delegate.ScriptBundleLoadDelegate;
  import com.taobao.luaview.util.AssetUtil;
  import com.taobao.luaview.util.DrawableUtil;
  import com.taobao.luaview.util.FileUtil;
  import com.taobao.luaview.util.IOUtil;
  import com.taobao.luaview.util.LogUtil;
  import com.taobao.luaview.util.ParamUtil;
  import com.taobao.luaview.util.TypefaceUtil;
  import com.taobao.luaview.view.imageview.BaseImageView;
  
  import org.luaj.vm2.lib.ResourceFinder;
  
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.io.InputStream;
  
  /**
   * 给定相对路径，找对应的资源（脚本、图片、字体等)
   *
   * @author song
   * @date 16/1/5
   */
  public class LuaResourceFinder implements ResourceFinder {
      public static final String DEFAULT_MAIN_ENTRY = "main.lua";//默认的脚本入口，在加载folder或者bundle的时候会默认加载该名称的脚本
      private static final String FILE_PATH_ANDROID_ASSET = "file:///android_asset/";
  
      private Context mContext;
  
      //内存脚本
      private ScriptBundle mScriptBundle;
  
      //加载的uri（可以是url，也可以是包名称）
      private String mUri;
  
      //基础scriptPath
      private String mBaseScriptFolderPath;//默认cache目录
      private String mBaseBundlePath;//Bundle文件系统路径
      private String mBaseAssetPath;//asset下路径
  
      //callback for drawable finder
      public interface DrawableFindCallback {
          public void onStart(String urlOrPath);
  
          public void onFinish(Drawable result);
      }
  
      public LuaResourceFinder(Context context) {
          if (context != null) {
              this.mContext = context.getApplicationContext();
          }
      }
  
      public void setScriptBundle(ScriptBundle scriptBundle) {
          mScriptBundle = scriptBundle;
      }
  
      public void setUri(String uri) {
          mUri = uri;
          mBaseScriptFolderPath = LuaScriptManager.getBaseScriptFolderPath();
          mBaseBundlePath = LuaScriptManager.buildScriptBundleFolderPath(uri);
          mBaseAssetPath = FileUtil.getAssetFolderPath(uri);//脚本默认放在asset目录下
      }
  
      public String getUri() {
          return mUri;
      }
  
      public String getBaseBundlePath() {
          return mBaseBundlePath;
      }
  
      /**
       * find Script in given name or path
       *
       * @param nameOrPath
       * @return
       */
      @Override
      public InputStream findResource(final String nameOrPath) {
          if (mScriptBundle != null && mScriptBundle.containsKey(nameOrPath)) {//from cache, 这里直接使用文件名称来处理
              LogUtil.d("[findResource-ScriptBundle]", nameOrPath);
              final ScriptFile scriptFile = mScriptBundle.getScriptFile(nameOrPath);
              return scriptFile.getInputStream();
          }
  
          if (LuaScriptManager.isLuaEncryptScript(nameOrPath)) {//.lv TODO 这里需要去掉，不再有本地lv文件
              return ScriptBundleLoadDelegate.loadEncryptScript(mContext, findFile(nameOrPath));
          } else {//.lua 或者 输入folder名字（其实是lvbundle的名字，如ppt440), 则加载 main.lua
              final String newName = LuaScriptManager.isLuaScript(nameOrPath) ? nameOrPath : DEFAULT_MAIN_ENTRY;//如果是脚本则加载，否则加载main.lua
              InputStream inputStream = ScriptBundleLoadDelegate.loadEncryptScript(mContext, findFile(LuaScriptManager.changeSuffix(newName, LuaScriptManager.POSTFIX_LV)));
              if (inputStream == null) {//如果.lv不存在，则尝试读取.lua
                  inputStream = findFile(newName);
              }
              return inputStream;
          }
      }
  
      /**
       * find drawable async
       *
       * @param nameOrPath
       * @param callback
       */
      public void findDrawable(final String nameOrPath, final DrawableFindCallback callback) {
          new SimpleTask1<Drawable>() {
              @Override
              protected void onPreExecute() {
                  super.onPreExecute();
                  if (callback != null) {
                      callback.onStart(nameOrPath);
                  }
              }
  
              @Override
              protected Drawable doInBackground(Object... params) {
                  return findDrawable(nameOrPath);
              }
  
              @Override
              protected void onPostExecute(Drawable drawable) {
                  super.onPostExecute(drawable);
                  if (callback != null) {
                      callback.onFinish(drawable);
                  }
              }
          }.executeInPool();
      }
  
      /**
       * find drawable async
       *
       * @param imageView
       * @param nameOrPath
       */
      public void findDrawable(final BaseImageView imageView, final String nameOrPath) {
          new SimpleTask1<Drawable>() {
              @Override
              protected void onPreExecute() {
                  super.onPreExecute();
                  if (imageView != null && nameOrPath != null) {
                      imageView.setTag(Constants.RES_LV_TAG_URL, nameOrPath);
                  }
              }
  
              @Override
              protected Drawable doInBackground(Object... params) {
                  return findDrawable(nameOrPath);
              }
  
              @Override
              protected void onPostExecute(Drawable drawable) {
                  super.onPostExecute(drawable);
                  if (imageView != null && nameOrPath != null && nameOrPath.equals(imageView.getTag(Constants.RES_LV_TAG_URL))) {
                      imageView.setImageDrawable(drawable);
                  }
              }
          }.executeInPool();
      }
  
      /**
       * 在 res 或者 asset 或者 文件系统 找drawable
       * TODO 异步
       *
       * @param nameOrPath
       * @return
       */
      public Drawable findDrawable(final String nameOrPath) {
          Drawable drawable = null;
          if (!TextUtils.isEmpty(nameOrPath)) {
              final String drawableName = FileUtil.hasPostfix(nameOrPath) ? nameOrPath : ParamUtil.getFileNameWithPostfix(nameOrPath, "png");//如果没有后缀，则处理成.png
  
              String filepath = buildPathInSdcardIfExists(drawableName);
              if (filepath != null) {//从filepath加载
                  drawable = DrawableUtil.getByPath(filepath);
              }
  
              if (drawable == null) {//从asset加载
                  filepath = buildPathInAssetsIfExists(drawableName);
                  if (filepath != null) {
                      drawable = DrawableUtil.getAssetByPath(mContext, drawableName);
                  }
              }
  
              if (drawable == null) {//直接使用name加载
                  drawable = DrawableUtil.getByName(mContext, nameOrPath);
              }
          }
          return drawable;
      }
  
      /**
       * 在 asset 或者 文件系统 找字体文件
       * TODO 优化
       *
       * @param nameOrPath
       * @return
       */
      public Typeface findTypeface(final String nameOrPath) {
          Typeface typeface = null;
          if (!TextUtils.isEmpty(nameOrPath)) {
              final String typefaceNameOrPath = FileUtil.hasPostfix(nameOrPath) ? nameOrPath : ParamUtil.getFileNameWithPostfix(nameOrPath, "ttf");//如果没有后缀，则处理成.ttf
              String filepath = buildPathInSdcardIfExists(typefaceNameOrPath);
  
              if (filepath != null) {//从文件系统加载
                  typeface = TypefaceUtil.create(filepath);
              }
  
              if (typeface == null) {//从asset下加载
                  filepath = buildPathInAssetsIfExists(typefaceNameOrPath);
                  if (filepath != null) {
                      typeface = TypefaceUtil.create(mContext, filepath);
                  }
              }
  
              if (typeface == null) {//default name
                  typeface = TypefaceUtil.create(mContext, nameOrPath);
              }
          }
          return typeface != null ? typeface : Typeface.DEFAULT;
      }
  
  
      /**
       * 在 文件系统 或者 asset下 找资源
       * TODO 异步
       *
       * @param nameOrPath
       * @return
       */
      public InputStream findFile(final String nameOrPath) {
          InputStream inputStream = null;
  
          if (!TextUtils.isEmpty(nameOrPath)) {
              String filepath = buildPathInSdcardIfExists(nameOrPath);
              if (filepath != null) {//从文件系统加载
                  inputStream = FileUtil.open(filepath);
              }
  
              if (inputStream == null) {//从asset下加载
                  filepath = buildPathInAssetsIfExists(nameOrPath);
                  if (filepath != null) {
                      inputStream = AssetUtil.open(mContext, filepath);
                  }
              }
  
              if(inputStream == null){//直接使用name加载
                  inputStream = AssetUtil.open(mContext, nameOrPath);
              }
          }
          return inputStream;
      }
  
      /**
       * exists
       *
       * @param nameOrPath
       * @return
       */
      public boolean exists(final String nameOrPath) {
          if (!TextUtils.isEmpty(nameOrPath)) {
              String fullPath = buildSecurePathInSdcard(nameOrPath);
              if (fullPath != null) {//文件
                  return FileUtil.exists(fullPath);
              } else {
                  fullPath = buildSecurePathInAssets(nameOrPath);
                  return AssetUtil.exists(mContext, fullPath);
              }
          }
          return false;
      }
  
      /**
       * 获取文件的完整路径
       *
       * @param nameOrPath
       * @return
       */
      public String buildFullPathInBundleOrAssets(final String nameOrPath) {
          if (!TextUtils.isEmpty(nameOrPath)) {
              String fullPath = buildPathInBundleFolder(nameOrPath);
              if (!FileUtil.exists(fullPath)) {
                  fullPath = buildPathInAssets(nameOrPath);
              }
              return fullPath;
          }
          return null;
      }
  
      /**
       * 找文件在SD卡的路径，如果存在在获取
       *
       * @param nameOrPath
       * @return
       */
      private String buildPathInSdcardIfExists(final String nameOrPath) {
          String filepath = buildPathInBundleFolder(nameOrPath);
          if (FileUtil.exists(filepath)) {//check bundle folder
              return filepath;
          }
  
          if (filepath == null) {//check script folder
              filepath = buildPathInRootFolder(nameOrPath);
              if (FileUtil.exists(filepath)) {
                  return filepath;
              }
          }
          return null;
      }
  
      /**
       * 获取安全路径（不一定在bundle下面，但必须在script目录下）
       *
       * @param nameOrPath
       * @return
       */
      public String buildSecurePathInSdcard(final String nameOrPath) {//主要用在文件操作
          String path = buildPathInBundleFolder(nameOrPath);
          if (path != null) {//处理../../../ TODO 这里如何应对全局路径
              final String canonicalPath = FileUtil.getCanonicalPath(path);
              if (canonicalPath != null && canonicalPath.startsWith(mBaseScriptFolderPath)) {
                  return path;
              } else {
                  LogUtil.e("[LuaView-Error buildSecurePathInSdcard error]", nameOrPath, "must in folder", mBaseScriptFolderPath);
                  return null;
              }
          }
          return null;
      }
  
      /**
       * build file path
       *
       * @param nameOrPath
       * @return
       */
      public String buildPathInBundleFolder(final String nameOrPath) {
          String result = null;
  
          if(nameOrPath != null && nameOrPath.startsWith("/")){//如果反斜杠开头，则直接返回，TODO 待优化
              return nameOrPath;
          }
  
          if (!TextUtils.isEmpty(mBaseBundlePath)) {
              if (!FileUtil.isContainsFolderPath(nameOrPath, mBaseBundlePath)) {//不带基础路径的情况
                  final String filePath = FileUtil.buildPath(mBaseBundlePath, nameOrPath);
                  LogUtil.d("[buildPathInBundleFolder-FileSystem]", filePath);
                  result = filePath;
              } else {
                  LogUtil.d("[buildPathInBundleFolder-FileSystem]", nameOrPath);
                  result = nameOrPath;
              }
          }
          return result;
      }
  
      /**
       * build file path
       *
       * @param nameOrPath
       * @return
       */
      public String buildPathInRootFolder(final String nameOrPath) {
          String result = null;
  
          if(nameOrPath != null && nameOrPath.startsWith("/")){//如果反斜杠开头，则直接返回，TODO 待优化
              return nameOrPath;
          }
  
          if (!TextUtils.isEmpty(mBaseScriptFolderPath)) {
              if (!FileUtil.isContainsFolderPath(nameOrPath, mBaseScriptFolderPath)) {//不带基础路径的情况
                  final String filePath = FileUtil.buildPath(mBaseScriptFolderPath, nameOrPath);
                  LogUtil.d("[buildPathInBundleFolder-FileSystem]", filePath);
                  result = filePath;
              } else {
                  LogUtil.d("[buildPathInBundleFolder-FileSystem]", nameOrPath);
                  result = nameOrPath;
              }
          }
          return result;
      }
  
  
      /**
       * 获取安全路径
       *
       * @param nameOrPath
       * @return
       */
      public String buildSecurePathInAssets(final String nameOrPath) {//主要用在文件操作
          String path = buildPathInAssets(nameOrPath);
          if (path != null) {//处理../../../
              final String canonicalPath = FileUtil.getCanonicalPath(path);
              return canonicalPath != null && canonicalPath.startsWith(mBaseAssetPath) ? path : null;
          }
          return null;
      }
  
      /**
       * build path in assets
       *
       * @param nameOrPath
       * @return
       */
      private String buildPathInAssetsIfExists(final String nameOrPath) {
          String filepath = buildPathInAssets(nameOrPath);
          if (AssetUtil.exists(mContext, filepath)) {
              return filepath;
          } else if (AssetUtil.exists(mContext, nameOrPath)) {
              return nameOrPath;
          }
          return null;
      }
  
      /**
       * 找文件在asset下的路径
       *
       * @param nameOrPath
       * @return
       */
      private String buildPathInAssets(final String nameOrPath) {
          String result = null;
  
          if(nameOrPath != null && nameOrPath.startsWith("/")){//如果反斜杠开头，则直接返回，TODO 待优化
              return nameOrPath;
          }
  
          if (!TextUtils.isEmpty(mBaseAssetPath) && !FileUtil.isContainsFolderPath(nameOrPath, mBaseAssetPath)) {//不带基础路径，或者不在asset下
              final String assetFilePath = FileUtil.buildPath(mBaseAssetPath, nameOrPath);
              LogUtil.d("[buildPathInAssets-Assets]", assetFilePath);
              result = assetFilePath;
          } else {
              LogUtil.d("[buildPathInAssets-Assets]", nameOrPath);
              result = nameOrPath;
          }
          return result;
      }
  
      public byte[] readFromAssets(String name) {
          AssetManager assetManager = this.mContext.getAssets();
          InputStream inputStream = null;
          try {
              inputStream = assetManager.open(name);
              return IOUtil.toBytes(inputStream);
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              try {
                  if (inputStream != null) {
                      inputStream.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
          return null;
      }
  }
  