All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.weeaboo.lua2.vm.LuaTable Maven / Gradle / Ivy

/*******************************************************************************
 * 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 nl.weeaboo.lua2.vm;

import static nl.weeaboo.lua2.vm.LuaConstants.META_LEN;
import static nl.weeaboo.lua2.vm.LuaConstants.META_NEWINDEX;
import static nl.weeaboo.lua2.vm.LuaConstants.NONE;
import static nl.weeaboo.lua2.vm.LuaConstants.NOVALS;
import static nl.weeaboo.lua2.vm.LuaConstants.TBOOLEAN;
import static nl.weeaboo.lua2.vm.LuaConstants.TLIGHTUSERDATA;
import static nl.weeaboo.lua2.vm.LuaConstants.TNUMBER;
import static nl.weeaboo.lua2.vm.LuaConstants.TSTRING;
import static nl.weeaboo.lua2.vm.LuaConstants.TTABLE;
import static nl.weeaboo.lua2.vm.LuaConstants.TTHREAD;
import static nl.weeaboo.lua2.vm.LuaConstants.TUSERDATA;
import static nl.weeaboo.lua2.vm.LuaNil.NIL;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nullable;

import nl.weeaboo.lua2.io.DelayedReader;
import nl.weeaboo.lua2.io.LuaSerializable;
import nl.weeaboo.lua2.io.LuaSerializer;

/**
 * Subclass of {@link LuaValue} for representing lua tables.
 * 

* Almost all API's implemented in {@link LuaTable} are defined and documented in {@link LuaValue}. *

* 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)} *

* The main table operations are defined on {@link LuaValue} for getting and setting values with and without * metatag processing: *

    *
  • {@link #get(LuaValue)}
  • *
  • {@link #set(LuaValue,LuaValue)}
  • *
  • {@link #rawget(LuaValue)}
  • *
  • {@link #rawset(LuaValue,LuaValue)}
  • *
  • plus overloads such as {@link #get(String)}, {@link #get(int)}, and so on
  • *
*

* To iterate over key-value pairs from Java, use * *

 * 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 )
 * }
 * 
* *

* As with other types, {@link LuaTable} instances should be constructed via one of the table constructor * methods on {@link LuaValue}: *

    *
  • {@link LuaValue#tableOf()} empty table
  • *
  • {@link LuaValue#tableOf(int, int)} table with capacity
  • *
  • {@link LuaValue#listOf(LuaValue[])} initialize array part
  • *
  • {@link LuaValue#listOf(LuaValue[], Varargs)} initialize array part
  • *
  • {@link LuaValue#tableOf(LuaValue[])} initialize named hash part
  • *
  • {@link LuaValue#tableOf(Varargs, int)} initialize named hash part
  • *
  • {@link LuaValue#tableOf(LuaValue[], LuaValue[])} initialize array and named parts
  • *
  • {@link LuaValue#tableOf(LuaValue[], LuaValue[], Varargs)} initialize array and named parts
  • *
* * @see LuaValue */ @LuaSerializable public class LuaTable extends LuaValue implements IMetatable, Externalizable { private static final long serialVersionUID = 2L; private static final int MIN_HASH_CAPACITY = 2; private static final LuaString N = valueOf("n"); private static final ISlot[] NOBUCKETS = {}; /** the array values. */ protected LuaValue[] array; /** the hash part. */ protected ISlot[] hash; /** the number of hash entries. */ protected int hashEntries; /** metatable for this table, or null. */ protected @Nullable IMetatable 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); if (unnamed != null) { 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)); } } if (named != null) { 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)); } } /** Check that subclasses override writeExternal. */ private boolean checkOverridden() { Class c = getClass(); if (c == LuaTable.class) { return true; } try { if (c.getDeclaredMethod("writeExternal", ObjectOutput.class) != null) { return true; } } catch (NoSuchMethodException e) { // Ignore } catch (SecurityException e) { // Assume the best return true; } throw new RuntimeException("Subclass of LuaTable doesn't implement writeExternal()"); } @Override public void writeExternal(ObjectOutput out) throws IOException { checkOverridden(); out.writeObject(metatable); out.writeInt(array.length); int lastNonNil = array.length - 1; while (lastNonNil >= 0 && array[lastNonNil] == null) { lastNonNil--; } out.writeInt(lastNonNil + 1); for (int n = 0; n <= lastNonNil; n++) { out.writeObject(array[n]); } writeHashEntries(out, hash); } private void writeHashEntries(ObjectOutput out, ISlot[] hashSlots) throws IOException { List keysAndValues = new ArrayList<>(); for (ISlot chain : hashSlots) { while (chain != null) { IStrongSlot cur = chain.first(); if (cur != null) { keysAndValues.add(cur.key()); keysAndValues.add(cur.value()); } chain = chain.rest(); } } // Write the number of entries, followed by all key/value pairs Object[] keysAndValuesArray = keysAndValues.toArray(); LuaSerializer ls = LuaSerializer.getCurrent(); if (ls != null) { ls.writeDelayed(keysAndValuesArray); } else { out.writeObject(keysAndValuesArray); } } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { metatable = (IMetatable)in.readObject(); int arrayLength = in.readInt(); int arrayUsed = in.readInt(); array = new LuaValue[arrayLength]; for (int n = 0; n < arrayUsed; n++) { array[n] = (LuaValue)in.readObject(); } readHashEntries(in); } private void readHashEntries(ObjectInput in) throws IOException, ClassNotFoundException { DelayedReader r = new DelayedReader() { @Override public void onRead(Object obj) { Object[] array = (Object[])obj; for (int n = 0; n < array.length; n += 2) { hashset((LuaValue)array[n], (LuaValue)array[n + 1]); } } }; LuaSerializer ls = LuaSerializer.getCurrent(); if (ls != null) { ls.readDelayed(r); } else { r.onRead(in.readObject()); } } @Override public int type() { return LuaConstants.TTABLE; } @Override public String typename() { return "table"; } @Override public boolean istable() { return true; } @Override public LuaTable checktable() { return this; } @Override public LuaTable opttable(LuaTable defval) { return this; } @Override public void presize(int narray) { if (narray > array.length) { array = resize(array, 1 << log2(narray)); } } private 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 ISlot[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; } @Override public LuaValue getn() { int count = 0; for (int n = 1; n <= getArrayLength(); n++) { if (rawget(n).isnil()) { break; } count++; } return valueOf(count); } /** * 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; } @Override public LuaValue getmetatable() { return (metatable != null) ? metatable.toLuaValue() : NIL; } @Override public LuaValue setmetatable(LuaValue mt) { boolean oldWeakKeys = metatable != null && metatable.useWeakKeys(); boolean oldWeakValues = metatable != null && metatable.useWeakValues(); metatable = metatableOf(mt); boolean newWeakKeys = metatable != null && metatable.useWeakKeys(); boolean newWeakValues = metatable != null && metatable.useWeakValues(); if (oldWeakKeys != newWeakKeys || oldWeakValues != newWeakValues) { // force a rehash rehash(0); } return this; } @Override public LuaValue get(int key) { LuaValue v = rawget(key); return v.isnil() && metatable != null ? gettable(this, valueOf(key)) : v; } @Override public LuaValue get(LuaValue key) { LuaValue v = rawget(key); return v.isnil() && metatable != null ? gettable(this, key) : v; } @Override public LuaValue rawget(int key) { if (key > 0 && key <= array.length) { LuaValue v = metatable == null ? array[key - 1] : metatable.arrayget(array, key - 1); return v != null ? v : NIL; } return hashget(LuaInteger.valueOf(key)); } @Override public LuaValue rawget(LuaValue key) { if (key.isinttype()) { int ikey = key.toint(); if (ikey > 0 && ikey <= array.length) { LuaValue v = metatable == null ? array[ikey - 1] : metatable.arrayget(array, ikey - 1); return v != null ? v : NIL; } } return hashget(key); } protected LuaValue hashget(LuaValue key) { if (hashEntries > 0) { for (ISlot slot = hash[hashSlot(key)]; slot != null; slot = slot.rest()) { IStrongSlot foundSlot; if ((foundSlot = slot.find(key)) != null) { return foundSlot.value(); } } } return NIL; } /** caller must ensure key is not nil. */ @Override public void set(LuaValue key, LuaValue value) { if (!key.isvalidkey() && !metatag(META_NEWINDEX).isfunction()) { throw typerror("table index"); } super.set(key, value); } @Override public void rawset(int key, LuaValue value) { if (!arrayset(key, value)) { hashset(LuaInteger.valueOf(key), value); } } /** caller must ensure key is not nil. */ @Override 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 : wrap(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 LuaConstants#NONE} if not removed */ public LuaValue remove(int pos) { int n = rawlen(); 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 = rawlen() + 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(); } @Override public int length() { return metatable != null ? len().toint() : rawlen(); } @Override public LuaValue len() { final LuaValue h = metatag(META_LEN); if (h.toboolean()) { return h.call(this); } return LuaInteger.valueOf(rawlen()); } @Override public int rawlen() { int a = getArrayLength(); int n = a + 1; int 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; } /** * Get the next element after a particular key in the table. * * @return key,value or nil */ @Override 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 (ISlot slot = hash[i]; slot != null; slot = slot.rest()) { if (found) { IStrongSlot 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 = metatable == null ? array[i] : 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) { ISlot slot = hash[i]; while (slot != null) { IStrongSlot 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 */ @Override 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 (ISlot slot = hash[index]; slot != null; slot = slot.rest()) { IStrongSlot 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); } ISlot entry = entry(key, value); hash[index] = (hash[index] != null) ? hash[index].add(entry) : entry; ++hashEntries; } } private static int hashpow2(int hashCode, int mask) { return hashCode & mask; } 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) { return; } int index = hashSlot(key); for (ISlot slot = hash[index]; slot != null; slot = slot.rest()) { IStrongSlot 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 (ISlot slot = hash[i]; slot != null; slot = slot.rest()) { if (slot.first() != null) { keys++; } } } return keys; } private void dropWeakArrayValues() { if (metatable != null) { for (int i = 0; i < array.length; ++i) { 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 (ISlot 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 (metatable != null && (metatable.useWeakKeys() || metatable.useWeakValues())) { // If this table has weak entries, hashEntries is just an upper bound. hashEntries = countHashKeys(); if (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 ISlot[] oldHash = hash; final LuaValue[] newArray; final ISlot[] 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; } // Make room for the new entry final int newHashSize = hashEntries - movingToArray + ((newKey < 0 || newKey > newArraySize) ? 1 : 0); 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 ISlot[newCapacity]; } else { newCapacity = 0; newHashMask = 0; newHash = NOBUCKETS; } // Move hash buckets for (int i = 0; i < oldCapacity; ++i) { for (ISlot slot = oldHash[i]; slot != null; slot = slot.rest()) { int k; if ((k = slot.arraykey(newArraySize)) > 0) { IStrongSlot entry = slot.first(); if (entry != null) { newArray[k - 1] = wrap(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); ISlot newEntry; if (metatable != null) { newEntry = 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; } @Override public @Nullable ISlot entry(LuaValue key, LuaValue value) { if (metatable != null) { return metatable.entry(key, value); } return defaultEntry(key, value); } static boolean isLargeKey(LuaValue key) { switch (key.type()) { case TSTRING: return key.rawlen() > 32; 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(final LuaValue comparator) { if (metatable != null && 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); int end = count - 1; while (end > 0) { swap(end, 0); end--; siftDown(0, end, cmpfunc); } } private void heapify(int count, LuaValue cmpfunc) { int start = (count - 2) / 2; while (start >= 0) { siftDown(start, count - 1, cmpfunc); start--; } } private void siftDown(int start, int end, LuaValue cmpfunc) { int root = start; while (true) { int swap = root; int leftChild = root * 2 + 1; int rightChild = leftChild + 1; if (leftChild <= end && compare(swap, leftChild, cmpfunc)) { swap = leftChild; } if (rightChild <= end && compare(swap, rightChild, cmpfunc)) { swap = rightChild; } if (swap != root) { swap(swap, root); root = swap; } else { return; } } } private boolean compare(int i, int j, LuaValue cmpfunc) { LuaValue a; LuaValue b; if (metatable == null) { a = array[i]; b = array[j]; } else { a = metatable.arrayget(array, i); b = 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; } /** * Returns the number of keys in the table. */ public int keyCount() { return keys().size(); } /** * Returns a snapshot of the keys in the table. */ public List keys() { List result = new ArrayList<>(); LuaValue k = NIL; while (true) { Varargs n = next(k); k = n.arg1(); if (k.isnil()) { break; } result.add(k); } return result; } // equality w/ metatable processing @Override public boolean eq_b(LuaValue val) { if (this == val) { return true; } if (metatable == null || !val.istable()) { return false; } LuaValue valmt = val.getmetatable(); return !valmt.isnil() && LuaValue.eqmtcall(this, getmetatable(), val, valmt); } /** Unpack all the elements of this table. */ public Varargs unpack() { return unpack(1, this.rawlen()); } /** Unpack all the elements of this table from element i. */ public Varargs unpack(int i) { return unpack(i, this.rawlen()); } /** 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); } } // Metatable operations @Override public boolean useWeakKeys() { return false; } @Override public boolean useWeakValues() { return false; } @Override public LuaValue toLuaValue() { return this; } @Override public LuaValue wrap(LuaValue value) { if (metatable != null) { return metatable.wrap(value); } return value; } @Override public LuaValue arrayget(LuaValue[] array, int index) { return array[index]; } /** * Return table.maxn() as defined by lua 5.0. *

* Provided for compatibility, not a scalable operation. * * @return value for maxn */ public double maxn() { double max = 0; for (LuaValue key : keys()) { if (key.type() == LuaConstants.TNUMBER) { max = Math.max(max, key.todouble()); } } return max; } /** * Call the supplied function once for each key-value pair. * * @param func function to call */ public LuaValue foreach(LuaValue func) { Varargs n; LuaValue k = NIL; LuaValue v; while (!(k = (n = next(k)).arg1()).isnil()) { if (!(v = func.call(k, n.arg(2))).isnil()) { return v; } } return NIL; } /** * Call the supplied function once for each key-value pair in the contiguous array part. * * @param func function to call */ public LuaValue foreachi(LuaValue func) { LuaValue v; for (int k = 0; !(v = rawget(++k)).isnil();) { LuaValue r = func.call(valueOf(k), v); if (!r.isnil()) { return r; } } return NIL; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy