/* * Copyright 2011 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.storage.client; 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; /** * Exposes the local/session {@link Storage} as a standard {@link Map * Map<String, String>}. * * <p> * <span style="color:red">Experimental API: This API is still under development * and is subject to change. </span> * </p> * * <p> * The following characteristics are associated with this Map: * </p> * <ol> * <li><em>Mutable</em> - All 'write' methods ({@link #put(String, String)}, * {@link #putAll(Map)}, {@link #remove(Object)}, {@link #clear()}, * {@link Entry#setValue(Object)}) operate as intended;</li> * <li><em>remove() on Iterators</em> - All remove() operations on available * Iterators (from {@link #keySet()}, {@link #entrySet()} and {@link #values()}) * operate as intended;</li> * <li><em>No <code>null</code> values and keys</em> - The Storage doesn't * accept keys or values which are <code>null</code>;</li> * <li><em>JavaScriptException instead of NullPointerException</em> - Some Map * (or other Collection) methods mandate the use of a * {@link NullPointerException} if some argument is <code>null</code> (e.g. * {@link #remove(Object)} remove(null)). this Map emits * {@link com.google.gwt.core.client.JavaScriptException}s instead;</li> * <li><em>String values and keys</em> - All keys and values in this Map are * String types.</li> * </ol> */ public class StorageMap extends AbstractMap<String, String> { /* * Represents a Map.Entry to a Storage item */ private class StorageEntry implements Map.Entry<String, String> { private final String key; public StorageEntry(String key) { this.key = key; } @Override @SuppressWarnings("rawtypes") public boolean equals(Object obj) { if (obj == null) { return false; } else if (obj == this) { return true; } else if (!(obj instanceof Map.Entry)) { return false; } Map.Entry e = (Map.Entry) obj; return eq(key, e.getKey()) && eq(getValue(), e.getValue()); } public String getKey() { return key; } public String getValue() { return storage.getItem(key); } @Override public int hashCode() { String value = getValue(); return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } public String setValue(String value) { String oldValue = storage.getItem(key); storage.setItem(key, value); return oldValue; } } /* * Represents an Iterator over all Storage items */ private class StorageEntryIterator implements Iterator< Map.Entry<String, String>> { private int index = -1; private boolean removed = false; public boolean hasNext() { return index < size() - 1; } public Map.Entry<String, String> next() { if (hasNext()) { index++; removed = false; return new StorageEntry(storage.key(index)); } throw new NoSuchElementException(); } public void remove() { if (index >= 0 && index < size()) { if (removed) { throw new IllegalStateException( "Cannot remove() Entry - already removed!"); } storage.removeItem(storage.key(index)); removed = true; index--; } else { throw new IllegalStateException( "Cannot remove() Entry - index=" + index + ", size=" + size()); } } } /* * Represents a Set<Map.Entry> over all Storage items */ private class StorageEntrySet extends AbstractSet<Map.Entry<String, String>> { @Override public void clear() { StorageMap.this.clear(); } @SuppressWarnings("rawtypes") @Override public boolean contains(Object o) { if (o == null || !(o instanceof Map.Entry)) { return false; } Map.Entry e = (Map.Entry) o; Object key = e.getKey(); return key != null && containsKey(key) && eq(get(key), e.getValue()); } @Override public Iterator<Map.Entry<String, String>> iterator() { return new StorageEntryIterator(); } @SuppressWarnings("rawtypes") @Override public boolean remove(Object o) { if (o == null || !(o instanceof Map.Entry)) { return false; } Map.Entry e = (Map.Entry) o; if (e.getKey() == null) { return false; } String key = e.getKey().toString(); String value = storage.getItem(key); if (eq(value, e.getValue())) { return StorageMap.this.remove(key) != null; } return false; } @Override public int size() { return StorageMap.this.size(); } } private Storage storage; private StorageEntrySet entrySet; /** * Creates the Map with the specified Storage as data provider. * * @param storage a local/session Storage instance obtained by either * {@link Storage#getLocalStorageIfSupported()} or * {@link Storage#getSessionStorageIfSupported()}. */ public StorageMap(Storage storage) { assert storage != null : "storage cannot be null"; this.storage = storage; } /** * Removes all items from the Storage. * * @see Storage#clear() */ @Override public void clear() { storage.clear(); } /** * Returns <code>true</code> if the Storage contains the specified key, <code> * false</code> otherwise. */ @Override public boolean containsKey(Object key) { return storage.getItem(key.toString()) != null; } /** * Returns <code>true</code> if the Storage contains the specified value, * <code>false</code> otherwise (or if the specified key is <code>null</code> * ). */ @Override public boolean containsValue(Object value) { int s = size(); for (int i = 0; i < s; i++) { if (value.equals(storage.getItem(storage.key(i)))) { return true; } } return false; } /** * Returns a Set containing all entries of the Storage. */ @Override public Set<Map.Entry<String, String>> entrySet() { if (entrySet == null) { entrySet = new StorageEntrySet(); } return entrySet; } /** * Returns the value associated with the specified key in the Storage. * * @param key the key identifying the value * @see Storage#getItem(String) */ @Override public String get(Object key) { if (key == null) { return null; } return storage.getItem(key.toString()); } /** * Adds (or overwrites) a new key/value pair in the Storage. * * @param key the key identifying the value (not <code>null</code>) * @param value the value associated with the key (not <code>null</code>) * @see Storage#setItem(String, String) */ @Override public String put(String key, String value) { if (key == null || value == null) { throw new IllegalArgumentException("Key and value cannot be null!"); } String old = storage.getItem(key); storage.setItem(key, value); return old; } /** * Removes the key/value pair from the Storage. * * @param key the key identifying the item to remove * @return the value associated with the key - <code>null</code> if the key * was not present in the Storage * @see Storage#removeItem(String) */ @Override public String remove(Object key) { String k = key.toString(); String old = storage.getItem(k); storage.removeItem(k); return old; } /** * Returns the number of items in the Storage. * * @return the number of items * @see Storage#getLength() */ @Override public int size() { return storage.getLength(); } private boolean eq(Object a, Object b) { if (a == b) { return true; } if (a == null) { return false; } return a.equals(b); } }