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