/*******************************************************************************
* Copyright (c) 2012 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;
//TODO 与3.0不一样
import android.content.Context;
import android.view.ViewGroup;
import com.taobao.luaview.cache.AppCache;
import com.taobao.luaview.cache.LuaCache;
import com.taobao.luaview.debug.DebugConnection;
import com.taobao.luaview.global.LuaResourceFinder;
import com.taobao.luaview.global.LuaViewConfig;
import com.taobao.luaview.scriptbundle.LuaScriptManager;
import com.taobao.luaview.util.LogUtil;
import com.taobao.luaview.util.LuaUtil;
import org.luaj.vm2.lib.BaseLib;
import org.luaj.vm2.lib.DebugLib;
import org.luaj.vm2.lib.PackageLib;
import org.luaj.vm2.lib.ResourceFinder;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.ref.WeakReference;
import java.util.Stack;
/**
* Global environment used by luaj. Contains global variables referenced by executing lua.
* <p/>
* <p/>
* <h3>Constructing and Initializing Instances</h3>
* Typically, this is constructed indirectly by a call to
* {@link JsePlatform.standardGlobasl()} or {@link JmePlatform.standardGlobals()},
* and then used to load lua scripts for execution as in the following example.
* <pre> {@code
* Globals globals = JsePlatform.standardGlobals();
* globals.load( new StringReader("print 'hello'"), "main.lua" ).call();
* } </pre>
* The creates a complete global environment with the standard libraries loaded.
* <p/>
* For specialized circumstances, the Globals may be constructed directly and loaded
* with only those libraries that are needed, for example.
* <pre> {@code
* Globals globals = new Globals();
* globals.load( new BaseLib() );
* } </pre>
* <p/>
* <h3>Loading and Executing Lua Code</h3>
* Globals contains convenience functions to load and execute lua source code given a Reader.
* A simple example is:
* <pre> {@code
* globals.load(new StringReader("print 'hello'"), "main.lua" ).call();
* } </pre>
* <p/>
* <h3>Fine-Grained Control of Compiling and Loading Lua</h3>
* Executable LuaFunctions are created from lua code in several steps
* <ul>
* <li>find the resource using the platform's {@link ResourceFinder}
* <li>compile lua to lua bytecode using {@link Compiler}
* <li>load lua bytecode to a {@link LuaPrototpye} using {@link Undumper}
* <li>construct {@link LuaClosure} from {@link Prototype} with {@link Globals} using {@link Loader}
* </ul>
* <p/>
* There are alternate flows when the direct lua-to-Java bytecode compiling {@link LuaJC} is used.
* <ul>
* <li>compile lua to lua bytecode using {@link Compiler} or load precompiled code using {@link Undumper}
* <li>convert lua bytecode to equivalent Java bytecode using {@link LuaJC} that implements {@link Loader} directly
* </ul>
* <p/>
* <h3>Java Field</h3>
* Certain public fields are provided that contain the current values of important global state:
* <ul>
* <li>{@link STDIN} Current value for standard input in the laaded IoLib, if any.
* <li>{@link STDOUT} Current value for standard output in the loaded IoLib, if any.
* <li>{@link STDERR} Current value for standard error in the loaded IoLib, if any.
* <li>{@link finder} Current loaded {@link ResourceFinder}, if any.
* <li>{@link compiler} Current loaded {@link Compiler}, if any.
* <li>{@link undumper} Current loaded {@link Undumper}, if any.
* <li>{@link loader} Current loaded {@link Loader}, if any.
* </ul>
* <p/>
* <h3>Lua Environment Variables</h3>
* When using {@link JsePlatform} or {@link JmePlatform},
* these environment variables are created within the Globals.
* <ul>
* <li>"_G" Pointer to this Globals.
* <li>"_VERSION" String containing the version of luaj.
* </ul>
* <p/>
* <h3>Use in Multithreaded Environments</h3>
* In a multi-threaded server environment, each server thread should create one Globals instance,
* which will be logically distinct and not interfere with each other, but share certain
* static immutable resources such as class data and string data.
* <p/>
*
* @see org.luaj.vm2.lib.jse.JsePlatform
* @see org.luaj.vm2.lib.jme.JmePlatform
* @see LuaValue
* @see Compiler
* @see Loader
* @see Undumper
* @see ResourceFinder
* @see LuaC
* @see LuaJC
*/
public class Globals extends LuaTable {
public boolean isInited = false;
public boolean isRefreshContainerEnable = true;
/**
* Android parent view
*/
private WeakReference<ViewGroup> mRenderTarget;
public ViewGroup container;
private ViewGroup tmpContainer;
//containers
private Stack<ViewGroup> mContainers;
//luacaches
private LuaCache mLuaCache = new LuaCache();
/**
* The current default input stream.
*/
public InputStream STDIN = null;
/**
* The current default output stream.
*/
// public PrintStream STDOUT = System.out;
/**
* The current default error stream.
*/
// public PrintStream STDERR = System.err;
/**
* The installed ResourceFinder for looking files by name.
*/
private ResourceFinder finder;
public LuaResourceFinder getLuaResourceFinder() {
if (!(finder instanceof LuaResourceFinder)) {
finder = new LuaResourceFinder(getContext());
}
return (LuaResourceFinder) finder;
}
/**
* The currently running thread. Should not be changed by non-library code.
*/
public LuaThread running = new LuaThread(this);
/**
* The BaseLib instance loaded into this Globals
*/
public BaseLib baselib;
/**
* The PackageLib instance loaded into this Globals
*/
public PackageLib package_;
/**
* The DebugLib instance loaded into this Globals, or null if debugging is not enabled
*/
public DebugLib debuglib;
public DebugConnection debugConnection;//用作debug
private Boolean isStandardSyntax = null;
/**
* Interface for module that converts a Prototype into a LuaFunction with an environment.
*/
public interface Loader {
/**
* Convert the prototype into a LuaFunction with the supplied environment.
*/
LuaFunction load(Prototype prototype, String chunkname, LuaValue env) throws IOException;
}
/**
* Interface for module that converts lua source text into a prototype.
*/
public interface Compiler {
/**
* Compile lua source into a Prototype. The InputStream is assumed to be in UTF-8.
*/
Prototype compile(InputStream stream, String chunkname, boolean standardSyntax) throws IOException;
}
/**
* Interface for module that loads lua binary chunk into a prototype.
*/
public interface Undumper {
/**
* Load the supplied input stream into a prototype.
*/
Prototype undump(InputStream stream, String chunkname) throws IOException;
}
/**
* Check that this object is a Globals object, and return it, otherwise throw an error.
*/
public Globals checkglobals() {
return this;
}
/**
* The installed loader.
*
* @see Loader
*/
public Loader loader;
/**
* The installed compiler.
*
* @see Compiler
*/
public Compiler compiler;
/**
* The installed undumper.
*
* @see Undumper
*/
public Undumper undumper;
/**
* Convenience function for loading a file that is either binary lua or lua source.
*
* @param filename Name of the file to load.
* @return LuaValue that can be call()'ed or invoke()'ed.
* @throws LuaError if the file could not be loaded.
*/
public LuaValue loadfile(String filename) {
try {
LuaResourceFinder luaFinder = getLuaResourceFinder();
StringBuffer sb = new StringBuffer();
if (luaFinder != null) {
sb.append(luaFinder.getBaseBundlePath()).append(filename);
} else {
sb.append(this.hashCode()).append("@").append(filename);
}
return load(luaFinder.findResource(filename), sb.toString(), "bt", this);
} catch (Exception e) {
return error("load " + filename + ": " + e);
}
}
/**
* Convenience function to load a string value as a script. Must be lua source.
*
* @param script Contents of a lua script, such as "print 'hello, world.'"
* @param chunkname Name that will be used within the chunk as the source.
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @throws LuaError if the script could not be compiled.
*/
public LuaValue load(String script, String chunkname) {
return load(new StrReader(script), chunkname);
}
/**
* Convenience function to load a string value as a script. Must be lua source.
*
* @param script Contents of a lua script, such as "print 'hello, world.'"
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @throws LuaError if the script could not be compiled.
*/
public LuaValue load(String script) {
return load(new StrReader(script), script);
}
/**
* Load the content form a reader as a text file. Must be lua source.
* The source is converted to UTF-8, so any characters appearing in quoted literals
* above the range 128 will be converted into multiple bytes.
*/
public LuaValue load(Reader reader, String chunkname) {
return load(new UTF8Stream(reader), chunkname, "t", this);
}
/**
* Load the content form an input stream as a binary chunk or text file.
*/
public LuaValue load(InputStream is, String chunkname, String mode, LuaValue env) {
try {
if (debugConnection != null) {
is.mark(is.available() + 1);
byte[] bytes = new byte[is.available()];
is.read(bytes);
is.reset();
debugConnection.sendScript(bytes, chunkname);
}
Prototype p = null;
if (LuaViewConfig.isCachePrototype() && chunkname != null) {//给prototype解析加cache
p = AppCache.getPrototpyeCache().getLru(chunkname);
if (p == null) {
int psize = is.available();//prototype size
p = loadPrototype(is, chunkname, mode);
AppCache.getPrototpyeCache().putLru(chunkname, p, psize);
}
} else {
p = loadPrototype(is, chunkname, mode);
}
return loader.load(p, chunkname, env);
} catch (LuaError l) {
throw l;
} catch (Exception e) {
return error("load " + chunkname + ": " + e);
}
}
/**
* 直接加载一个prototype
*
* @param prototype
* @param chunkname
* @return
*/
public LuaValue load(Prototype prototype, String chunkname) {
return load(prototype, chunkname, this);
}
public LuaValue load(Prototype prototype, String chunkname, LuaValue env) {
try {
return loader.load(prototype, chunkname, env);
} catch (Exception e) {
LogUtil.d("[load prototype error]", chunkname, e);
return error("load " + chunkname + ": " + e);
}
}
/**
* Load lua source or lua binary from an input stream into a Prototype.
* The InputStream is either a binary lua chunk starting with the lua binary chunk signature,
* or a text input file. If it is a text input file, it is interpreted as a UTF-8 byte sequence.
*/
public Prototype loadPrototype(InputStream is, String chunkname, String mode) throws IOException {
if (mode.indexOf('b') >= 0) {
if (undumper == null)
error("No undumper.");
if (!is.markSupported())
is = new BufferedStream(is);
is.mark(4);
final Prototype p = undumper.undump(is, chunkname);
if (p != null)
return p;
is.reset();
}
if (mode.indexOf('t') >= 0) {
return compilePrototype(is, chunkname);
}
error("Failed to load prototype " + chunkname + " using mode '" + mode + "'");
return null;
}
/**
* Compile lua source from a Reader into a Prototype. The characters in the reader
* are converted to bytes using the UTF-8 encoding, so a string literal containing
* characters with codepoints 128 or above will be converted into multiple bytes.
*/
public Prototype compilePrototype(Reader reader, String chunkname) throws IOException {
return compilePrototype(new UTF8Stream(reader), chunkname);
}
/**
* Compile lua source from an InputStream into a Prototype.
* The input is assumed to be UTf-8, but since bytes in the range 128-255 are passed along as
* literal bytes, any ASCII-compatible encoding such as ISO 8859-1 may also be used.
*/
public Prototype compilePrototype(InputStream stream, String chunkname) throws IOException {
if (compiler == null)
error("No compiler.");
return compiler.compile(stream, chunkname, isStandardSyntax());
}
/**
* Function which yields the current thread.
*
* @param args Arguments to supply as return values in the resume function of the resuming thread.
* @return Values supplied as arguments to the resume() call that reactivates this thread.
*/
public Varargs yield(Varargs args) {
if (running == null || running.isMainThread())
throw new LuaError("cannot yield main thread");
final LuaThread.State s = running.state;
return s.lua_yield(args);
}
/**
* Reader implementation to read chars from a String in JME or JSE.
*/
static class StrReader extends Reader {
final String s;
int i = 0;
final int n;
StrReader(String s) {
this.s = s;
n = s.length();
}
public void close() throws IOException {
i = n;
}
public int read() throws IOException {
return i < n ? s.charAt(i++) : -1;
}
public int read(char[] cbuf, int off, int len) throws IOException {
int j = 0;
for (; j < len && i < n; ++j, ++i)
cbuf[off + j] = s.charAt(i);
return j > 0 || len == 0 ? j : -1;
}
}
/* Abstract base class to provide basic buffered input storage and delivery.
* This class may be moved to its own package in the future.
*/
abstract static class AbstractBufferedStream extends InputStream {
protected byte[] b;
protected int i = 0, j = 0;
protected AbstractBufferedStream(int buflen) {
this.b = new byte[buflen];
}
abstract protected int avail() throws IOException;
public int read() throws IOException {
int a = avail();
return (a <= 0 ? -1 : 0xff & b[i++]);
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public int read(byte[] b, int i0, int n) throws IOException {
int a = avail();
if (a <= 0) return -1;
final int n_read = Math.min(a, n);
System.arraycopy(this.b, i, b, i0, n_read);
i += n_read;
return n_read;
}
public long skip(long n) throws IOException {
final long k = Math.min(n, j - i);
i += k;
return k;
}
public int available() throws IOException {
return j - i;
}
}
/**
* Simple converter from Reader to InputStream using UTF8 encoding that will work
* on both JME and JSE.
* This class may be moved to its own package in the future.
*/
static class UTF8Stream extends AbstractBufferedStream {
private final char[] c = new char[32];
private final Reader r;
UTF8Stream(Reader r) {
super(96);
this.r = r;
}
protected int avail() throws IOException {
if (i < j) return j - i;
int n = r.read(c);
if (n < 0)
return -1;
if (n == 0) {
int u = r.read();
if (u < 0)
return -1;
c[0] = (char) u;
n = 1;
}
j = LuaString.encodeToUtf8(c, n, b, i = 0);
return j;
}
public void close() throws IOException {
r.close();
}
}
/**
* Simple buffered InputStream that supports mark.
* Used to examine an InputStream for a 4-byte binary lua signature,
* and fall back to text input when the signature is not found,
* as well as speed up normal compilation and reading of lua scripts.
* This class may be moved to its own package in the future.
*/
static class BufferedStream extends AbstractBufferedStream {
private final InputStream s;
public BufferedStream(InputStream s) {
this(128, s);
}
BufferedStream(int buflen, InputStream s) {
super(buflen);
this.s = s;
}
protected int avail() throws IOException {
if (i < j) return j - i;
if (j >= b.length) i = j = 0;
// leave previous bytes in place to implement mark()/reset().
int n = s.read(b, j, b.length - j);
if (n < 0)
return -1;
if (n == 0) {
int u = s.read();
if (u < 0)
return -1;
b[j] = (byte) u;
n = 1;
}
j += n;
return n;
}
public void close() throws IOException {
s.close();
}
public synchronized void mark(int n) {
if (i > 0 || n > b.length) {
byte[] dest = n > b.length ? new byte[n] : b;
System.arraycopy(b, i, dest, 0, j - i);
j -= i;
i = 0;
b = dest;
}
}
public boolean markSupported() {
return true;
}
public synchronized void reset() throws IOException {
i = 0;
}
}
//----------------------------------------------------------------------------------------------
/**
* call Lua Function
*
* @param funName
* @param objs
* @return
*/
public Object callLuaFunction(String funName, Object... objs) {
if (funName != null) {
final LuaValue callback = this.get(funName);
return LuaUtil.callFunction(callback, objs);
}
return LuaValue.NIL;
}
//----------------------------------------------------------------------------------------------
/**
* 设置是否标准语法,如果为null的时候则依据url来判断
*
* @param standardSyntax
*/
public void setUseStandardSyntax(boolean standardSyntax) {
isStandardSyntax = standardSyntax;
}
/**
* 是否是标准语法
*
* @return
*/
public boolean isStandardSyntax() {
if (isStandardSyntax != null) {
return isStandardSyntax;
} else {
final LuaResourceFinder finder = getLuaResourceFinder();
final String url = finder != null ? finder.getUri() : null;
return LuaScriptManager.isLuaStandardSyntaxUrl(url);
}
}
/**
* 延迟加载库
*
* @param binder
*/
public void tryLazyLoad(final LuaValue binder) {
load(binder);//load directly
}
/**
* 保存原有的container
*
* @param viewGroup
*/
@Deprecated
public void saveContainer(final ViewGroup viewGroup) {
if (this.container == null) {
this.tmpContainer = viewGroup;
this.container = viewGroup;
} else {
this.tmpContainer = this.container;
this.container = viewGroup;
}
}
/**
* 使用保存的container恢复
*/
@Deprecated
public void restoreContainer() {
this.container = this.tmpContainer;
}
/**
* push a container
*
* @param viewGroup
*/
public void pushContainer(final ViewGroup viewGroup) {
if (this.mContainers == null) {
this.mContainers = new Stack<ViewGroup>();
}
this.mContainers.push(this.container);
this.container = viewGroup;
}
/**
* pop a container
*/
public void popContainer() {
if (this.mContainers != null) {
this.container = this.mContainers.pop();
}
}
/**
* 设置LuaView,对LuaView弱引用
*
* @param renderTarget
*/
public void setRenderTarget(ViewGroup renderTarget) {
this.mRenderTarget = new WeakReference<ViewGroup>(renderTarget);
}
/**
* 获取LuaView
*
* @return
*/
public ViewGroup getRenderTarget() {
return this.mRenderTarget != null ? this.mRenderTarget.get() : null;
}
public LuaCache getLuaCache() {
return mLuaCache;
}
/**
* 获取context
*
* @return
*/
public Context getContext() {
ViewGroup renderTarget = this.mRenderTarget != null ? this.mRenderTarget.get() : null;
return renderTarget != null ? renderTarget.getContext() : null;
}
/**
* 获取App级别的Context
*
* @return
*/
public Context getAppContext() {
return getContext() != null ? getContext().getApplicationContext() : null;
}
/**
* 清除container缓存
*/
public void onDestroy() {
finder = null;
mRenderTarget = null;
container = null;
tmpContainer = null;
clearCache();
mLuaCache = null;
}
/**
* clear Cache
*/
public void clearCache() {
if (mLuaCache != null) {//清空cache数据
mLuaCache.clearCachedObjects();//从window中移除的时候清理数据(临时的数据)
}
LuaCache.clear();
}
}