org.luaj.vm.LTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of luaj-j2me Show documentation
Show all versions of luaj-j2me Show documentation
Luaj 1.0.5 for the j2me platform
/*******************************************************************************
* 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.vm;
import java.util.Vector;
import org.luaj.vm.LFunction;
import org.luaj.vm.LInteger;
import org.luaj.vm.LNil;
import org.luaj.vm.LString;
import org.luaj.vm.LValue;
import org.luaj.vm.Lua;
import org.luaj.vm.LuaErrorException;
import org.luaj.vm.LuaState;
/**
* Simple implementation of table structure for Lua VM. Maintains both an array
* part and a hash part. Does not attempt to achieve the same performance as the
* C version.
*
* Java code can put values in the table or get values out (bypassing the
* metatable, if there is one) using put() and get(). There are specializations
* of put() and get() for integers and Strings to avoid allocating wrapper
* objects when possible.
*
* remove() methods are private: setting a key's value to nil is the correct way
* to remove an entry from the table.
*
*
*/
public class LTable extends LValue {
protected Object[] array;
protected LValue[] hashKeys;
protected Object[] hashValues;
private int hashEntries;
private LTable m_metatable;
private static final int MIN_HASH_CAPACITY = 2;
private static final LValue[] NONE = {};
/** Construct an empty LTable with no initial capacity. */
public LTable() {
array = NONE;
hashKeys = NONE;
}
/**
* Construct an empty LTable that is expected to contain entries with keys
* in the range 1 .. narray and nhash non-integer keys.
*/
public LTable( int narray, int nhash ) {
if ( nhash > 0 && nhash < MIN_HASH_CAPACITY )
nhash = MIN_HASH_CAPACITY;
array = new Object[narray];
hashKeys = new LValue[nhash];
hashValues = new Object[nhash];
}
public boolean isTable() {
return true;
}
/** Get capacity of hash part */
public int getArrayCapacity() {
return array.length;
}
/** Get capacity of hash part */
public int getHashCapacity() {
return hashKeys.length;
}
/**
* Return total number of keys mapped to non-nil values. Not to be confused
* with luaLength, which returns some number n such that the value at n+1 is
* nil.
*
* @deprecated this is not scalable. Does a linear search through the table. Use luaLength() instead.
*/
public int size() {
int count = 0;
for ( int i=array.length; --i>=0; )
if ( array[i] != null )
count++;
for ( int i=hashKeys.length; --i>=0; )
if ( hashKeys[i] != null )
count++;
return count;
}
/**
* Generic put method for all types of keys, but does not use the metatable.
*/
public void put( LValue key, LValue val ) {
if ( key.isInteger() ) {
int pos = key.toJavaInt() - 1;
int n = array.length;
if ( pos>=0 && pos<=n ) {
if ( pos == n )
expandArrayPart();
array[pos] = normalizePut(val);
return;
}
}
hashSet( key, normalizePut(val) );
}
/**
* Method for putting an integer-keyed value. Bypasses the metatable, if
* any.
*/
public void put( int key, LValue val ) {
int pos = key - 1;
int n = array.length;
if ( pos>=0 && pos<=n ) {
if ( pos == n )
expandArrayPart();
array[pos] = normalizePut(val);
} else {
hashSet( LInteger.valueOf(key), normalizePut(val) );
}
}
/**
* Utility method for putting a string-keyed value directly, typically for
* initializing a table. Bypasses the metatable, if any.
*/
public void put( String key, LValue val ) {
hashSet( LString.valueOf(key), normalizePut(val) );
}
/**
* Utility method for putting a string key, int value directly, typically for
* initializing a table. Bypasses the metatable, if any.
*/
public void put( String key, int val ) {
hashSet( LString.valueOf(key), LInteger.valueOf(val) );
}
/**
* Expand the array part of the backing for more values to fit in.
*/
private void expandArrayPart() {
int n = array.length;
int m = Math.max(2,n*2);
arrayExpand(m);
for ( int i=n; i0 && ikey<=array.length )
return normalizeGet(array[ikey-1]);
}
return normalizeGet(hashGet(key));
}
/** Utility method for retrieving an integer-keyed value */
public LValue get( int key ) {
return normalizeGet( key>0 && key<=array.length?
array[key-1]:
hashGet(LInteger.valueOf(key)) );
}
/** Check for null, and convert to nilor leave alone
*/
protected LValue normalizeGet(Object val) {
return val==null? LNil.NIL: (LValue) val;
}
/**
* Return true if the table contains an entry with the given key,
* false if not. Ignores the metatable.
*/
public boolean containsKey( LValue key ) {
if ( key.isInteger() ) {
int ikey = key.toJavaInt();
if ( ikey>0 && ikey<=array.length )
return null != array[ikey-1];
}
return null != hashGet(key);
}
/**
* Return true if the table contains an entry with the given integer-valued key,
* false if not. Ignores the metatable.
*/
public boolean containsKey( int key ) {
return (key>0 && key<=array.length?
array[key-1] != null:
(hashKeys.length>0 && hashKeys[hashFindSlot(LInteger.valueOf(key))]!=null));
}
private static final int MAX_KEY = 0x3fffffff;
/**
* Try to find a boundary in table `t'. A `boundary' is an integer index
* such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
*/
public int luaLength() {
// find `i' and `j' such that i is present and j is not
int i = 0;
int j = array.length;
if (j<=0 || containsKey(j)) {
if ( hashKeys.length == 0 )
return j;
for ( ++j; containsKey(j) && j < MAX_KEY; j*=2 )
i = j;
}
// binary search
while ( j - i > 1) {
int m = (i+j) / 2;
if ( ! containsKey(m) )
j = m;
else
i = m;
}
return i;
}
/** Valid for tables */
public LTable luaGetMetatable() {
return this.m_metatable;
}
/** Valid for tables */
public LTable luaSetMetatable(LValue metatable) {
if ( m_metatable != null && m_metatable.containsKey(TM_METATABLE) )
throw new LuaErrorException("cannot change a protected metatable");
if ( metatable == null || metatable.isNil() )
this.m_metatable = null;
else if ( metatable.luaGetType() == Lua.LUA_TTABLE ) {
org.luaj.vm.LTable t = (org.luaj.vm.LTable) metatable;
LValue m = t.get(TM_MODE);
if ( m.isString() && m.toJavaString().indexOf('v')>=0 ) {
LTable w = new LWeakTable(this);
w.m_metatable = t;
return w;
}
this.m_metatable = t;
} else {
throw new LuaErrorException("not a table: "+metatable.luaGetTypeName());
}
return this;
}
public String toJavaString() {
return "table: "+id();
}
public int luaGetType() {
return Lua.LUA_TTABLE;
}
/**
* Helper method to get all the keys in this table in an array. Meant to be
* used instead of keys() (which returns an enumeration) when an array is
* more convenient. Note that for a very large table, getting an Enumeration
* instead would be more space efficient.
*
* @deprecated this is not scalable. Does a linear search through the table.
*/
public LValue[] getKeys() {
int n = array.length;
int o = hashKeys.length;
LValue k;
Vector v = new Vector();
// array parts
for ( int pos=0; pos n )
return LNil.NIL;
LValue removed = get(ikey);
LValue replaced;
do {
put(ikey, replaced=get(ikey+1));
ikey++;
} while ( ! replaced.isNil() );
return removed;
}
/**
* Returns the largest positive numerical index of the given table,
* or zero if the table has no positive numerical indices.
* (To do its job this function does a linear traversal of the whole table.)
* @return LValue that is the largest int
*/
public LValue luaMaxN() {
int n = array.length;
int m = hashKeys.length;
int r = Integer.MIN_VALUE;
// array part
for ( int i=n; --i>=0; ) {
if ( array[i] != null ) {
r = i+1;
break;
}
}
// hash part
for ( int i=0; i r )
r = k;
}
}
return LInteger.valueOf( r == Integer.MIN_VALUE? 0: r );
}
// ----------------- sort support -----------------------------
//
// implemented heap sort from wikipedia
//
public void luaSort(LuaState vm, LValue compare) {
heapSort(luaLength(), vm, compare);
}
private void heapSort(int count, LuaState vm, LValue cmpfunc) {
heapify(count, vm, cmpfunc);
for ( int end=count-1; end>0; ) {
swap(end, 0);
siftDown(0, --end, vm, cmpfunc);
}
}
private void heapify(int count, LuaState vm, LValue cmpfunc) {
for ( int start=count/2-1; start>=0; --start )
siftDown(start, count - 1, vm, cmpfunc);
}
private void siftDown(int start, int end, LuaState vm, LValue cmpfunc) {
for ( int root=start; root*2+1 <= end; ) {
int child = root*2+1;
if (child < end && compare(child, child + 1, vm, cmpfunc))
++child;
if (compare(root, child, vm, cmpfunc)) {
swap(root, child);
root = child;
} else
return;
}
}
private boolean compare(int i, int j, LuaState vm, LValue cmpfunc) {
LValue a = get(i+1);
LValue b = get(j+1);
if ( a.isNil() || b.isNil() )
return false;
if ( ! cmpfunc.isNil() ) {
vm.pushlvalue(cmpfunc);
vm.pushlvalue(a);
vm.pushlvalue(b);
vm.call(2, 1);
boolean result = vm.toboolean(-1);
vm.resettop();
return result;
} else {
return b.luaBinCmpUnknown( Lua.OP_LT, a );
}
}
private void swap(int i, int j) {
LValue a = get(i+1);
put(i+1, get(j+1));
put(j+1, a);
}
/**
* Leave key,value pair on top, or nil if at end of list.
* @param vm the LuaState to leave the values on
* @param indexedonly TODO
* @param index index to start search
* @return true if next exists, false if at end of list
*/
public boolean next(LuaState vm, LValue key, boolean indexedonly ) {
int n = array.length;
int m = (indexedonly? -1: hashKeys.length);
int i = findindex(key, n, m);
if ( i < 0 )
vm.error( "invalid key to 'next'" );
// check array part
for ( ; i 0 ) {
int slot = hashFindSlot( key );
hashClearSlot( slot );
}
}
protected void hashClearSlot( int i ) {
if ( hashKeys[ i ] != null ) {
int j = i;
int n = hashKeys.length;
while ( hashKeys[ j = ( ( j + 1 ) % n ) ] != null ) {
final int k = ( ( hashKeys[ j ].hashCode() )& 0x7FFFFFFF ) % n;
if ( ( j > i && ( k <= i || k > j ) ) ||
( j < i && ( k <= i && k > j ) ) ) {
hashKeys[ i ] = hashKeys[ j ];
hashValues[ i ] = hashValues[ j ];
i = j;
}
}
--hashEntries;
hashKeys[ i ] = null;
hashValues[ i ] = null;
if ( hashEntries == 0 ) {
hashKeys = NONE;
hashValues = null;
}
}
}
protected boolean checkLoadFactor() {
// Using a load factor of 2/3 because that is easy to compute without
// overflow or division.
final int hashCapacity = hashKeys.length;
return ( hashCapacity >> 1 ) >= ( hashCapacity - hashEntries );
}
protected void rehash() {
final int oldCapacity = hashKeys.length;
final int newCapacity = ( oldCapacity > 0 ) ? 2 * oldCapacity : MIN_HASH_CAPACITY;
final LValue[] oldKeys = hashKeys;
final Object[] oldValues = hashValues;
hashKeys = new LValue[ newCapacity ];
hashValues = new Object[ newCapacity ];
for ( int i = 0; i < oldCapacity; ++i ) {
final LValue k = oldKeys[i];
if ( k != null ) {
final Object v = oldValues[i];
final int slot = hashFindSlot( k );
hashKeys[slot] = k;
hashValues[slot] = v;
}
}
}
}