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