/*
* $Id: LuaObject.java,v 1.6 2006/12/22 14:06:40 thiago Exp $
* Copyright (C) 2003-2007 Kepler Project.
*
* 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.keplerproject.luajava;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.StringTokenizer;
/**
* This class represents a Lua object of any type. A LuaObject is constructed by a {@link LuaState} object using one of
* the four methods:
* <ul>
* <li>{@link LuaState#getLuaObject(String globalName)}</li>
* <li>{@link LuaState#getLuaObject(LuaObject parent, String name)}</li>
* <li>{@link LuaState#getLuaObject(LuaObject parent, Number name)}</li>
* <li>{@link LuaState#getLuaObject(LuaObject parent, LuaObject name)}</li>
* <li>{@link LuaState#getLuaObject(int index)}</li>
* </ul>
* The LuaObject will represent only the object itself, not a variable or a stack index, so when you change a string,
* remember that strings are immutable objects in Lua, and the LuaObject you have will represent the old one.
*
* <h2>Proxies</h2>
*
* LuaJava allows you to implement a class in Lua, like said before. If you want to create this proxy from Java, you
* should have a LuaObject representing the table that has the functions that implement the interface. From this
* LuaObject you can call the <code>createProxy(String implements)</code>. This method receives the string with the
* name of the interfaces implemented by the object separated by comma.
*
* @author Rizzato
* @author Thiago Ponte
*/
public class LuaObject
{
protected Integer ref;
protected LuaState L;
/**
* Creates a reference to an object in the variable globalName
*
* @param L
* @param globalName
*/
protected LuaObject(LuaState L, String globalName)
{
synchronized (L)
{
this.L = L;
L.getGlobal(globalName);
registerValue(-1);
L.pop(1);
}
}
/**
* Creates a reference to an object inside another object
*
* @param parent
* The Lua Table or Userdata that contains the Field.
* @param name
* The name that index the field
*/
protected LuaObject(LuaObject parent, String name) throws LuaException
{
synchronized (parent.getLuaState())
{
this.L = parent.getLuaState();
if (!parent.isTable() && !parent.isUserdata())
{
throw new LuaException("Object parent should be a table or userdata .");
}
parent.push();
L.pushString(name);
L.getTable(-2);
L.remove(-2);
registerValue(-1);
L.pop(1);
}
}
/**
* This constructor creates a LuaObject from a table that is indexed by a number.
*
* @param parent
* The Lua Table or Userdata that contains the Field.
* @param name
* The name (number) that index the field
* @throws LuaException
* When the parent object isn't a Table or Userdata
*/
protected LuaObject(LuaObject parent, Number name) throws LuaException
{
synchronized (parent.getLuaState())
{
this.L = parent.getLuaState();
if (!parent.isTable() && !parent.isUserdata())
throw new LuaException("Object parent should be a table or userdata .");
parent.push();
L.pushNumber(name.doubleValue());
L.getTable(-2);
L.remove(-2);
registerValue(-1);
L.pop(1);
}
}
/**
* This constructor creates a LuaObject from a table that is indexed by a LuaObject.
*
* @param parent
* The Lua Table or Userdata that contains the Field.
* @param name
* The name (LuaObject) that index the field
* @throws LuaException
* When the parent object isn't a Table or Userdata
*/
protected LuaObject(LuaObject parent, LuaObject name) throws LuaException
{
if (parent.getLuaState() != name.getLuaState())
throw new LuaException("LuaStates must be the same!");
synchronized (parent.getLuaState())
{
if (!parent.isTable() && !parent.isUserdata())
throw new LuaException("Object parent should be a table or userdata .");
this.L = parent.getLuaState();
parent.push();
name.push();
L.getTable(-2);
L.remove(-2);
registerValue(-1);
L.pop(1);
}
}
/**
* Creates a reference to an object in the given index of the stack
*
* @param L
* @param index
* of the object on the lua stack
*/
protected LuaObject(LuaState L, int index)
{
synchronized (L)
{
this.L = L;
registerValue(index);
}
}
/**
* Gets the Object's State
*/
public LuaState getLuaState()
{
return L;
}
/**
* Creates the reference to the object in the registry table
*
* @param index
* of the object on the lua stack
*/
private void registerValue(int index)
{
synchronized (L)
{
L.pushValue(index);
int key = L.Lref(LuaState.LUA_REGISTRYINDEX.intValue());
ref = new Integer(key);
}
}
protected void finalize()
{
try
{
synchronized (L)
{
if (L.getCPtrPeer() != 0)
L.LunRef(LuaState.LUA_REGISTRYINDEX.intValue(), ref.intValue());
}
}
catch (Exception e)
{
System.err.println("Unable to release object " + ref);
}
}
/**
* Pushes the object represented by <code>this<code> into L's stack
*/
public void push()
{
L.rawGetI(LuaState.LUA_REGISTRYINDEX.intValue(), ref.intValue());
}
public boolean isNil()
{
synchronized (L)
{
push();
boolean bool = L.isNil(-1);
L.pop(1);
return bool;
}
}
public boolean isBoolean()
{
synchronized (L)
{
push();
boolean bool = L.isBoolean(-1);
L.pop(1);
return bool;
}
}
public boolean isNumber()
{
synchronized (L)
{
push();
boolean bool = L.isNumber(-1);
L.pop(1);
return bool;
}
}
public boolean isString()
{
synchronized (L)
{
push();
boolean bool = L.isString(-1);
L.pop(1);
return bool;
}
}
public boolean isFunction()
{
synchronized (L)
{
push();
boolean bool = L.isFunction(-1);
L.pop(1);
return bool;
}
}
public boolean isJavaObject()
{
synchronized (L)
{
push();
boolean bool = L.isObject(-1);
L.pop(1);
return bool;
}
}
public boolean isJavaFunction()
{
synchronized (L)
{
push();
boolean bool = L.isJavaFunction(-1);
L.pop(1);
return bool;
}
}
public boolean isTable()
{
synchronized (L)
{
push();
boolean bool = L.isTable(-1);
L.pop(1);
return bool;
}
}
public boolean isUserdata()
{
synchronized (L)
{
push();
boolean bool = L.isUserdata(-1);
L.pop(1);
return bool;
}
}
public int type()
{
synchronized (L)
{
push();
int type = L.type(-1);
L.pop(1);
return type;
}
}
public boolean getBoolean()
{
synchronized (L)
{
push();
boolean bool = L.toBoolean(-1);
L.pop(1);
return bool;
}
}
public double getNumber()
{
synchronized (L)
{
push();
double db = L.toNumber(-1);
L.pop(1);
return db;
}
}
public String getString()
{
synchronized (L)
{
push();
String str = L.toString(-1);
L.pop(1);
return str;
}
}
public Object getObject() throws LuaException
{
synchronized (L)
{
push();
Object obj = L.getObjectFromUserdata(-1);
L.pop(1);
return obj;
}
}
/**
* If <code>this<code> is a table or userdata tries to set
* a field value.
*/
public LuaObject getField(String field) throws LuaException
{
return L.getLuaObject(this, field);
}
/**
* Calls the object represented by <code>this</code> using Lua function pcall.
*
* @param args -
* Call arguments
* @param nres -
* Number of objects returned
* @return Object[] - Returned Objects
* @throws LuaException
*/
public Object[] call(Object[] args, int nres) throws LuaException
{
synchronized (L)
{
if (!isFunction() && !isTable() && !isUserdata())
throw new LuaException("Invalid object. Not a function, table or userdata .");
int top = L.getTop();
push();
int nargs;
if (args != null)
{
nargs = args.length;
for (int i = 0; i < nargs; i++)
{
Object obj = args[i];
L.pushObjectValue(obj);
}
}
else
nargs = 0;
int err = L.pcall(nargs, nres, 0);
if (err != 0)
{
String str;
if (L.isString(-1))
{
str = L.toString(-1);
L.pop(1);
}
else
str = "";
if (err == LuaState.LUA_ERRRUN.intValue())
{
str = "Runtime error. " + str;
}
else if (err == LuaState.LUA_ERRMEM.intValue())
{
str = "Memory allocation error. " + str;
}
else if (err == LuaState.LUA_ERRERR.intValue())
{
str = "Error while running the error handler function. " + str;
}
else
{
str = "Lua Error code " + err + ". " + str;
}
throw new LuaException(str);
}
if (nres == LuaState.LUA_MULTRET.intValue())
nres = L.getTop() - top;
if (L.getTop() - top < nres)
{
throw new LuaException("Invalid Number of Results .");
}
Object[] res = new Object[nres];
for (int i = nres; i > 0; i--)
{
res[i - 1] = L.toJavaObject(-1);
L.pop(1);
}
return res;
}
}
/**
* Calls the object represented by <code>this</code> using Lua function pcall. Returns 1 object
*
* @param args -
* Call arguments
* @return Object - Returned Object
* @throws LuaException
*/
public Object call(Object[] args) throws LuaException
{
return call(args, 1)[0];
}
public String toString()
{
synchronized (L)
{
try
{
if (isNil())
return "nil";
else if (isBoolean())
return String.valueOf(getBoolean());
else if (isNumber())
return String.valueOf(getNumber());
else if (isString())
return getString();
else if (isFunction())
return "Lua Function";
else if (isJavaObject())
return getObject().toString();
else if (isUserdata())
return "Userdata";
else if (isTable())
return "Lua Table";
else if (isJavaFunction())
return "Java Function";
else
return null;
}
catch (LuaException e)
{
return null;
}
}
}
/**
* Function that creates a java proxy to the object represented by <code>this</code>
*
* @param implem
* Interfaces that are implemented, separated by <code>,</code>
*/
public Object createProxy(String implem) throws ClassNotFoundException, LuaException
{
synchronized (L)
{
if (!isTable())
throw new LuaException("Invalid Object. Must be Table.");
StringTokenizer st = new StringTokenizer(implem, ",");
Class[] interfaces = new Class[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++)
interfaces[i] = Class.forName(st.nextToken());
InvocationHandler handler = new LuaInvocationHandler(this);
return Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces, handler);
}
}
}