001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016:
017: package com.google.gwt.user.client.ui;
018:
019: import com.google.gwt.core.client.JavaScriptObject;
020:
021: import java.util.AbstractMap;
022: import java.util.AbstractSet;
023: import java.util.ArrayList;
024: import java.util.Collection;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Set;
029:
030: /**
031: * Special-case Map implementation which imposes limits on the types of keys
032: * that can be used in return for much faster speed. In specific, only strings
033: * that could be added to a JavaScript object as keys are valid.
034: */
035:
036: class FastStringMap<T> extends AbstractMap<String, T> {
037: private static class ImplMapEntry<T> implements
038: Map.Entry<String, T> {
039:
040: private String key;
041:
042: private T value;
043:
044: ImplMapEntry(String key, T value) {
045: this .key = key;
046: this .value = value;
047: }
048:
049: @Override
050: public boolean equals(Object a) {
051: if (a instanceof Map.Entry) {
052: Map.Entry<?, ?> s = (Map.Entry<?, ?>) a;
053: if (equalsWithNullCheck(key, s.getKey())
054: && equalsWithNullCheck(value, s.getValue())) {
055: return true;
056: }
057: }
058: return false;
059: }
060:
061: // strip prefix from key
062: public String getKey() {
063: return key;
064: }
065:
066: public T getValue() {
067: return value;
068: }
069:
070: @Override
071: public int hashCode() {
072: int keyHash = 0;
073: int valueHash = 0;
074: if (key != null) {
075: keyHash = key.hashCode();
076: }
077: if (value != null) {
078: valueHash = value.hashCode();
079: }
080: return keyHash ^ valueHash;
081: }
082:
083: public T setValue(T object) {
084: T old = value;
085: value = object;
086: return old;
087: }
088:
089: private boolean equalsWithNullCheck(Object a, Object b) {
090: if (a == b) {
091: return true;
092: } else if (a == null) {
093: return false;
094: } else {
095: return a.equals(b);
096: }
097: }
098: }
099:
100: /*
101: * Accesses need to be prefixed with ':' to prevent conflict with built-in
102: * JavaScript properties.
103: */
104: private JavaScriptObject map;
105:
106: public FastStringMap() {
107: init();
108: }
109:
110: @Override
111: public void clear() {
112: init();
113: }
114:
115: @Override
116: public boolean containsKey(Object key) {
117: return containsKey(keyMustBeString(key), map);
118: }
119:
120: @Override
121: public boolean containsValue(Object arg0) {
122: return values().contains(arg0);
123: }
124:
125: @Override
126: public Set<Map.Entry<String, T>> entrySet() {
127: return new AbstractSet<Map.Entry<String, T>>() {
128:
129: @Override
130: public boolean contains(Object key) {
131: Map.Entry<?, ?> s = (Map.Entry<?, ?>) key;
132: Object value = get(s.getKey());
133: if (value == null) {
134: return value == s.getValue();
135: } else {
136: return value.equals(s.getValue());
137: }
138: }
139:
140: @Override
141: public Iterator<Map.Entry<String, T>> iterator() {
142:
143: Iterator<Map.Entry<String, T>> custom = new Iterator<Map.Entry<String, T>>() {
144: Iterator<String> keys = keySet().iterator();
145:
146: public boolean hasNext() {
147: return keys.hasNext();
148: }
149:
150: public Map.Entry<String, T> next() {
151: String key = keys.next();
152: return new ImplMapEntry<T>(key, get(key));
153: }
154:
155: public void remove() {
156: keys.remove();
157: }
158: };
159: return custom;
160: }
161:
162: @Override
163: public int size() {
164: return FastStringMap.this .size();
165: }
166:
167: };
168: }
169:
170: @Override
171: public T get(Object key) {
172: return get(keyMustBeString(key));
173: }
174:
175: // Prepend ':' to avoid conflicts with built-in Object properties.
176: public native T get(String key) /*-{
177: var value
178: = this.@com.google.gwt.user.client.ui.FastStringMap::map[':' + key];
179: return (value == null) ? null : value;
180: }-*/;
181:
182: @Override
183: public boolean isEmpty() {
184: return size() == 0;
185: }
186:
187: @Override
188: public Set<String> keySet() {
189: return new AbstractSet<String>() {
190: @Override
191: public boolean contains(Object key) {
192: return containsKey(key);
193: }
194:
195: @Override
196: public Iterator<String> iterator() {
197: List<String> l = new ArrayList<String>();
198: addAllKeysFromJavascriptObject(l, map);
199: return l.iterator();
200: }
201:
202: @Override
203: public int size() {
204: return FastStringMap.this .size();
205: }
206: };
207: }
208:
209: // Prepend ':' to avoid conflicts with built-in Object properties.
210: @Override
211: public native T put(String key, T widget) /*-{
212: var previous
213: = this.@com.google.gwt.user.client.ui.FastStringMap::map[':' + key];
214: this.@com.google.gwt.user.client.ui.FastStringMap::map[':' + key] = widget;
215: return (previous == null) ? null : previous;
216: }-*/;
217:
218: @Override
219: public void putAll(Map<? extends String, ? extends T> arg0) {
220: for (Map.Entry<? extends String, ? extends T> entry : arg0
221: .entrySet()) {
222: put(entry.getKey(), entry.getValue());
223: }
224: }
225:
226: @Override
227: public T remove(Object key) {
228: return remove(keyMustBeString(key));
229: }
230:
231: // only count keys with ':' prefix
232: @Override
233: public native int size() /*-{
234: var value = this.@com.google.gwt.user.client.ui.FastStringMap::map;
235: var count = 0;
236: for(var key in value) {
237: if (key.charAt(0) == ':') ++count;
238: }
239: return count;
240: }-*/;
241:
242: @Override
243: public Collection<T> values() {
244: List<T> values = new ArrayList<T>();
245: addAllValuesFromJavascriptObject(values, map);
246: return values;
247: }
248:
249: // only count keys with ':' prefix
250: private native void addAllKeysFromJavascriptObject(
251: Collection<String> s, JavaScriptObject javaScriptObject) /*-{
252: for(var key in javaScriptObject) {
253: if (key.charAt(0) != ':') continue;
254: s.@java.util.Collection::add(Ljava/lang/Object;)(key.substring(1));
255: }
256: }-*/;
257:
258: // only count keys with ':' prefix
259: private native void addAllValuesFromJavascriptObject(
260: Collection<T> s, JavaScriptObject javaScriptObject) /*-{
261: for(var key in javaScriptObject) {
262: if (key.charAt(0) != ':') continue;
263: var value = javaScriptObject[key];
264: s.@java.util.Collection::add(Ljava/lang/Object;)(value);
265: }
266: }-*/;
267:
268: // Prepend ':' to avoid conflicts with built-in Object properties.
269: private native boolean containsKey(String key, JavaScriptObject obj)/*-{
270: return obj[':' + key] !== undefined;
271: }-*/;
272:
273: private native void init() /*-{
274: this.@com.google.gwt.user.client.ui.FastStringMap::map = [];
275: }-*/;
276:
277: private String keyMustBeString(Object key) {
278: if (key instanceof String) {
279: return (String) key;
280: } else {
281: throw new IllegalArgumentException(this .getClass()
282: .getName()
283: + " can only have Strings as keys, not" + key);
284: }
285: }
286:
287: // Prepend ':' to avoid conflicts with built-in Object properties.
288: private native T remove(String key) /*-{
289: var previous
290: = this.@com.google.gwt.user.client.ui.FastStringMap::map[':' + key];
291: delete this.@com.google.gwt.user.client.ui.FastStringMap::map[':' + key];
292: return (previous == null) ? null : previous;
293: }-*/;
294: }
|