| /* |
| * $Id: AbstractTableMap.java 38 2012-01-04 22:44:15Z andre@naef.com $ |
| * See LICENSE.txt for license terms. |
| */ |
| |
| package com.naef.jnlua.util; |
| |
| import java.util.AbstractMap; |
| import java.util.AbstractSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| |
| import com.naef.jnlua.LuaState; |
| import com.naef.jnlua.LuaValueProxy; |
| |
| /** |
| * Abstract map implementation backed by a Lua table. |
| */ |
| public abstract class AbstractTableMap<K> extends AbstractMap<K, Object> |
| implements LuaValueProxy { |
| // -- State |
| private Set<Map.Entry<K, Object>> entrySet; |
| |
| // -- Construction |
| /** |
| * Creates a new instance. |
| */ |
| public AbstractTableMap() { |
| } |
| |
| // -- Map methods |
| @Override |
| public Set<Map.Entry<K, Object>> entrySet() { |
| if (entrySet == null) { |
| entrySet = new EntrySet(); |
| } |
| return entrySet; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return entrySet().isEmpty(); |
| } |
| |
| @Override |
| public boolean containsKey(Object key) { |
| checkKey(key); |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| pushValue(); |
| luaState.pushJavaObject(key); |
| luaState.getTable(-2); |
| try { |
| return !luaState.isNil(-1); |
| } finally { |
| luaState.pop(2); |
| } |
| } |
| } |
| |
| @Override |
| public Object get(Object key) { |
| checkKey(key); |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| pushValue(); |
| luaState.pushJavaObject(key); |
| luaState.getTable(-2); |
| try { |
| return luaState.toJavaObject(-1, Object.class); |
| } finally { |
| luaState.pop(2); |
| } |
| } |
| } |
| |
| @Override |
| public Object put(K key, Object value) { |
| checkKey(key); |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| Object oldValue = get(key); |
| pushValue(); |
| luaState.pushJavaObject(key); |
| luaState.pushJavaObject(value); |
| luaState.setTable(-3); |
| luaState.pop(1); |
| return oldValue; |
| } |
| } |
| |
| @Override |
| public Object remove(Object key) { |
| checkKey(key); |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| Object oldValue = get(key); |
| pushValue(); |
| luaState.pushJavaObject(key); |
| luaState.pushNil(); |
| luaState.setTable(-3); |
| luaState.pop(1); |
| return oldValue; |
| } |
| } |
| |
| // -- Protected methods |
| /** |
| * Checks a key for validity. If the key is not valid, the method throws an |
| * appropriate runtime exception. The method is invoked for all input keys. |
| * |
| * <p> |
| * This implementation checks that the key is not <code>null</code>. Lua |
| * does not allow <code>nil</code> as a table key. Subclasses may implement |
| * more restrictive checks. |
| * </p> |
| * |
| * @param key |
| * the key |
| * @throws NullPointerException |
| * if the key is <code>null</code> |
| */ |
| protected void checkKey(Object key) { |
| if (key == null) { |
| throw new NullPointerException("key must not be null"); |
| } |
| } |
| |
| /** |
| * Indicates if this table map filters keys from the Lua table. If the |
| * method returns <code>true</code>, the table map invokes |
| * {@link #acceptKey(int)} on each key retrieved from the underlying table |
| * to determine whether the key is accepted or rejected. |
| * |
| * <p> |
| * This implementation returns <code>false</code>. Subclasses may override |
| * the method alongside {@link #acceptKey(int)} to implement key filtering. |
| * </p> |
| * |
| * @return whether this table map filters keys from the Lua table |
| */ |
| protected boolean filterKeys() { |
| return false; |
| } |
| |
| /** |
| * Accepts or rejects a key from the Lua table. Only table keys that are |
| * accepted are processed. The method allows subclasses to filter the Lua |
| * table. The method is called only if {@link #filterKeys()} returns |
| * <code>true</code>. |
| * |
| * <p> |
| * This implementation returns <code>true</code> regardless of the input, |
| * thus accepting all keys. Subclasses may override the method alongside |
| * {@link #filterKeys()} to implement key filtering. |
| * </p> |
| * |
| * @param index |
| * the stack index containing the candidate key |
| * @return whether the key is accepted |
| */ |
| protected boolean acceptKey(int index) { |
| return true; |
| } |
| |
| /** |
| * Converts the key at the specified stack index to a Java object. If this |
| * table maps performs key filtering, the method is invoked only for keys it |
| * has accepted. |
| * |
| * @param index |
| * the stack index containing the key |
| * @return the Java object representing the key |
| * @see #filterKeys() |
| * @see #acceptKey(int) |
| */ |
| protected abstract K convertKey(int index); |
| |
| // -- Nested types |
| /** |
| * Lua table entry set. |
| */ |
| private class EntrySet extends AbstractSet<Map.Entry<K, Object>> { |
| // -- Set methods |
| @Override |
| public Iterator<Map.Entry<K, Object>> iterator() { |
| return new EntryIterator(); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| pushValue(); |
| luaState.pushNil(); |
| while (luaState.next(-2)) { |
| if (!filterKeys() || acceptKey(-2)) { |
| luaState.pop(3); |
| return false; |
| } |
| } |
| luaState.pop(1); |
| return true; |
| } |
| } |
| |
| @Override |
| public int size() { |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| int count = 0; |
| pushValue(); |
| if (filterKeys()) { |
| luaState.pushNil(); |
| while (luaState.next(-2)) { |
| if (acceptKey(-2)) { |
| count++; |
| } |
| luaState.pop(1); |
| } |
| } else { |
| count = luaState.tableSize(-1); |
| } |
| luaState.pop(1); |
| return count; |
| } |
| } |
| |
| @Override |
| public boolean contains(Object object) { |
| checkKey(object); |
| if (!(object instanceof AbstractTableMap<?>.Entry)) { |
| return false; |
| } |
| @SuppressWarnings("unchecked") |
| Entry luaTableEntry = (Entry) object; |
| if (luaTableEntry.getLuaState() != getLuaState()) { |
| return false; |
| } |
| return containsKey(luaTableEntry.key); |
| } |
| |
| @Override |
| public boolean remove(Object object) { |
| if (!(object instanceof AbstractTableMap<?>.Entry)) { |
| return false; |
| } |
| @SuppressWarnings("unchecked") |
| Entry luaTableEntry = (Entry) object; |
| if (luaTableEntry.getLuaState() != getLuaState()) { |
| return false; |
| } |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| pushValue(); |
| luaState.pushJavaObject(object); |
| luaState.getTable(-2); |
| boolean contains = !luaState.isNil(-1); |
| luaState.pop(1); |
| if (contains) { |
| luaState.pushJavaObject(object); |
| luaState.pushNil(); |
| luaState.setTable(-3); |
| } |
| luaState.pop(1); |
| return contains; |
| } |
| } |
| } |
| |
| /** |
| * Lua table iterator. |
| */ |
| private class EntryIterator implements Iterator<Map.Entry<K, Object>> { |
| // -- State |
| private K key; |
| |
| // -- Iterator methods |
| @Override |
| public boolean hasNext() { |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| pushValue(); |
| luaState.pushJavaObject(key); |
| while (luaState.next(-2)) { |
| if (!filterKeys() || acceptKey(-2)) { |
| luaState.pop(3); |
| return true; |
| } |
| } |
| luaState.pop(1); |
| return false; |
| } |
| } |
| |
| @Override |
| public Map.Entry<K, Object> next() { |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| pushValue(); |
| luaState.pushJavaObject(key); |
| while (luaState.next(-2)) { |
| if (!filterKeys() || acceptKey(-2)) { |
| key = convertKey(-2); |
| luaState.pop(3); |
| return new Entry(key); |
| } |
| } |
| luaState.pop(1); |
| throw new NoSuchElementException(); |
| } |
| } |
| |
| @Override |
| public void remove() { |
| LuaState luaState = getLuaState(); |
| synchronized (luaState) { |
| pushValue(); |
| luaState.pushJavaObject(key); |
| luaState.pushNil(); |
| luaState.setTable(-3); |
| luaState.pop(1); |
| } |
| } |
| } |
| |
| /** |
| * Bindings entry. |
| */ |
| private class Entry implements Map.Entry<K, Object> { |
| // -- State |
| private K key; |
| |
| // -- Construction |
| /** |
| * Creates a new instance. |
| */ |
| public Entry(K key) { |
| this.key = key; |
| } |
| |
| // -- Map.Entry methods |
| @Override |
| public K getKey() { |
| return key; |
| } |
| |
| @Override |
| public Object getValue() { |
| return get(key); |
| } |
| |
| @Override |
| public Object setValue(Object value) { |
| return put(key, value); |
| } |
| |
| // -- Object methods |
| @Override |
| public boolean equals(Object obj) { |
| if (!(obj instanceof AbstractTableMap<?>.Entry)) { |
| return false; |
| } |
| @SuppressWarnings("unchecked") |
| Entry other = (Entry) obj; |
| return getLuaState() == other.getLuaState() |
| && key.equals(other.key); |
| } |
| |
| @Override |
| public int hashCode() { |
| return getLuaState().hashCode() * 65599 + key.hashCode(); |
| } |
| |
| @Override |
| public String toString() { |
| return key.toString(); |
| } |
| |
| // -- Private methods |
| /** |
| * Returns the Lua script engine. |
| */ |
| private LuaState getLuaState() { |
| return AbstractTableMap.this.getLuaState(); |
| } |
| } |
| } |