package org.swellrt.beta.model.remote;
import java.util.HashMap;
import java.util.Set;
import org.swellrt.beta.common.SException;
import org.swellrt.beta.model.SEvent;
import org.swellrt.beta.model.SMap;
import org.swellrt.beta.model.SNode;
import org.swellrt.beta.model.SPrimitive;
import org.swellrt.beta.model.SUtils;
import org.swellrt.beta.model.js.HasJsProxy;
import org.swellrt.beta.model.js.Proxy;
import org.swellrt.beta.model.js.SMapProxyHandler;
import org.waveprotocol.wave.model.adt.ObservableBasicMap;
public class SMapRemote extends SNodeRemoteContainer implements SMap, HasJsProxy, ObservableBasicMap.Listener<String, SNodeRemote> {
public static SMapRemote create(SObjectRemote object, SubstrateId substrateId, ObservableBasicMap<String, SNodeRemote> map) {
return new SMapRemote(object, substrateId, map);
}
/** the underlying wave map */
private final ObservableBasicMap<String, SNodeRemote> map;
/** cache of SNodeRemote instances in the map */
private final HashMap<String, SNodeRemote> cache;
private Proxy proxy;
protected SMapRemote(SObjectRemote object, SubstrateId substrateId, ObservableBasicMap<String, SNodeRemote> map) {
super(substrateId, object);
this.cache = new HashMap<String, SNodeRemote>();
this.map = map;
this.map.addListener(this);
}
@Override
protected void clearCache() {
cache.clear();
for (SNodeRemote n: cache.values())
if (n instanceof SNodeRemoteContainer)
((SNodeRemoteContainer) n).clearCache();
}
/**
* Perform a sanity check. Raise an exception if this node
* can't perform the operation or the container object is
* in a bad state.
* <p>
* Don't use it for read operations to avoid client frameworks
* (like angular2) receiving exceptions in templates.
*/
protected void check() throws SException {
if (this.getParent() == null)
throw new SException(SException.NOT_ATTACHED_NODE);
getObject().check();
}
@Override
public void clear() throws SException {
check();
Set<String> keys = map.keySet();
for (String k: keys) {
map.remove(k);
}
cache.clear();
}
@Override
public boolean has(String key) throws SException {
check();
return map.keySet().contains(key);
}
@Override
public SNode getNode(String key) throws SException {
// Don't call check here, this is a read operation!
if (!map.keySet().contains(key))
return null;
SNodeRemote node = null;
if (!cache.containsKey(key)) {
node = map.get(key);
if (node instanceof SPrimitive) {
((SPrimitive) node).setNameKey(key);
}
// This should be always true!
if (node instanceof SNodeRemote)
((SNodeRemote) node).attach(this); // lazily set parent
cache.put(key, node);
} else {
node = cache.get(key);
}
return node;
}
@Override
public Object get(String key) throws SException {
SNode node = getNode(key);
getObject().checkReadable(node);
if (node == null)
return null;
if (node instanceof SPrimitive)
return ((SPrimitive) node).get();
return node;
}
@Override
public boolean isEmpty() {
return map.keySet().isEmpty();
}
@Override
public String[] keys() throws SException {
check();
return map.keySet().toArray(new String[map.keySet().size()]);
}
@Override
public SMap put(String key, SNode value) throws SException {
check();
getObject().checkWritable(getNode(key));
SNodeRemote remoteValue = getObject().transformToRemote(value, this, false);
map.put(key, remoteValue);
cache.put(key, remoteValue);
return this;
}
@Override
public SMap put(String key, Object value) throws SException {
SNode node = SUtils.castToSNode(value);
return put(key, node);
}
@Override
public void remove(String key) throws SException {
check();
getObject().checkWritable(getNode(key));
if (!map.keySet().contains(key))
return;
SNodeRemote nr = map.get(key);
if (nr instanceof SNodeRemoteContainer) {
SNodeRemoteContainer nrc = (SNodeRemoteContainer) nr;
nrc.deattach();
}
map.remove(key);
cache.remove(key);
getObject().deleteNode(nr);
}
@Override
public int size() {
return map.keySet().size();
}
@Override
public Object asNative() {
if (proxy == null)
proxy = new Proxy(this, new SMapProxyHandler());
return proxy;
}
@Override
public void setJsProxy(Proxy proxy) {
this.proxy = proxy;
}
@Override
public Proxy getJsProxy() {
return this.proxy;
}
//
// Event handling
//
@Override
public void onEntrySet(String key, SNodeRemote oldValue, SNodeRemote newValue) {
//System.out.println("Map("+this.toString()+") onEntrySet [key="+key+" oldValue="+(oldValue != null ? oldValue : "null")+ " newValue="+(newValue != null ? newValue : "null")+"]");
try {
check(); // Ignore events if state is inconsistent
SNode eventValue = null;
int eventType = -1;
SNode cachedValue = cache.remove(key); // refresh cache in any case
// on removed
if (newValue == null) {
eventType = SEvent.REMOVED_VALUE;
if (cachedValue instanceof SNodeRemoteContainer)
((SNodeRemoteContainer) cachedValue).deattach();
eventValue = cachedValue;
// on added
} else if (oldValue == null) {
eventType = SEvent.ADDED_VALUE;
// ensure attach the node, set keyname
eventValue = getNode(key);
// on updated
} else {
eventType = SEvent.UPDATED_VALUE;
// ensure attach the node, set keyname
eventValue = getNode(key);
}
SEvent e = new SEvent(eventType, this, key, eventValue);
triggerEvent(e);
} catch (SException e) {
// Swallow it
}
}
@Override
public String toString() {
return "SMapRemote ["+getSubstrateId()+"]";
}
}