| /* | |
| * $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(); | |
| } | |
| } | |
| } |