| /******************************************************************************* |
| * 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.vm3; |
| |
| 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]; |
| } |
| } |