content: /*******************************************************************************
   * Copyright (c) 2010-2011 Luaj.org. All rights reserved.
   * <p/>
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   * <p/>
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   * <p/>
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   ******************************************************************************/
  package org.luaj.vm2.lib;
  
  //TODO 与3.0不一样
  
  import com.taobao.luaview.util.LogUtil;
  
  import org.luaj.vm2.Globals;
  import org.luaj.vm2.LuaFunction;
  import org.luaj.vm2.LuaString;
  import org.luaj.vm2.LuaTable;
  import org.luaj.vm2.LuaValue;
  import org.luaj.vm2.Varargs;
  
  import java.io.InputStream;
  
  /**
   * Subclass of {@link LibFunction} which implements the lua standard package and module
   * library functions.
   * <p/>
   * <h3>Lua Environment Variables</h3>
   * The following variables are available to lua scrips when this library has been loaded:
   * <ul>
   * <li><code>"package.loaded"</code> Lua table of loaded modules.
   * <li><code>"package.path"</code> Search path for lua scripts.
   * <li><code>"package.preload"</code> Lua table of uninitialized preload functions.
   * <li><code>"package.searchers"</code> Lua table of functions that search for object to load.
   * </ul>
   * <p/>
   * <h3>Java Environment Variables</h3>
   * These Java environment variables affect the library behavior:
   * <ul>
   * <li><code>"luaj.package.path"</code> Initial value for <code>"package.path"</code>.  Default value is <code>"?.lua"</code>
   * </ul>
   * <p/>
   * <h3>Loading</h3>
   * Typically, this library is included as part of a call to either
   * {@link JsePlatform#standardGlobals()} or {@link JmePlatform#standardGlobals()}
   * <pre> {@code
   * Globals globals = JsePlatform.standardGlobals();
   * System.out.println( globals.get("require").call"foo") );
   * } </pre>
   * <p/>
   * To instantiate and use it directly,
   * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as:
   * <pre> {@code
   * Globals globals = new Globals();
   * globals.load(new JseBaseLib());
   * globals.load(new PackageLib());
   * System.out.println( globals.get("require").call("foo") );
   * } </pre>
   * <h3>Limitations</h3>
   * This library has been implemented to match as closely as possible the behavior in the corresponding library in C.
   * However, the default filesystem search semantics are different and delegated to the bas library
   * as outlined in the {@link BaseLib} and {@link JseBaseLib} documentation.
   * <p/>
   *
   * @see LibFunction
   * @see BaseLib
   * @see JseBaseLib
   * @see JsePlatform
   * @see JmePlatform
   * @see <a href="http://www.lua.org/manual/5.2/manual.html#6.3">Lua 5.2 Package Lib Reference</a>
   */
  public class PackageLib extends TwoArgFunction {
  
      /**
       * The default value to use for package.path.  This can be set with the system property
       * <code>"luaj.package.path"</code>, and is <code>"?.lua"</code> by default.
       */
      public static String DEFAULT_LUA_PATH;
  
      static {
          try {
              DEFAULT_LUA_PATH = System.getProperty("luaj.package.path");
          } catch (Exception e) {
              LogUtil.i(e.toString());
  //			System.out.println(e.toString());
          }
          if (DEFAULT_LUA_PATH == null)
              DEFAULT_LUA_PATH = "?.lua";
      }
  
      private static final LuaString _LOADED = valueOf("loaded");
      private static final LuaString _LOADLIB = valueOf("loadlib");
      private static final LuaString _PRELOAD = valueOf("preload");
      private static final LuaString _PATH = valueOf("path");
      private static final LuaString _SEARCHPATH = valueOf("searchpath");
      private static final LuaString _SEARCHERS = valueOf("searchers");
  
      /**
       * The globals that were used to load this library.
       */
      Globals globals;
  
      /**
       * The table for this package.
       */
      LuaTable package_;
  
      /**
       * Loader that loads from {@link preload} table if found there
       */
      public preload_searcher preload_searcher;
  
      /**
       * Loader that loads as a lua script using the lua path currently in {@link path}
       */
      public lua_searcher lua_searcher;
  
      /**
       * Loader that loads as a Java class.  Class must have public constructor and be a LuaValue.
       */
      public java_searcher java_searcher;
  
      private static final LuaString _SENTINEL = valueOf("\u0001");
  
      private static final String FILE_SEP = System.getProperty("file.separator");
  
      public PackageLib() {
      }
  
      public LuaValue call(LuaValue modname, LuaValue env) {
          globals = env.checkglobals();
          globals.set("require", new require());
          package_ = new LuaTable();
          package_.set(_LOADED, new LuaTable());
          package_.set(_PRELOAD, new LuaTable());
          package_.set(_PATH, LuaValue.valueOf(DEFAULT_LUA_PATH));
          package_.set(_LOADLIB, new loadlib());
          package_.set(_SEARCHPATH, new searchpath());
          LuaTable searchers = new LuaTable();
          searchers.set(1, preload_searcher = new preload_searcher());
          searchers.set(2, lua_searcher = new lua_searcher());
          searchers.set(3, java_searcher = new java_searcher());
          package_.set(_SEARCHERS, searchers);
          package_.get(_LOADED).set("package", package_);
          env.set("package", package_);
          globals.package_ = this;
          return env;
      }
  
      /**
       * Allow packages to mark themselves as loaded
       */
      public void setIsLoaded(String name, LuaTable value) {
          package_.get(_LOADED).set(name, value);
      }
  
  
      /**
       * Set the lua path used by this library instance to a new value.
       * Merely sets the value of {@link path} to be used in subsequent searches.
       */
      public void setLuaPath(String newLuaPath) {
          package_.set(_PATH, LuaValue.valueOf(newLuaPath));
      }
  
      public String tojstring() {
          return "package";
      }
  
      // ======================== Package loading =============================
  
      /**
       * require (modname)
       * <p/>
       * Loads the given module. The function starts by looking into the package.loaded table
       * to determine whether modname is already loaded. If it is, then require returns the value
       * stored at package.loaded[modname]. Otherwise, it tries to find a loader for the module.
       * <p/>
       * To find a loader, require is guided by the package.searchers sequence.
       * By changing this sequence, we can change how require looks for a module.
       * The following explanation is based on the default configuration for package.searchers.
       * <p/>
       * First require queries package.preload[modname]. If it has a value, this value
       * (which should be a function) is the loader. Otherwise require searches for a Lua loader using
       * the path stored in package.path. If that also fails, it searches for a Java loader using
       * the classpath, using the public default constructor, and casting the instance to LuaFunction.
       * <p/>
       * Once a loader is found, require calls the loader with two arguments: modname and an extra value
       * dependent on how it got the loader. If the loader came from a file, this extra value is the file name.
       * If the loader is a Java instance of LuaFunction, this extra value is the environment.
       * If the loader returns any non-nil value, require assigns the returned value to package.loaded[modname].
       * If the loader does not return a non-nil value and has not assigned any value to package.loaded[modname],
       * then require assigns true to this entry.
       * In any case, require returns the final value of package.loaded[modname].
       * <p/>
       * If there is any error loading or running the module, or if it cannot find any loader for the module,
       * then require raises an error.
       */
      public class require extends OneArgFunction {
          public LuaValue call(LuaValue arg) {
              LuaString name = arg.checkstring();
              LuaValue loaded = package_.get(_LOADED);
              LuaValue result = loaded.get(name);
              if (result.toboolean()) {
                  if (result == _SENTINEL)
                      error("loop or previous error loading module '" + name + "'");
                  return result;
              }/* else if (globals != null && (result = globals.lazyLoad(name.checkjstring())) != null){//TODO add by song, 如果是加载自定义的内容则使用globals加载
                  loaded.set(name, _SENTINEL);
                  return result;
              }*/
  
  			/* else must load it; iterate over available loaders */
              LuaTable tbl = package_.get(_SEARCHERS).checktable();
              StringBuffer sb = new StringBuffer();
              Varargs loader = null;
              for (int i = 1; true; i++) {
                  LuaValue searcher = tbl.get(i);
                  if (searcher.isnil()) {
                      error("module '" + name + "' not found: " + name + sb);
                  }
  
  			    /* call loader with module name as argument */
                  loader = searcher.invoke(name);
                  if (loader.isfunction(1))
                      break;
                  if (loader.isstring(1))
                      sb.append(loader.tojstring(1));
              }
  
              // load the module using the loader
              loaded.set(name, _SENTINEL);
              try {
                  result = loader.arg1().call(name, loader.arg(2));
                  if (!result.isnil())
                      loaded.set(name, result);
                  else if ((result = loaded.get(name)) == _SENTINEL)
                      loaded.set(name, result = LuaValue.TRUE);
  
                  return result;
              } catch (Exception e){
                  LogUtil.e("[PackageLib]", name, "load failed!", e);
                  return NIL;
              }
          }
      }
  
      public static class loadlib extends VarArgFunction {
          public Varargs loadlib(Varargs args) {
              args.checkstring(1);
              return varargsOf(NIL, valueOf("dynamic libraries not enabled"), valueOf("absent"));
          }
      }
  
      public class preload_searcher extends VarArgFunction {
          public Varargs invoke(Varargs args) {
              LuaString name = args.checkstring(1);
              LuaValue val = package_.get(_PRELOAD).get(name);
              return val.isnil() ?
                      valueOf("\n\tno field package.preload['" + name + "']") :
                      val;
          }
      }
  
      public class lua_searcher extends VarArgFunction {
          public Varargs invoke(Varargs args) {
              LuaString name = args.checkstring(1);
              InputStream is = null;
  
              // get package path
              LuaValue path = package_.get(_PATH);
              if (!path.isstring())
                  return valueOf("package.path is not a string");
  
              // get the searchpath function.
              Varargs v = package_.get(_SEARCHPATH).invoke(varargsOf(name, path));
  
              // Did we get a result?
              if (!v.isstring(1))
                  return v.arg(2).tostring();
              LuaString filename = v.arg1().strvalue();
  
              // Try to load the file.
              v = globals.loadfile(filename.tojstring());
              if (v.arg1().isfunction())
                  return LuaValue.varargsOf(v.arg1(), filename);
  
              // report error
              return varargsOf(NIL, valueOf("'" + filename + "': " + v.arg(2).tojstring()));
          }
      }
  
      public class searchpath extends VarArgFunction {
          public Varargs invoke(Varargs args) {
              String name = args.checkjstring(1);
              String path = args.checkjstring(2);
              String sep = args.optjstring(3, ".");
              String rep = args.optjstring(4, FILE_SEP);
  
              // check the path elements
              int e = -1;
              int n = path.length();
              StringBuffer sb = null;
              name = name.replace(sep.charAt(0), rep.charAt(0));
              while (e < n) {
  
                  // find next template
                  int b = e + 1;
                  e = path.indexOf(';', b);
                  if (e < 0)
                      e = path.length();
                  String template = path.substring(b, e);
  
                  // create filename
                  int q = template.indexOf('?');
                  String filename = template;
                  if (q >= 0) {
                      filename = template.substring(0, q) + name + template.substring(q + 1);
                  }
  
                  // try opening the file
                  InputStream is = globals.getLuaResourceFinder().findResource(filename);//modify by song
                  if (is != null) {
                      try {
                          is.close();
                      } catch (java.io.IOException ioe) {
                      }
                      return valueOf(filename);
                  }
  
                  // report error
                  if (sb == null)
                      sb = new StringBuffer();
                  sb.append("\n\t" + filename);
              }
              return varargsOf(NIL, valueOf(sb.toString()));
          }
      }
  
      public class java_searcher extends VarArgFunction {
          public Varargs invoke(Varargs args) {
              String name = args.checkjstring(1);
              String classname = toClassname(name);
              Class c = null;
              LuaValue v = null;
              try {
                  c = Class.forName(classname);
                  v = (LuaValue) c.newInstance();
                  if (v.isfunction())
                      ((LuaFunction) v).initupvalue1(globals);
                  return varargsOf(v, globals);
              } catch (ClassNotFoundException cnfe) {
                  return valueOf("\n\tno class '" + classname + "'");
              } catch (Exception e) {
                  return valueOf("\n\tjava load failed on '" + classname + "', " + e);
              }
          }
      }
  
      /**
       * Convert lua filename to valid class name
       */
      public static final String toClassname(String filename) {
          int n = filename.length();
          int j = n;
          if (filename.endsWith(".lua"))
              j -= 4;
          for (int k = 0; k < j; k++) {
              char c = filename.charAt(k);
              if ((!isClassnamePart(c)) || (c == '/') || (c == '\\')) {
                  StringBuffer sb = new StringBuffer(j);
                  for (int i = 0; i < j; i++) {
                      c = filename.charAt(i);
                      sb.append(
                              (isClassnamePart(c)) ? c :
                                      ((c == '/') || (c == '\\')) ? '.' : '_');
                  }
                  return sb.toString();
              }
          }
          return n == j ? filename : filename.substring(0, j);
      }
  
      private static final boolean isClassnamePart(char c) {
          if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
              return true;
          switch (c) {
              case '.':
              case '$':
              case '_':
                  return true;
              default:
                  return false;
          }
      }
  }
  