| /* |
| * Copyright 2007 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 com.google.gwt.user.client.ui; |
| |
| import com.google.gwt.core.client.JavaScriptObject; |
| |
| import java.util.AbstractMap; |
| import java.util.AbstractSet; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Special-case Map implementation which imposes limits on the types of keys |
| * that can be used in return for much faster speed. In specific, only strings |
| * that could be added to a JavaScript object as keys are valid. |
| */ |
| |
| class FastStringMap<T> extends AbstractMap<String, T> { |
| private static class ImplMapEntry<T> implements Map.Entry<String, T> { |
| |
| private String key; |
| |
| private T value; |
| |
| ImplMapEntry(String key, T value) { |
| this.key = key; |
| this.value = value; |
| } |
| |
| @Override |
| public boolean equals(Object a) { |
| if (a instanceof Map.Entry<?, ?>) { |
| Map.Entry<?, ?> s = (Map.Entry<?, ?>) a; |
| if (equalsWithNullCheck(key, s.getKey()) |
| && equalsWithNullCheck(value, s.getValue())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // strip prefix from key |
| public String getKey() { |
| return key; |
| } |
| |
| public T getValue() { |
| return value; |
| } |
| |
| @Override |
| public int hashCode() { |
| int keyHash = 0; |
| int valueHash = 0; |
| if (key != null) { |
| keyHash = key.hashCode(); |
| } |
| if (value != null) { |
| valueHash = value.hashCode(); |
| } |
| return keyHash ^ valueHash; |
| } |
| |
| public T setValue(T object) { |
| T old = value; |
| value = object; |
| return old; |
| } |
| |
| private boolean equalsWithNullCheck(Object a, Object b) { |
| if (a == b) { |
| return true; |
| } else if (a == null) { |
| return false; |
| } else { |
| return a.equals(b); |
| } |
| } |
| } |
| |
| /* |
| * Accesses need to be prefixed with ':' to prevent conflict with built-in |
| * JavaScript properties. |
| */ |
| private JavaScriptObject map; |
| |
| public FastStringMap() { |
| init(); |
| } |
| |
| @Override |
| public void clear() { |
| init(); |
| } |
| |
| @Override |
| public boolean containsKey(Object key) { |
| return containsKey(keyMustBeString(key), map); |
| } |
| |
| @Override |
| public boolean containsValue(Object arg0) { |
| return values().contains(arg0); |
| } |
| |
| @Override |
| public Set<Map.Entry<String, T>> entrySet() { |
| return new AbstractSet<Map.Entry<String, T>>() { |
| |
| @Override |
| public boolean contains(Object key) { |
| Map.Entry<?, ?> s = (Map.Entry<?, ?>) key; |
| Object value = get(s.getKey()); |
| if (value == null) { |
| return value == s.getValue(); |
| } else { |
| return value.equals(s.getValue()); |
| } |
| } |
| |
| @Override |
| public Iterator<Map.Entry<String, T>> iterator() { |
| |
| Iterator<Map.Entry<String, T>> custom = new Iterator<Map.Entry<String, T>>() { |
| Iterator<String> keys = keySet().iterator(); |
| |
| public boolean hasNext() { |
| return keys.hasNext(); |
| } |
| |
| public Map.Entry<String, T> next() { |
| String key = keys.next(); |
| return new ImplMapEntry<T>(key, get(key)); |
| } |
| |
| public void remove() { |
| keys.remove(); |
| } |
| }; |
| return custom; |
| } |
| |
| @Override |
| public int size() { |
| return FastStringMap.this.size(); |
| } |
| |
| }; |
| } |
| |
| @Override |
| public T get(Object key) { |
| return get(keyMustBeString(key)); |
| } |
| |
| // Prepend ':' to avoid conflicts with built-in Object properties. |
| public native T get(String key) /*-{ |
| return this.@com.google.gwt.user.client.ui.FastStringMap::map[':' + key]; |
| }-*/; |
| |
| @Override |
| public boolean isEmpty() { |
| return size() == 0; |
| } |
| |
| @Override |
| public Set<String> keySet() { |
| return new AbstractSet<String>() { |
| @Override |
| public boolean contains(Object key) { |
| return containsKey(key); |
| } |
| |
| @Override |
| public Iterator<String> iterator() { |
| List<String> l = new ArrayList<String>(); |
| addAllKeysFromJavascriptObject(l, map); |
| return l.iterator(); |
| } |
| |
| @Override |
| public int size() { |
| return FastStringMap.this.size(); |
| } |
| }; |
| } |
| |
| // Prepend ':' to avoid conflicts with built-in Object properties. |
| @Override |
| public native T put(String key, T value) /*-{ |
| key = ':' + key; |
| var map = this.@com.google.gwt.user.client.ui.FastStringMap::map; |
| var previous = map[key]; |
| map[key] = value; |
| return previous; |
| }-*/; |
| |
| @Override |
| public void putAll(Map<? extends String, ? extends T> arg0) { |
| for (Map.Entry<? extends String, ? extends T> entry : arg0.entrySet()) { |
| put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| @Override |
| public T remove(Object key) { |
| return remove(keyMustBeString(key)); |
| } |
| |
| // only count keys with ':' prefix |
| @Override |
| public native int size() /*-{ |
| var value = this.@com.google.gwt.user.client.ui.FastStringMap::map; |
| var count = 0; |
| for(var key in value) { |
| if (key.charAt(0) == ':') ++count; |
| } |
| return count; |
| }-*/; |
| |
| @Override |
| public Collection<T> values() { |
| List<T> values = new ArrayList<T>(); |
| addAllValuesFromJavascriptObject(values, map); |
| return values; |
| } |
| |
| // only count keys with ':' prefix |
| private native void addAllKeysFromJavascriptObject(Collection<String> s, |
| JavaScriptObject javaScriptObject) /*-{ |
| for(var key in javaScriptObject) { |
| if (key.charAt(0) != ':') continue; |
| s.@java.util.Collection::add(Ljava/lang/Object;)(key.substring(1)); |
| } |
| }-*/; |
| |
| // only count keys with ':' prefix |
| private native void addAllValuesFromJavascriptObject(Collection<T> s, |
| JavaScriptObject javaScriptObject) /*-{ |
| for(var key in javaScriptObject) { |
| if (key.charAt(0) != ':') continue; |
| var value = javaScriptObject[key]; |
| s.@java.util.Collection::add(Ljava/lang/Object;)(value); |
| } |
| }-*/; |
| |
| // Prepend ':' to avoid conflicts with built-in Object properties. |
| private native boolean containsKey(String key, JavaScriptObject obj)/*-{ |
| return (':' + key) in obj; |
| }-*/; |
| |
| private native void init() /*-{ |
| this.@com.google.gwt.user.client.ui.FastStringMap::map = []; |
| }-*/; |
| |
| private String keyMustBeString(Object key) { |
| if (key instanceof String) { |
| return (String) key; |
| } else { |
| throw new IllegalArgumentException(this.getClass().getName() |
| + " can only have Strings as keys, not" + key); |
| } |
| } |
| |
| // Prepend ':' to avoid conflicts with built-in Object properties. |
| private native T remove(String key) /*-{ |
| key = ':' + key; |
| var map = this.@com.google.gwt.user.client.ui.FastStringMap::map; |
| var previous = map[key]; |
| delete map[key]; |
| return previous; |
| }-*/; |
| } |