|  | /******************************************************************************* | 
|  | * Copyright (c) 2010-2011 Luaj.org. All rights reserved. | 
|  | * | 
|  | * 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: | 
|  | * | 
|  | * The above copyright notice and this permission notice shall be included in | 
|  | * all copies or substantial portions of the Software. | 
|  | * | 
|  | * 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; | 
|  |  | 
|  | import java.io.InputStream; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | /** | 
|  | * Subclass of {@link LibFunction} which implements the lua standard package and module | 
|  | * library functions. | 
|  | * | 
|  | * <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> | 
|  | * | 
|  | * <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> | 
|  | * | 
|  | * <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 = System.getProperty("luaj.package.path"); | 
|  | static { | 
|  | 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) | 
|  | * | 
|  | * 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. | 
|  | * | 
|  | * 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. | 
|  | * | 
|  | * 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. | 
|  | * | 
|  | * 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]. | 
|  | * | 
|  | * 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 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); | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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.FINDER.findResource(filename); | 
|  | 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; | 
|  | } | 
|  | } | 
|  | } |