/*
* Copyright 2011 Chad Retz
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.gwtnode.core;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString;
/**
* Map for a JSON object that is keyed by string. Counts will
* not work correctly if the JavaScriptObject is modified outside
* of this class. Also, do NOT try to modify this collection during
* iteration except via {@link Entry#setValue(Object)}.
* {@link Iterator#remove()} is NOT supported, even though
* {@link Map#remove(Object)} is (but not during iteration).
*
* @author Chad Retz
*/
public class JsonStringObjectMap<T> extends AbstractMap<String, T> {
//XXX: It appears that GWT's JSNI 'this' reference doesn't like
// non-static inner classes. That's why all my inner classes here
// are static
private native int getCount(JavaScriptObject obj) /*-{
var cnt = 0;
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
cnt++;
}
}
return cnt;
}-*/;
private final JavaScriptObject map;
private int count = 0;
public JsonStringObjectMap() {
this(JavaScriptObject.createObject());
}
public JsonStringObjectMap(JavaScriptObject map) {
this.map = map;
count = getCount(map);
}
private void incrementCount() {
count++;
}
private void decrementCount() {
count--;
}
public JavaScriptObject getNativeObject() {
return map;
}
@Override
public native T put(String key, T value) /*-{
var map = this.@org.gwtnode.core.JsonStringObjectMap::map;
if (!map.hasOwnProperty(key)) {
this.@org.gwtnode.core.JsonStringObjectMap::incrementCount();
}
map[key] = value;
}-*/;
@Override
public native T get(Object key) /*-{
var map = this.@org.gwtnode.core.JsonStringObjectMap::map;
return map[key];
}-*/;
@Override
public native boolean containsKey(Object key) /*-{
var map = this.@org.gwtnode.core.JsonStringObjectMap::map;
return map[key];
}-*/;
@Override
public native T remove(Object key) /*-{
var map = this.@org.gwtnode.core.JsonStringObjectMap::map;
var ret = null;
if (map[key]) {
ret = map[key];
delete map[key];
this.@org.gwtnode.core.JsonStringObjectMap::decrementCount();
}
return ret;
}-*/;
@Override
public Set<Entry<String, T>> entrySet() {
return new JsonStringObjectEntrySet<T>(this);
}
private static class JsonStringObjectEntrySet<T> extends AbstractSet<Entry<String, T>> {
private final JsonStringObjectMap<T> base;
private JsonStringObjectEntrySet(JsonStringObjectMap<T> base) {
this.base = base;
}
@Override
public Iterator<Entry<String, T>> iterator() {
return new JsonStringObjectIterator<T>(base.map);
}
@Override
public int size() {
return base.count;
}
}
private static class JsonStringObjectIterator<T> implements Iterator<Entry<String, T>> {
private final JavaScriptObject map;
private final JsArrayString keys;
private int index = 0;
private JsonStringObjectIterator(JavaScriptObject map) {
this.map = map;
keys = getKeys(map);
}
private native JsArrayString getKeys(JavaScriptObject obj) /*-{
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys;
}-*/;
@Override
public boolean hasNext() {
return keys.length() > index;
}
@Override
public Entry<String, T> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Entry<String, T> ret = new JsonStringObjectEntry<T>(keys.get(index), map);
index++;
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
private static class JsonStringObjectEntry<T> implements Entry<String, T> {
private final JavaScriptObject map;
private final String key;
private JsonStringObjectEntry(String key, JavaScriptObject map) {
this.map = map;
this.key = key;
}
@Override
public String getKey() {
return key;
}
@Override
public native T getValue() /*-{
var map = this.@org.gwtnode.core.JsonStringObjectMap.JsonStringObjectEntry::map;
var key = this.@org.gwtnode.core.JsonStringObjectMap.JsonStringObjectEntry::key;
return map[key];
}-*/;
@Override
public native T setValue(T value) /*-{
var map = this.@org.gwtnode.core.JsonStringObjectMap.JsonStringObjectEntry::map;
var key = this.@org.gwtnode.core.JsonStringObjectMap.JsonStringObjectEntry::key;
var ret = map[key];
map[key] = value;
return ret;
}-*/;
}
}