/******************************************************************************* * Copyright (c) 2009 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; import java.lang.ref.WeakReference; import java.util.Vector; /** * Subclass of {@link LuaValue} for representing lua tables. * <p> * Almost all API's implemented in {@link LuaTable} are defined and documented in {@link LuaValue}. * <p> * If a table is needed, the one of the type-checking functions can be used such as * {@link #istable()}, * {@link #checktable()}, or * {@link #opttable(LuaTable)} * <p> * The main table operations are defined on {@link LuaValue} * for getting and setting values with and without metatag processing: * <ul> * <li>{@link #get(LuaValue)}</li> * <li>{@link #set(LuaValue,LuaValue)}</li> * <li>{@link #rawget(LuaValue)}</li> * <li>{@link #rawset(LuaValue,LuaValue)}</li> * <li>plus overloads such as {@link #get(String)}, {@link #get(int)}, and so on</li> * </ul> * <p> * To iterate over key-value pairs from Java, use * <pre> {@code * LuaValue k = LuaValue.NIL; * while ( true ) { * Varargs n = table.next(k); * if ( (k = n.arg1()).isnil() ) * break; * LuaValue v = n.arg(2) * process( k, v ) * }}</pre> * * <p> * As with other types, {@link LuaTable} instances should be constructed via one of the table constructor * methods on {@link LuaValue}: * <ul> * <li>{@link LuaValue#tableOf()} empty table</li> * <li>{@link LuaValue#tableOf(int, int)} table with capacity</li> * <li>{@link LuaValue#listOf(LuaValue[])} initialize array part</li> * <li>{@link LuaValue#listOf(LuaValue[], Varargs)} initialize array part</li> * <li>{@link LuaValue#tableOf(LuaValue[])} initialize named hash part</li> * <li>{@link LuaValue#tableOf(Varargs, int)} initialize named hash part</li> * <li>{@link LuaValue#tableOf(LuaValue[], LuaValue[])} initialize array and named parts</li> * <li>{@link LuaValue#tableOf(LuaValue[], LuaValue[], Varargs)} initialize array and named parts</li> * </ul> * @see LuaValue */ public class LuaTable extends LuaValue implements Metatable { private static final int MIN_HASH_CAPACITY = 2; private static final LuaString N = valueOf("n"); /** the array values */ protected LuaValue[] array; /** the hash part */ protected Slot[] hash; /** the number of hash entries */ protected int hashEntries; /** metatable for this table, or null */ protected Metatable m_metatable; /** Construct empty table */ public LuaTable() { array = NOVALS; hash = NOBUCKETS; } /** * Construct table with preset capacity. * @param narray capacity of array part * @param nhash capacity of hash part */ public LuaTable(int narray, int nhash) { presize(narray, nhash); } /** * Construct table with named and unnamed parts. * @param named Named elements in order {@code key-a, value-a, key-b, value-b, ... } * @param unnamed Unnamed elements in order {@code value-1, value-2, ... } * @param lastarg Additional unnamed values beyond {@code unnamed.length} */ public LuaTable(LuaValue[] named, LuaValue[] unnamed, Varargs lastarg) { int nn = (named!=null? named.length: 0); int nu = (unnamed!=null? unnamed.length: 0); int nl = (lastarg!=null? lastarg.narg(): 0); presize(nu+nl, nn>>1); for ( int i=0; i<nu; i++ ) rawset(i+1,unnamed[i]); if ( lastarg != null ) for ( int i=1,n=lastarg.narg(); i<=n; ++i ) rawset(nu+i,lastarg.arg(i)); for ( int i=0; i<nn; i+=2 ) if (!named[i+1].isnil()) rawset(named[i], named[i+1]); } /** * Construct table of unnamed elements. * @param varargs Unnamed elements in order {@code value-1, value-2, ... } */ public LuaTable(Varargs varargs) { this(varargs,1); } /** * Construct table of unnamed elements. * @param varargs Unnamed elements in order {@code value-1, value-2, ... } * @param firstarg the index in varargs of the first argument to include in the table */ public LuaTable(Varargs varargs, int firstarg) { int nskip = firstarg-1; int n = Math.max(varargs.narg()-nskip,0); presize( n, 1 ); set(N, valueOf(n)); for ( int i=1; i<=n; i++ ) set(i, varargs.arg(i+nskip)); } public int type() { return LuaValue.TTABLE; } public String typename() { return "table"; } public boolean istable() { return true; } public LuaTable checktable() { return this; } public LuaTable opttable(LuaTable defval) { return this; } public void presize( int narray ) { if ( narray > array.length ) array = resize( array, 1 << log2(narray) ); } public void presize(int narray, int nhash) { if ( nhash > 0 && nhash < MIN_HASH_CAPACITY ) nhash = MIN_HASH_CAPACITY; // Size of both parts must be a power of two. array = (narray>0? new LuaValue[1 << log2(narray)]: NOVALS); hash = (nhash>0? new Slot[1 << log2(nhash)]: NOBUCKETS); hashEntries = 0; } /** Resize the table */ private static LuaValue[] resize( LuaValue[] old, int n ) { LuaValue[] v = new LuaValue[n]; System.arraycopy(old, 0, v, 0, old.length); return v; } /** * Get the length of the array part of the table. * @return length of the array part, does not relate to count of objects in the table. */ protected int getArrayLength() { return array.length; } /** * Get the length of the hash part of the table. * @return length of the hash part, does not relate to count of objects in the table. */ protected int getHashLength() { return hash.length; } public LuaValue getmetatable() { return ( m_metatable != null ) ? m_metatable.toLuaValue() : null; } public LuaValue setmetatable(LuaValue metatable) { boolean hadWeakKeys = m_metatable != null && m_metatable.useWeakKeys(); boolean hadWeakValues = m_metatable != null && m_metatable.useWeakValues(); m_metatable = metatableOf( metatable ); if ( ( hadWeakKeys != ( m_metatable != null && m_metatable.useWeakKeys() )) || ( hadWeakValues != ( m_metatable != null && m_metatable.useWeakValues() ))) { // force a rehash rehash( 0 ); } return this; } public LuaValue get( int key ) { LuaValue v = rawget(key); return v.isnil() && m_metatable!=null? gettable(this,valueOf(key)): v; } public LuaValue get( LuaValue key ) { LuaValue v = rawget(key); return v.isnil() && m_metatable!=null? gettable(this,key): v; } public LuaValue rawget( int key ) { if ( key>0 && key<=array.length ) { LuaValue v = m_metatable == null ? array[key-1] : m_metatable.arrayget(array, key-1); return v != null ? v : NIL; } return hashget( LuaInteger.valueOf(key) ); } public LuaValue rawget( LuaValue key ) { if ( key.isinttype() ) { int ikey = key.toint(); if ( ikey>0 && ikey<=array.length ) { LuaValue v = m_metatable == null ? array[ikey-1] : m_metatable.arrayget(array, ikey-1); return v != null ? v : NIL; } } return hashget( key ); } protected LuaValue hashget(LuaValue key) { if ( hashEntries > 0 ) { for ( Slot slot = hash[ hashSlot(key) ]; slot != null; slot = slot.rest() ) { StrongSlot foundSlot; if ( ( foundSlot = slot.find(key) ) != null ) { return foundSlot.value(); } } } return NIL; } public void set( int key, LuaValue value ) { if ( m_metatable==null || ! rawget(key).isnil() || ! settable(this,LuaInteger.valueOf(key),value) ) rawset(key, value); } /** caller must ensure key is not nil */ public void set( LuaValue key, LuaValue value ) { if (!key.isvalidkey() && !metatag(NEWINDEX).isfunction()) typerror("table index"); if ( m_metatable==null || ! rawget(key).isnil() || ! settable(this,key,value) ) rawset(key, value); } public void rawset( int key, LuaValue value ) { if ( ! arrayset(key, value) ) hashset( LuaInteger.valueOf(key), value ); } /** caller must ensure key is not nil */ public void rawset( LuaValue key, LuaValue value ) { if ( !key.isinttype() || !arrayset(key.toint(), value) ) hashset( key, value ); } /** Set an array element */ private boolean arrayset( int key, LuaValue value ) { if ( key>0 && key<=array.length ) { array[key - 1] = value.isnil() ? null : (m_metatable != null ? m_metatable.wrap(value) : value); return true; } return false; } /** Remove the element at a position in a list-table * * @param pos the position to remove * @return The removed item, or {@link #NONE} if not removed */ public LuaValue remove(int pos) { int n = length(); if ( pos == 0 ) pos = n; else if (pos > n) return NONE; LuaValue v = rawget(pos); for ( LuaValue r=v; !r.isnil(); ) { r = rawget(pos+1); rawset(pos++, r); } return v.isnil()? NONE: v; } /** Insert an element at a position in a list-table * * @param pos the position to remove * @param value The value to insert */ public void insert(int pos, LuaValue value) { if ( pos == 0 ) pos = length()+1; while ( ! value.isnil() ) { LuaValue v = rawget( pos ); rawset(pos++, value); value = v; } } /** Concatenate the contents of a table efficiently, using {@link Buffer} * * @param sep {@link LuaString} separater to apply between elements * @param i the first element index * @param j the last element index, inclusive * @return {@link LuaString} value of the concatenation */ public LuaValue concat(LuaString sep, int i, int j) { Buffer sb = new Buffer (); if ( i<=j ) { sb.append( get(i).checkstring() ); while ( ++i<=j ) { sb.append( sep ); sb.append( get(i).checkstring() ); } } return sb.tostring(); } public int length() { int a = getArrayLength(); int n = a+1,m=0; while ( !rawget(n).isnil() ) { m = n; n += a+getHashLength()+1; } while ( n > m+1 ) { int k = (n+m) / 2; if ( !rawget(k).isnil() ) m = k; else n = k; } return m; } public LuaValue len() { return LuaInteger.valueOf(length()); } public int rawlen() { return length(); } /** * Get the next element after a particular key in the table * @return key,value or nil */ public Varargs next( LuaValue key ) { int i = 0; do { // find current key index if ( ! key.isnil() ) { if ( key.isinttype() ) { i = key.toint(); if ( i>0 && i<=array.length ) { break; } } if ( hash.length == 0 ) error( "invalid key to 'next'" ); i = hashSlot( key ); boolean found = false; for ( Slot slot = hash[i]; slot != null; slot = slot.rest() ) { if ( found ) { StrongSlot nextEntry = slot.first(); if ( nextEntry != null ) { return nextEntry.toVarargs(); } } else if ( slot.keyeq( key ) ) { found = true; } } if ( !found ) { error( "invalid key to 'next'" ); } i += 1+array.length; } } while ( false ); // check array part for ( ; i<array.length; ++i ) { if ( array[i] != null ) { LuaValue value = m_metatable == null ? array[i] : m_metatable.arrayget(array, i); if (value != null) { return varargsOf(LuaInteger.valueOf(i+1),value); } } } // check hash part for ( i -= array.length; i < hash.length; ++i ) { Slot slot = hash[i]; while ( slot != null ) { StrongSlot first = slot.first(); if ( first != null ) return first.toVarargs(); slot = slot.rest(); } } // nothing found, push nil, return nil. return NIL; } /** * Get the next element after a particular key in the * contiguous array part of a table * @return key,value or none */ public Varargs inext(LuaValue key) { int k = key.checkint() + 1; LuaValue v = rawget(k); return v.isnil()? NONE: varargsOf(LuaInteger.valueOf(k),v); } /** * Set a hashtable value * @param key key to set * @param value value to set */ public void hashset(LuaValue key, LuaValue value) { if ( value.isnil() ) hashRemove(key); else { int index = 0; if ( hash.length > 0 ) { index = hashSlot( key ); for ( Slot slot = hash[ index ]; slot != null; slot = slot.rest() ) { StrongSlot foundSlot; if ( ( foundSlot = slot.find( key ) ) != null ) { hash[index] = hash[index].set( foundSlot, value ); return; } } } if ( checkLoadFactor() ) { if ( key.isinttype() && key.toint() > 0 ) { // a rehash might make room in the array portion for this key. rehash( key.toint() ); if ( arrayset(key.toint(), value) ) return; } else { rehash( -1 ); } index = hashSlot( key ); } Slot entry = ( m_metatable != null ) ? m_metatable.entry( key, value ) : defaultEntry( key, value ); hash[ index ] = ( hash[index] != null ) ? hash[index].add( entry ) : entry; ++hashEntries; } } public static int hashpow2( int hashCode, int mask ) { return hashCode & mask; } public static int hashmod( int hashCode, int mask ) { return ( hashCode & 0x7FFFFFFF ) % mask; } /** * Find the hashtable slot index to use. * @param key the key to look for * @param hashMask N-1 where N is the number of hash slots (must be power of 2) * @return the slot index */ public static int hashSlot( LuaValue key, int hashMask ) { switch ( key.type() ) { case TNUMBER: case TTABLE: case TTHREAD: case TLIGHTUSERDATA: case TUSERDATA: return hashmod( key.hashCode(), hashMask ); default: return hashpow2( key.hashCode(), hashMask ); } } /** * Find the hashtable slot to use * @param key key to look for * @return slot to use */ private int hashSlot(LuaValue key) { return hashSlot( key, hash.length - 1 ); } private void hashRemove( LuaValue key ) { if ( hash.length > 0 ) { int index = hashSlot(key); for ( Slot slot = hash[index]; slot != null; slot = slot.rest() ) { StrongSlot foundSlot; if ( ( foundSlot = slot.find( key ) ) != null ) { hash[index] = hash[index].remove( foundSlot ); --hashEntries; return; } } } } private boolean checkLoadFactor() { return hashEntries >= hash.length; } private int countHashKeys() { int keys = 0; for ( int i = 0; i < hash.length; ++i ) { for ( Slot slot = hash[i]; slot != null; slot = slot.rest() ) { if ( slot.first() != null ) keys++; } } return keys; } private void dropWeakArrayValues() { for ( int i = 0; i < array.length; ++i ) { m_metatable.arrayget(array, i); } } private int countIntKeys(int[] nums) { int total = 0; int i = 1; // Count integer keys in array part for ( int bit = 0; bit < 31; ++bit ) { if ( i > array.length ) break; int j = Math.min(array.length, 1 << bit); int c = 0; while ( i <= j ) { if ( array[ i++ - 1 ] != null ) c++; } nums[bit] = c; total += c; } // Count integer keys in hash part for ( i = 0; i < hash.length; ++i ) { for ( Slot s = hash[i]; s != null; s = s.rest() ) { int k; if ( ( k = s.arraykey(Integer.MAX_VALUE) ) > 0 ) { nums[log2(k)]++; total++; } } } return total; } // Compute ceil(log2(x)) static int log2(int x) { int lg = 0; x -= 1; if ( x < 0 ) // 2^(-(2^31)) is approximately 0 return Integer.MIN_VALUE; if ( ( x & 0xFFFF0000 ) != 0 ) { lg = 16; x >>>= 16; } if ( ( x & 0xFF00 ) != 0 ) { lg += 8; x >>>= 8; } if ( ( x & 0xF0 ) != 0 ) { lg += 4; x >>>= 4; } switch (x) { case 0x0: return 0; case 0x1: lg += 1; break; case 0x2: lg += 2; break; case 0x3: lg += 2; break; case 0x4: lg += 3; break; case 0x5: lg += 3; break; case 0x6: lg += 3; break; case 0x7: lg += 3; break; case 0x8: lg += 4; break; case 0x9: lg += 4; break; case 0xA: lg += 4; break; case 0xB: lg += 4; break; case 0xC: lg += 4; break; case 0xD: lg += 4; break; case 0xE: lg += 4; break; case 0xF: lg += 4; break; } return lg; } /* * newKey > 0 is next key to insert * newKey == 0 means number of keys not changing (__mode changed) * newKey < 0 next key will go in hash part */ private void rehash(int newKey) { if ( m_metatable != null && ( m_metatable.useWeakKeys() || m_metatable.useWeakValues() )) { // If this table has weak entries, hashEntries is just an upper bound. hashEntries = countHashKeys(); if ( m_metatable.useWeakValues() ) { dropWeakArrayValues(); } } int[] nums = new int[32]; int total = countIntKeys(nums); if ( newKey > 0 ) { total++; nums[log2(newKey)]++; } // Choose N such that N <= sum(nums[0..log(N)]) < 2N int keys = nums[0]; int newArraySize = 0; for ( int log = 1; log < 32; ++log ) { keys += nums[log]; if (total * 2 < 1 << log) { // Not enough integer keys. break; } else if (keys >= (1 << (log - 1))) { newArraySize = 1 << log; } } final LuaValue[] oldArray = array; final Slot[] oldHash = hash; final LuaValue[] newArray; final Slot[] newHash; // Copy existing array entries and compute number of moving entries. int movingToArray = 0; if ( newKey > 0 && newKey <= newArraySize ) { movingToArray--; } if (newArraySize != oldArray.length) { newArray = new LuaValue[newArraySize]; if (newArraySize > oldArray.length) { for (int i = log2(oldArray.length + 1), j = log2(newArraySize) + 1; i < j; ++i) { movingToArray += nums[i]; } } else if (oldArray.length > newArraySize) { for (int i = log2(newArraySize + 1), j = log2(oldArray.length) + 1; i < j; ++i) { movingToArray -= nums[i]; } } System.arraycopy(oldArray, 0, newArray, 0, Math.min(oldArray.length, newArraySize)); } else { newArray = array; } final int newHashSize = hashEntries - movingToArray + ((newKey < 0 || newKey > newArraySize) ? 1 : 0); // Make room for the new entry final int oldCapacity = oldHash.length; final int newCapacity; final int newHashMask; if (newHashSize > 0) { // round up to next power of 2. newCapacity = ( newHashSize < MIN_HASH_CAPACITY ) ? MIN_HASH_CAPACITY : 1 << log2(newHashSize); newHashMask = newCapacity - 1; newHash = new Slot[ newCapacity ]; } else { newCapacity = 0; newHashMask = 0; newHash = NOBUCKETS; } // Move hash buckets for ( int i = 0; i < oldCapacity; ++i ) { for ( Slot slot = oldHash[i]; slot != null; slot = slot.rest() ) { int k; if ( ( k = slot.arraykey( newArraySize ) ) > 0 ) { StrongSlot entry = slot.first(); if (entry != null) newArray[ k - 1 ] = entry.value(); } else { int j = slot.keyindex( newHashMask ); newHash[j] = slot.relink( newHash[j] ); } } } // Move array values into hash portion for ( int i = newArraySize; i < oldArray.length; ) { LuaValue v; if ( ( v = oldArray[ i++ ] ) != null ) { int slot = hashmod( LuaInteger.hashCode( i ), newHashMask ); Slot newEntry; if ( m_metatable != null ) { newEntry = m_metatable.entry( valueOf(i), v ); if ( newEntry == null ) continue; } else { newEntry = defaultEntry( valueOf(i), v ); } newHash[ slot ] = ( newHash[slot] != null ) ? newHash[slot].add( newEntry ) : newEntry; } } hash = newHash; array = newArray; hashEntries -= movingToArray; } public Slot entry( LuaValue key, LuaValue value ) { return defaultEntry( key, value ); } protected static boolean isLargeKey(LuaValue key) { switch (key.type()) { case TSTRING: return key.rawlen() > LuaString.RECENT_STRINGS_MAX_LENGTH; case TNUMBER: case TBOOLEAN: return false; default: return true; } } protected static Entry defaultEntry(LuaValue key, LuaValue value) { if ( key.isinttype() ) { return new IntKeyEntry( key.toint(), value ); } else if (value.type() == TNUMBER) { return new NumberValueEntry( key, value.todouble() ); } else { return new NormalEntry( key, value ); } } // ----------------- sort support ----------------------------- // // implemented heap sort from wikipedia // // Only sorts the contiguous array part. // /** Sort the table using a comparator. * @param comparator {@link LuaValue} to be called to compare elements. */ public void sort(LuaValue comparator) { if (m_metatable != null && m_metatable.useWeakValues()) { dropWeakArrayValues(); } int n = array.length; while ( n > 0 && array[n-1] == null ) --n; if ( n > 1 ) heapSort(n, comparator); } private void heapSort(int count, LuaValue cmpfunc) { heapify(count, cmpfunc); for ( int end=count-1; end>0; ) { swap(end, 0); siftDown(0, --end, cmpfunc); } } private void heapify(int count, LuaValue cmpfunc) { for ( int start=count/2-1; start>=0; --start ) siftDown(start, count - 1, cmpfunc); } private void siftDown(int start, int end, LuaValue cmpfunc) { for ( int root=start; root*2+1 <= end; ) { int child = root*2+1; if (child < end && compare(child, child + 1, cmpfunc)) ++child; if (compare(root, child, cmpfunc)) { swap(root, child); root = child; } else return; } } private boolean compare(int i, int j, LuaValue cmpfunc) { LuaValue a, b; if (m_metatable == null) { a = array[i]; b = array[j]; } else { a = m_metatable.arrayget(array, i); b = m_metatable.arrayget(array, j); } if ( a == null || b == null ) return false; if ( ! cmpfunc.isnil() ) { return cmpfunc.call(a,b).toboolean(); } else { return a.lt_b(b); } } private void swap(int i, int j) { LuaValue a = array[i]; array[i] = array[j]; array[j] = a; } /** This may be deprecated in a future release. * It is recommended to count via iteration over next() instead * @return count of keys in the table * */ public int keyCount() { LuaValue k = LuaValue.NIL; for ( int i=0; true; i++ ) { Varargs n = next(k); if ( (k = n.arg1()).isnil() ) return i; } } /** This may be deprecated in a future release. * It is recommended to use next() instead * @return array of keys in the table * */ public LuaValue[] keys() { Vector l = new Vector(); LuaValue k = LuaValue.NIL; while ( true ) { Varargs n = next(k); if ( (k = n.arg1()).isnil() ) break; l.addElement( k ); } LuaValue[] a = new LuaValue[l.size()]; l.copyInto(a); return a; } // equality w/ metatable processing public LuaValue eq( LuaValue val ) { return eq_b(val)? TRUE: FALSE; } public boolean eq_b( LuaValue val ) { if ( this == val ) return true; if ( m_metatable == null || !val.istable() ) return false; LuaValue valmt = val.getmetatable(); return valmt!=null && LuaValue.eqmtcall(this, m_metatable.toLuaValue(), val, valmt); } /** Unpack all the elements of this table */ public Varargs unpack() { return unpack(1, this.length()); } /** Unpack all the elements of this table from element i */ public Varargs unpack(int i) { return unpack(i, this.length()); } /** Unpack the elements from i to j inclusive */ public Varargs unpack(int i, int j) { int n = j + 1 - i; switch (n) { case 0: return NONE; case 1: return get(i); case 2: return varargsOf(get(i), get(i+1)); default: if (n < 0) return NONE; LuaValue[] v = new LuaValue[n]; while (--n >= 0) v[n] = get(i+n); return varargsOf(v); } } /** * Represents a slot in the hash table. */ interface Slot { /** Return hash{pow2,mod}( first().key().hashCode(), sizeMask ) */ int keyindex( int hashMask ); /** Return first Entry, if still present, or null. */ StrongSlot first(); /** Compare given key with first()'s key; return first() if equal. */ StrongSlot find( LuaValue key ); /** * Compare given key with first()'s key; return true if equal. May * return true for keys no longer present in the table. */ boolean keyeq( LuaValue key ); /** Return rest of elements */ Slot rest(); /** * Return first entry's key, iff it is an integer between 1 and max, * inclusive, or zero otherwise. */ int arraykey( int max ); /** * Set the value of this Slot's first Entry, if possible, or return a * new Slot whose first entry has the given value. */ Slot set( StrongSlot target, LuaValue value ); /** * Link the given new entry to this slot. */ Slot add( Slot newEntry ); /** * Return a Slot with the given value set to nil; must not return null * for next() to behave correctly. */ Slot remove( StrongSlot target ); /** * Return a Slot with the same first key and value (if still present) * and rest() equal to rest. */ Slot relink( Slot rest ); } /** * Subclass of Slot guaranteed to have a strongly-referenced key and value, * to support weak tables. */ interface StrongSlot extends Slot { /** Return first entry's key */ LuaValue key(); /** Return first entry's value */ LuaValue value(); /** Return varargsOf(key(), value()) or equivalent */ Varargs toVarargs(); } private static class LinkSlot implements StrongSlot { private Entry entry; private Slot next; LinkSlot( Entry entry, Slot next ) { this.entry = entry; this.next = next; } public LuaValue key() { return entry.key(); } public int keyindex( int hashMask ) { return entry.keyindex( hashMask ); } public LuaValue value() { return entry.value(); } public Varargs toVarargs() { return entry.toVarargs(); } public StrongSlot first() { return entry; } public StrongSlot find(LuaValue key) { return entry.keyeq(key) ? this : null; } public boolean keyeq(LuaValue key) { return entry.keyeq(key); } public Slot rest() { return next; } public int arraykey( int max ) { return entry.arraykey( max ); } public Slot set(StrongSlot target, LuaValue value) { if ( target == this ) { entry = entry.set( value ); return this; } else { return setnext(next.set( target, value )); } } public Slot add( Slot entry ) { return setnext(next.add( entry )); } public Slot remove( StrongSlot target ) { if ( this == target ) { return new DeadSlot( key(), next ); } else { this.next = next.remove( target ); } return this; } public Slot relink(Slot rest) { // This method is (only) called during rehash, so it must not change this.next. return ( rest != null ) ? new LinkSlot(entry, rest) : (Slot)entry; } // this method ensures that this.next is never set to null. private Slot setnext(Slot next) { if ( next != null ) { this.next = next; return this; } else { return entry; } } public String toString() { return entry + "; " + next; } } /** * Base class for regular entries. * * <p> * If the key may be an integer, the {@link #arraykey(int)} method must be * overridden to handle that case. */ static abstract class Entry extends Varargs implements StrongSlot { public abstract LuaValue key(); public abstract LuaValue value(); abstract Entry set(LuaValue value); public int arraykey( int max ) { return 0; } public LuaValue arg(int i) { switch (i) { case 1: return key(); case 2: return value(); } return NIL; } public int narg() { return 2; } /** * Subclasses should redefine as "return this;" whenever possible. */ public Varargs toVarargs() { return varargsOf(key(), value()); } public LuaValue arg1() { return key(); } public Varargs subargs(int start) { switch (start) { case 1: return this; case 2: return value(); } return NONE; } public StrongSlot first() { return this; } public Slot rest() { return null; } public StrongSlot find(LuaValue key) { return keyeq(key) ? this : null; } public Slot set(StrongSlot target, LuaValue value) { return set( value ); } public Slot add( Slot entry ) { return new LinkSlot( this, entry ); } public Slot remove(StrongSlot target) { return new DeadSlot( key(), null ); } public Slot relink( Slot rest ) { return ( rest != null ) ? new LinkSlot( this, rest ) : (Slot)this; } } static class NormalEntry extends Entry { private final LuaValue key; private LuaValue value; NormalEntry( LuaValue key, LuaValue value ) { this.key = key; this.value = value; } public LuaValue key() { return key; } public LuaValue value() { return value; } public Entry set(LuaValue value) { this.value = value; return this; } public Varargs toVarargs() { return this; } public int keyindex( int hashMask ) { return hashSlot( key, hashMask ); } public boolean keyeq(LuaValue key) { return key.raweq(this.key); } } private static class IntKeyEntry extends Entry { private final int key; private LuaValue value; IntKeyEntry(int key, LuaValue value) { this.key = key; this.value = value; } public LuaValue key() { return valueOf( key ); } public int arraykey(int max) { return ( key >= 1 && key <= max ) ? key : 0; } public LuaValue value() { return value; } public Entry set(LuaValue value) { this.value = value; return this; } public int keyindex( int mask ) { return hashmod( LuaInteger.hashCode( key ), mask ); } public boolean keyeq(LuaValue key) { return key.raweq( this.key ); } } /** * Entry class used with numeric values, but only when the key is not an integer. */ private static class NumberValueEntry extends Entry { private double value; private final LuaValue key; NumberValueEntry(LuaValue key, double value) { this.key = key; this.value = value; } public LuaValue key() { return key; } public LuaValue value() { return valueOf(value); } public Entry set(LuaValue value) { LuaValue n = value.tonumber(); if ( !n.isnil() ) { this.value = n.todouble(); return this; } else { return new NormalEntry( this.key, value ); } } public int keyindex( int mask ) { return hashSlot( key, mask ); } public boolean keyeq(LuaValue key) { return key.raweq(this.key); } } /** * A Slot whose value has been set to nil. The key is kept in a weak reference so that * it can be found by next(). */ private static class DeadSlot implements Slot { private final Object key; private Slot next; private DeadSlot( LuaValue key, Slot next ) { this.key = isLargeKey(key) ? new WeakReference( key ) : (Object)key; this.next = next; } private LuaValue key() { return (LuaValue) (key instanceof WeakReference ? ((WeakReference) key).get() : key); } public int keyindex(int hashMask) { // Not needed: this entry will be dropped during rehash. return 0; } public StrongSlot first() { return null; } public StrongSlot find(LuaValue key) { return null; } public boolean keyeq(LuaValue key) { LuaValue k = key(); return k != null && key.raweq(k); } public Slot rest() { return next; } public int arraykey(int max) { return -1; } public Slot set(StrongSlot target, LuaValue value) { Slot next = ( this.next != null ) ? this.next.set( target, value ) : null; if ( key() != null ) { // if key hasn't been garbage collected, it is still potentially a valid argument // to next(), so we can't drop this entry yet. this.next = next; return this; } else { return next; } } public Slot add(Slot newEntry) { return ( next != null ) ? next.add(newEntry) : newEntry; } public Slot remove(StrongSlot target) { if ( key() != null ) { next = next.remove(target); return this; } else { return next; } } public Slot relink(Slot rest) { return rest; } public String toString() { StringBuffer buf = new StringBuffer(); buf.append("<dead"); LuaValue k = key(); if (k != null) { buf.append(": "); buf.append(k.toString()); } buf.append('>'); if (next != null) { buf.append("; "); buf.append(next.toString()); } return buf.toString(); } }; private static final Slot[] NOBUCKETS = {}; // Metatable operations public boolean useWeakKeys() { return false; } public boolean useWeakValues() { return false; } public LuaValue toLuaValue() { return this; } public LuaValue wrap(LuaValue value) { return value; } public LuaValue arrayget(LuaValue[] array, int index) { return array[index]; } }