/**
* Copyright 2009 Google Inc.
*
* 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.waveprotocol.wave.client.common.util;
import com.google.common.annotations.VisibleForTesting;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.ReadableStringMap;
import org.waveprotocol.wave.model.util.ReadableStringSet;
import org.waveprotocol.wave.model.util.StringMap;
import java.util.Map;
/**
* An implementation of StringMap<V> based on JavaScript objects.
*
* @author ohler@google.com (Christian Ohler)
*
* @param <V> type of values in the map
*/
public class JsoStringMap<V> implements StringMap<V> {
@VisibleForTesting public final JsoView backend;
private JsoStringMap(JsoView backend) {
this.backend = backend;
}
@SuppressWarnings("unchecked")
public static <V> JsoStringMap<V> create() {
return new JsoStringMap<V>(createBackend());
}
/** Construct an empty StringMap */
static JsoView createBackend() {
if (!QuirksConstants.DOES_NOT_SUPPORT_JSO_PROTO_FIELD) {
return createProtoless();
} else {
return JsoView.create();
}
}
/** Construct an empty StringMap with null for the hidden __proto__ field */
private static native JsoView createProtoless() /*-{
return {__proto__:null};
}-*/;
@Override
public void clear() {
backend.clear();
}
@Override
public boolean containsKey(String key) {
return backend.containsKey(escape(key));
}
@Override
public V getExisting(String key) {
key = escape(key);
if (!backend.containsKey(key)) {
// Not using Preconditions.checkState to avoid unecessary string concatenation
throw new IllegalStateException("getExisting: Key '" + key + "' is not in map");
}
return backend.<V>getObjectUnsafe(key);
}
@Override
public V get(String key, V defaultValue) {
key = escape(key);
if (backend.containsKey(key)) {
return backend.<V>getObjectUnsafe(key);
} else {
return defaultValue;
}
}
@Override
public V get(String key) {
return backend.<V>getObjectUnsafe(escape(key));
}
@Override
public void put(String key, V value) {
backend.setObject(escape(key), value);
}
@Override
public String someKey() {
return backend.firstKey();
}
/**
* The purpose of these methods is to prevent the __proto__ key from ever
* being set on the underlying JSO.
*/
static String escape(String key) {
Preconditions.checkNotNull(key, "StringMap/StringSet cannot contain null keys");
return QuirksConstants.DOES_NOT_SUPPORT_JSO_PROTO_FIELD || key.startsWith("__") ?
'_' + key : key;
}
static String unescape(String key) {
return QuirksConstants.DOES_NOT_SUPPORT_JSO_PROTO_FIELD || key.startsWith("__") ?
key.substring(1) : key;
}
@Override
public void putAll(ReadableStringMap<V> pairsToAdd) {
// This cast should not fail as we should not have other
// implementations of ReadableStringMap in the client.
backend.putAll(((JsoStringMap<V>) pairsToAdd).backend);
}
@Override
public void putAll(Map<String, V> pairsToAdd) {
for (Map.Entry<String, V> e : pairsToAdd.entrySet()) {
backend.setObject(escape(e.getKey()), e.getValue());
}
}
@Override
public void remove(String key) {
backend.remove(escape(key));
}
@Override
public void each(final ProcV<? super V> callback) {
backend.each(new ProcV<V>() {
@Override
public void apply(String key, V item) {
callback.apply(unescape(key), item);
}
});
}
@Override
public void filter(final EntryFilter<? super V> filter) {
backend.each(new ProcV<V>() {
@Override
public void apply(String key, V item) {
if (filter.apply(unescape(key), item)) {
// entry stays
} else {
backend.remove(key);
}
}
});
}
@Override
public boolean isEmpty() {
return backend.isEmpty();
}
@Override
public int countEntries() {
return backend.countEntries();
}
@SuppressWarnings("unchecked") // Safe because we return a read-only string set
@Override
public ReadableStringSet keySet() {
return new JsoStringSet(backend);
}
@Override
public String toString() {
final StringBuilder b = new StringBuilder("{");
each(new ProcV<V>() {
@Override
public void apply(String key, V item) {
if (b.length() > 1) {
b.append(",");
}
b.append(key + ":" + item);
}
});
b.append("}");
return b.toString();
}
}