All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.luaj.lib.PackageLib Maven / Gradle / Ivy

There is a newer version: 1.0.5
Show newest version
/*******************************************************************************
* Copyright (c) 2007 LuaJ. 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.lib;

import java.io.InputStream;
import java.io.PrintStream;
import java.util.Vector;

import org.luaj.vm.CallInfo;
import org.luaj.vm.LBoolean;
import org.luaj.vm.LFunction;
import org.luaj.vm.LString;
import org.luaj.vm.LTable;
import org.luaj.vm.LValue;
import org.luaj.vm.Lua;
import org.luaj.vm.LuaState;
import org.luaj.vm.Platform;


public class PackageLib extends LFunction {

	public static String DEFAULT_LUA_PATH = "?.lua";
	
	public static InputStream STDIN = null;
	public static PrintStream STDOUT = System.out;
	public static LTable      LOADED = null;

	private static final LString _M = new LString("_M");
	private static final LString _NAME = new LString("_NAME");	
	private static final LString _PACKAGE = new LString("_PACKAGE");	
	private static final LString _DOT = new LString(".");
	private static final LString _EMPTY = new LString("");
	private static final LString __INDEX = new LString("__index");
	private static final LString _LOADERS = new LString("loaders");
	private static final LString _PRELOAD = new LString("preload");
	private static final LString _PATH = new LString("path");
	private static final LValue  _SENTINEL = _EMPTY;
	
	private static final String[] NAMES = {
		"package",
		"module",
		"require",
		"loadlib",
		"seeall",
		"preload_loader",
		"lua_loader",
		"java_loader",
	};
	
	private static final int INSTALL        = 0;
	private static final int MODULE         = 1;
	private static final int REQUIRE        = 2;
	private static final int LOADLIB        = 3;
	private static final int SEEALL         = 4;
	private static final int PRELOAD_LOADER = 5;
	private static final int LUA_LOADER     = 6;
	private static final int JAVA_LOADER    = 7;
	
	// all functions in package share this environment 
	private static LTable pckg;
	
	public static void install( LTable globals ) {
		for ( int i=1; i<=REQUIRE; i++ )
			globals.put( NAMES[i], new PackageLib(i) );
		pckg = new LTable();
		for ( int i=LOADLIB; i<=SEEALL; i++ )
			pckg.put( NAMES[i], new PackageLib(i) );
		LOADED = new LTable();
		pckg.put( "loaded", LOADED );
		pckg.put( _PRELOAD, new LTable() );
		LTable loaders = new LTable(3,0);
		for ( int i=PRELOAD_LOADER; i<=JAVA_LOADER; i++ )
			loaders.luaInsertPos(0, new PackageLib(i) );
		pckg.put( "loaders", loaders );
		pckg.put( _PATH, new LString(DEFAULT_LUA_PATH) );
		globals.put( "package", pckg );
		setIsLoaded( "package", pckg );
	}

	/** Allow packages to mark themselves as loaded */
	public static void setIsLoaded(String name, LTable value) {
		LOADED.put(name, value);
	}

	public static void setLuaPath( String newLuaPath ) {
		pckg.put( _PATH, new LString(newLuaPath) );
	}
	
	private final int id;

	private PackageLib( int id ) {
		this.id = id;
	}
	
	public String toString() {
		return NAMES[id]+"()";
	}
	
	public int invoke( LuaState vm ) {
		switch ( id ) {
		case INSTALL:
			install(vm._G);
			return 0;
		case MODULE: 
			return module(vm);
		case REQUIRE: 
			return require(vm);
		case LOADLIB: 
			return loadlib(vm);
		case SEEALL: { 
			LTable t = vm.checktable(1);
			LTable m = t.luaGetMetatable();
			if ( m == null )
				t.luaSetMetatable(m = new LTable());
			m.put(__INDEX, vm._G);
			return 0;
		}
		case PRELOAD_LOADER: {
			return loader_preload(vm);
		}
		case LUA_LOADER: {
			return loader_Lua(vm);
		}
		case JAVA_LOADER: {
			return loader_Java(vm);
		}
		default:
			LuaState.vmerror( "bad package id" );
			return 0;
		}
	}
	
	
	// ======================== Module, Package loading =============================
	/**
	 * module (name [, ...])
	 * 
	 * Creates a module. If there is a table in package.loaded[name], this table
	 * is the module. Otherwise, if there is a global table t with the given
	 * name, this table is the module. Otherwise creates a new table t and sets
	 * it as the value of the global name and the value of package.loaded[name].
	 * This function also initializes t._NAME with the given name, t._M with the
	 * module (t itself), and t._PACKAGE with the package name (the full module
	 * name minus last component; see below). Finally, module sets t as the new
	 * environment of the current function and the new value of
	 * package.loaded[name], so that require returns t.
	 * 
	 * If name is a compound name (that is, one with components separated by
	 * dots), module creates (or reuses, if they already exist) tables for each
	 * component. For instance, if name is a.b.c, then module stores the module
	 * table in field c of field b of global a.
	 * 
	 * This function may receive optional options after the module name, where
	 * each option is a function to be applied over the module.
	 */
	public static int module(LuaState vm) {
		LString modname = vm.checklstring(1);
		int n = vm.gettop();
		LValue value = LOADED.luaGetTable(vm, modname);
		LTable module;
		if ( ! value.isTable() ) { /* not found? */
			
		    /* try global variable (and create one if it does not exist) */
			module = findtable( vm._G, modname );
			if ( module == null )
				vm.error( "name conflict for module '"+modname+"'" );
			LOADED.luaSetTable(vm, modname, module);
		} else {
			module = (LTable) value;
		}
		
		
		/* check whether table already has a _NAME field */
		LValue name = module.luaGetTable(vm, _NAME);
		if ( name.isNil() ) {
			modinit( vm, module, modname );
		}
		
		// set the environment of the current function
		CallInfo ci = vm.getStackFrame(0);
		ci.closure.env = module;
		
		// apply the functions
		for ( int i=2; i<=n; i++ ) {
			vm.pushvalue( i );   /* get option (a function) */
			vm.pushlvalue( module );  /* module */
			vm.call( 1, 0 );
		}
		
		// returns no results
		return 0;
	}

	/**
	 * 
	 * @param table the table at which to start the search
	 * @param fname the name to look up or create, such as "abc.def.ghi"
	 * @return the table for that name, possible a new one, or null if a non-table has that name already. 
	 */
	private static LTable findtable(LTable table, LString fname) {
		int b, e=(-1);
		do {
			e = fname.indexOf(_DOT, b=e+1 );
			if ( e < 0 )
				e = fname.m_length;
			LString key = fname.substring(b, e);
			LValue val = table.get(key);
			if ( val.isNil() ) { /* no such field? */
				LTable field = new LTable(); /* new table for field */
				table.put(key, field);
				table = field;
			} else if ( val.luaGetType() != Lua.LUA_TTABLE ) {  /* field has a non-table value? */
				return null;
			} else {
				table = (LTable) val;
			}
		} while ( e < fname.m_length );
		return table;
	}

	private static void modinit(LuaState vm, LTable module, LString modname) {
		/* module._M = module */
		module.luaSetTable(vm, _M, module);
		int e = modname.lastIndexOf(_DOT);
		module.luaSetTable(vm, _NAME, modname );
		module.luaSetTable(vm, _PACKAGE, (e<0? _EMPTY: modname.substring(0,e+1)) );
	}

	/** 
	 * 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.loaders array. By changing this array, 
	 * we can change how require looks for a module. The following explanation is based on the 
	 * default configuration for package.loaders.
	 *  
	 * 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 C loader 
	 * using the path stored in package.cpath. If that also fails, it tries an all-in-one loader 
	 * (see package.loaders).
	 * 
	 * Once a loader is found, require calls the loader with a single argument, modname. 
	 * If the loader returns any value, require assigns the returned value to package.loaded[modname]. 
	 * If the loader returns no 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 signals an error.
	 */	
	public int require( LuaState vm ) {
		LString name = vm.checklstring(1);
		LValue loaded = LOADED.luaGetTable(vm, name);
		if ( loaded.toJavaBoolean() ) {
			if ( loaded == _SENTINEL )
				vm.error("loop or previous error loading module '"+name+"'");
			vm.pushlvalue( loaded );
			return 1;
		}
		
		/* else must load it; iterate over available loaders */
		LValue val = pckg.luaGetTable(vm, _LOADERS);
		if ( ! val.isTable() )
			vm.error( "'package.loaders' must be a table" );
		LTable tbl = (LTable) val;
		Vector v = new Vector();
		for ( int i=1; true; i++ ) {
			LValue loader = tbl.get(i);
			if ( loader.isNil() ) {
		    	vm.error( "module '"+name+"' not found: "+v );
		    }
		    /* call loader with module name as argument */
			vm.pushlvalue(loader);
		    vm.pushlstring(name);
		    vm.call(1, 1);
		    if ( vm.isfunction(-1) )
		    	break;  /* module loaded successfully */
		    if ( vm.isstring(-1) )  /* loader returned error message? */
		    	v.addElement(vm.tolstring(-1));  /* accumulate it */
	    	vm.pop(1);
		}

		// load the module using the loader
		LOADED.luaSetTable(vm, name, _SENTINEL);
		vm.pushlstring( name );  /* pass name as argument to module */
		vm.call( 1, 1 ); /* run loaded module */
		if ( ! vm.isnil(-1) ) /* non-nil return? */
			LOADED.luaSetTable(vm, name, vm.topointer(-1) ); /* _LOADED[name] = returned value */
		LValue result = LOADED.luaGetTable(vm, name); 
		if ( result == _SENTINEL ) {   /* module did not set a value? */
			LOADED.luaSetTable(vm, name, result=LBoolean.TRUE ); /* _LOADED[name] = true */
		}
		vm.pushlvalue(result);
		return 1;
	}

	public static int loadlib( LuaState vm ) {
		vm.checkstring(1);
		vm.pushnil();
		vm.pushstring("dynamic libraries not enabled");
		vm.pushstring("absent");
		return 3;
	}


	private int loader_preload( LuaState vm ) {
		LString name = vm.tolstring(1);
		LValue preload = pckg.luaGetTable(vm, _PRELOAD);
		if ( ! preload.isTable() )
			vm.error("package.preload '"+name+"' must be a table");
		LValue val = preload.luaGetTable(vm, name);
		if ( val.isNil() )
			vm.pushstring("\n\tno field package.preload['"+name+"']");
		else
			vm.pushlvalue(val);
		return 1;
	}

	private int loader_Lua( LuaState vm ) {
		String name = vm.tostring(1);
		InputStream is = findfile( vm, name, _PATH );
		if ( is != null ) {
			String filename = vm.tostring(-1);
			if ( ! BaseLib.loadis(vm, is, filename) )
				loaderror( vm, filename );
		}
		return 1;
	}

	private int loader_Java( LuaState vm ) {
		String name = vm.tostring(1);
		Class c = null;
		LValue v = null;
		try {
			c = Class.forName(name);
			v = (LValue) c.newInstance();
			vm.pushlvalue( v );
		} catch ( ClassNotFoundException  cnfe ) {
			vm.pushstring("\n\tno class '"+name+"'" );
		} catch ( Exception e ) {
			vm.pushstring("\n\tjava load failed on '"+name+"', "+e );
		}
		return 1;
	}

	private InputStream findfile(LuaState vm, String name, LString pname) {
		Platform p = Platform.getInstance();
		LValue pkg = pckg.luaGetTable(vm, pname);
		if ( ! pkg.isString() )
			vm.error("package."+pname+" must be a string");
		String path = pkg.toJavaString();
		int e = -1;
		int n = path.length();
		StringBuffer sb = null;
		name = name.replace('.','/');
		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 = p.openFile(filename);
			if ( is != null ) {
				vm.pushstring(filename);
				return is;
			}
			
			// report error
			if ( sb == null )
				sb = new StringBuffer();
			sb.append( "\n\tno file '"+filename+"'");
		}
		
		// not found, push error on stack and return
		vm.pushstring(sb.toString());
		return null;
	}

	private static void loaderror(LuaState vm, String filename) {
		vm.error( "error loading module '"+vm.tostring(1)+"' from file '"+filename+"':\n\t"+vm.tostring(-1) );
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy