package xapi.gwt.collect;
import xapi.annotation.inject.InstanceOverride;
import xapi.collect.api.StringTo;
import xapi.collect.impl.ArrayIterable;
import xapi.collect.impl.EntryValueAdapter;
import xapi.collect.impl.IteratorWrapper;
import xapi.fu.MappedIterable;
import xapi.fu.Out2;
import xapi.platform.GwtPlatform;
import static xapi.fu.MappedIterable.mapIterable;
import com.google.gwt.core.client.GwtScriptOnly;
import com.google.gwt.core.client.JavaScriptObject;
import java.util.Iterator;
import java.util.Map.Entry;
/**
* This is an old-school jso-style implementation of an insertion ordered map that is optimized for gwt.
* TODO: make a simpler version that does not preserve order and use it when we don't care to pay for the extra abstraction.
*/
@InstanceOverride(implFor=StringTo.class)
@SuppressWarnings("serial")
@GwtScriptOnly
@GwtPlatform
public class StringToGwt <V> extends JavaScriptObject implements StringTo<V>{
protected StringToGwt() {
}
public static StringTo<Object> newInstance() {
return create(Object.class);
}
public static native <V> StringTo<V> create(Class<? extends V> valueType)
/*-{
return {_v$: valueType, _k: @xapi.gwt.collect.StringToGwt::newStringArray()() };
}-*/;
public static native StringToGwt<Object> create()
/*-{
return {_k: @xapi.gwt.collect.StringToGwt::newStringArray()() };
}-*/;
public static native <T> StringToGwt<T> fromJso(JavaScriptObject o)
/*-{
if (o.hasOwnProperty("_v$") && o.hasOwnProperty("_k")) {
return o; // TODO: perhaps make a copy?
}
var m = @xapi.gwt.collect.StringToGwt::create()();
for (var i in o) {
if (o.hasOwnProperty(i)) {
var slot = {i : this._k.length, v: o[i]};
m._k[slot.i] = i;
m[i] = slot;
}
}
}-*/;
private static String[] newStringArray() {
return new String[0];
}
static class KeyItr extends ArrayIterable<String>{
private final StringToGwt src;
public KeyItr(StringToGwt from) {
super(from.keyArray());
this.src = from;
}
@Override
protected void remove(final String key) {
src.remove(key);
}
}
static class EntryItr <V> implements Iterator<Entry<String, V>> {
final StringToGwt<V> src;
int pos = 0;
String[] keys;
int max;
Entry<String, V> entry;
EntryItr(StringToGwt<V> src) {
this.src = src;
keys = src.keyArray();
max = keys.length;
}
@Override
public boolean hasNext() {
return pos < max;
}
@Override
public Entry<String,V> next() {
final String key = keys[pos++];
final V next = src.get(key);
entry = new Entry<String,V>() {
@Override
public String getKey() {
return key;
}
@Override
public V getValue() {
return next;
}
@Override
public V setValue(final V value) {
if (value == null) {
return src.remove(key);
} else {
return src.put(key, value);
}
}
};
return entry;
}
@Override
public void remove() {
assert entry != null : "You must call next() before remove() in StringToGwt.entries()";
src.remove(entry.getKey());
}
}
public final native JavaScriptObject toJso()
/*-{
var out = {}, i = this._k.length;
while(i --> 0) {
var k = this._k[i];
out[k] = this[k].v;
}
return out;
}-*/;
@Override
public final native String[] keyArray()
/*-{
return @javaemul.internal.ArrayHelper::clone([Ljava/lang/Object;II)(this._k, 0, this._k.length);
}-*/;
private final native String[] nativeKeys()
/*-{
return this._k;
}-*/;
@Override
public final native boolean containsKey(Object key)
/*-{
return this.hasOwnProperty(key)&&this[key] != undefined;
}-*/;
@Override
public final boolean containsValue(final Object value) {
final String[] keys = nativeKeys();
int i = keys.length;
if (value == null) {
for (;i-->0;) {
if (get(keys[i]) == null) {
return true;
}
}
} else {
for (;i-->0;) {
if (value.equals(get(keys[i]))) {
return true;
}
}
}
return false;
}
@Override
public final native V get(String key)
/*-{
return this.hasOwnProperty(key) ? this[key].v : null;
}-*/;
@Override
@GwtScriptOnly
public final native V put(String key, V value)
/*-{
var index = this._k.indexOf(key);
if (index == -1) {
var slot = {i : this._k.length, v: value};
this[key] = slot;
this._k[slot.i] = key;
return null;
}
var slot = this[this._k[index]], v = slot.v;
slot.v = value;
return v;
}-*/;
@Override
public final native boolean isEmpty()
/*-{
return this._k.length == 0;
}-*/;
@Override
public final native void clear()
/*-{
for (
var i = this._k.length;
i -->0;
delete this[this._k[i]]
);
this._k.length = 0;
}-*/;
@Override
public final native V remove(String key)
/*-{
var index = this._k.indexOf(key);
if (index == -1) return null;
var value = this[key];
delete this[key];
this._k.splice(index, 1);
return value.v;
}-*/;
@Override
public final void putAll(final Iterable<Entry<String,V>> items) {
for (final Entry<String, V> item : items) {
put(item.getKey(), item.getValue());
}
}
@Override
public final void addAll(final Iterable<Out2<String,V>> items) {
for (final Out2<String, V> item : items) {
put(item.out1(), item.out2());
}
}
@Override
public final void removeAll(final Iterable<String> items) {
for (final String item : items) {
remove(item);
}
}
@Override
public final Iterable<String> keys() {
return new KeyItr(this);
}
@Override
public final Iterable<V> values() {
return new EntryValueAdapter<String, V>(entries());
}
@Override
public final Class<String> keyType() {
return String.class;
}
@Override
public final native Class<V> valueType()
/*-{
return this._v$;
}-*/;
@Override
public final Iterable<Entry<String,V>> entries() {
return new IteratorWrapper<>(new EntryItr(this));
}
@Override
public final MappedIterable<Out2<String, V>> view() {
return ()-> mapIterable(entries(), Out2::fromEntry).iterator();
}
@Override
public final int size() {
return nativeKeys().length;
}
}