Java tutorial
/* * Copyright 2013 Xi CHEN * * 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.seanchenxi.gwt.storage.client; import java.io.Serializable; import java.util.HashSet; import java.util.Set; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; import com.google.gwt.core.client.JavaScriptException; import com.google.gwt.storage.client.Storage; import com.google.gwt.user.client.rpc.SerializationException; import com.google.web.bindery.event.shared.HandlerRegistration; import com.seanchenxi.gwt.storage.client.cache.StorageCache; import com.seanchenxi.gwt.storage.client.serializer.StorageSerializer; /** * Extends the GWT HTML5 Storage API, by adding <b>Object Value</b> Support. * * * <p> * You can obtain a Storage by either invoking * {@link #getLocalStorage()} or * {@link #getSessionStorage()}. * </p> * * * <p> * If Web Storage is NOT supported in the browser, these methods return <code> * null</code>. * </p> * * * <p> * Note: Storage events into other windows are not supported. * </p> * * * <p> * This may not be supported on all browsers. * </p> * * * @see <a href="http://www.w3.org/TR/webstorage/#storage-0">W3C Web Storage - Storage</a> * @see <a href="http://caniuse.com/#feat=namevalue-storage">Can I use... - Web Storage</a> * @see <a href="https://developers.google.com/web-toolkit/doc/latest/DevGuideHtml5Storage">GWT Developer's Guide - Client-side Storage (Web Storage)</a> * @see <a href=" * http://google-web-toolkit.googlecode.com/svn/javadoc/latest/com/google/gwt/storage/client/Storage.html"> * com.google.gwt.storage.client.Storage</a> */ public final class StorageExt { private static StorageExt localStorage; private static StorageExt sessionStorage; private static final StorageSerializer TYPE_SERIALIZER = GWT.create(StorageSerializer.class); /** * Returns a Local Storage. * * @return the localStorage instance, or <code>null</code> if Web Storage is NOT supported. */ public static StorageExt getLocalStorage() { if (localStorage == null && Storage.isLocalStorageSupported()) { localStorage = new StorageExt(Storage.getLocalStorageIfSupported()); } return localStorage; } /** * Returns a Session Storage. * * @return the sessionStorage instance, or <code>null</code> if Web Storage is NOT supported. */ public static StorageExt getSessionStorage() { if (sessionStorage == null && Storage.isSessionStorageSupported()) { sessionStorage = new StorageExt(Storage.getSessionStorageIfSupported()); } return sessionStorage; } private StorageChangeEvent.Level eventLevel; private Set<StorageChangeEvent.Handler> handlers; private final StorageCache cache; private final Storage storage; /** * This class can never be instantiated externally. Use * {@link #getLocalStorage()} ()} or * {@link #getSessionStorage()} ()} instead. */ private StorageExt(Storage storage) { assert storage != null : "Storage can not be null, check your browser's HTML 5 support state."; this.storage = storage; this.cache = GWT.create(StorageCache.class); this.eventLevel = StorageChangeEvent.Level.STRING; } /** * Registers an event handler for {@link StorageChangeEvent} * * @param handler an event handler instance * @return {@link HandlerRegistration} used to remove this handler */ public HandlerRegistration addStorageChangeHandler(final StorageChangeEvent.Handler handler) { if (handler == null) throw new IllegalArgumentException("Handler can not be null"); ensureHandlerSet().add(handler); return new HandlerRegistration() { @Override public void removeHandler() { if (handlers != null) { handlers.remove(handler); if (handlers.isEmpty()) { handlers = null; } } } }; } /** * Removes all items in the Storage, and its cache if activated * * @see <a href="http://www.w3.org/TR/webstorage/#dom-storage-clear">W3C Web * Storage - Storage.clear()</a> */ public void clear() { storage.clear(); cache.clear(); fireEvent(StorageChangeEvent.ChangeType.CLEAR, null, null, null, null, null); } /** * Clear all cached serialized object from configured {@link StorageCache} */ public void clearCache() { cache.clear(); } /** * Test if this storage contains a value for the specified key. * * <p> * {@link StorageKeyFactory} is preferred to get a {@link StorageKey} instance for primitive types. * </p> * * @param key the key whose presence in this storage is to be tested * @param <T> the type of stored value * @return <tt>true</tt> if this storage contains a value for the specified key. */ public <T extends Serializable> boolean containsKey(StorageKey<T> key) { return storage.getItem(key.name()) != null; } /** * Returns the value in the Storage associated with the specified key, * or {@code null} if this Storage contains no mapping for the key. * <p> * Note : Deserialization will be performed to return a correct value type. <br/> * {@link StorageKeyFactory} is preferred to get a {@link StorageKey} instance for primitive types. * </p> * * @param key the key to a value in the Storage * @param <T> the type of stored value * @return the value associated with the given key * @throws SerializationException * @see <a href="http://www.w3.org/TR/webstorage/#dom-storage-getitem">W3C Web * Storage - Storage.getItem(k)</a> */ public <T> T get(StorageKey<T> key) throws SerializationException { T item = cache.get(key); if (item == null) { item = TYPE_SERIALIZER.deserialize(key.getClazz(), storage.getItem(key.name())); cache.put(key, item); } return item; } /** * Get directly the stored string value associated with the specified key. * * <p> * Note : No deserialization will be performed * </p> * * @param key the key to a value in the Storage * @return the stored string value associated with the given key * @see <a href="http://www.w3.org/TR/webstorage/#dom-storage-getitem">W3C Web * Storage - Storage.getItem(k)</a> */ public String getString(String key) { return storage.getItem(key); } /** * Returns the key at the specified index. * * @param index the index of the key * @return the key at the specified index in this Storage * @see <a href="http://www.w3.org/TR/webstorage/#dom-storage-key">W3C Web * Storage - Storage.key(n)</a> */ public String key(int index) { return storage.key(index); } /** * Store the specified value with the specified key in this storage. * * <p> * Note: <code>null</code> value is not allowed. <br/> * If the storage previously contained a mapping for the key, the old * value is replaced.<br/> * {@link StorageKeyFactory} is preferred to get a {@link StorageKey} instance for primitive types. * </p> * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @throws SerializationException * @throws StorageQuotaExceededException */ public <T> void put(StorageKey<T> key, T value) throws SerializationException, StorageQuotaExceededException { if (value == null) { throw new NullPointerException(); } try { String data = TYPE_SERIALIZER.serialize(key.getClazz(), value); // Update store and cache String oldData = storage.getItem(key.name()); storage.setItem(key.name(), data); T oldValue = cache.put(key, value); fireEvent(StorageChangeEvent.ChangeType.PUT, key, value, oldValue, data, oldData); } catch (JavaScriptException e) { String msg = e.getMessage(); if (msg != null && msg.contains("QUOTA") && msg.contains("DOM")) { throw new StorageQuotaExceededException(key, e); } throw e; } } /** * Removes the record for the specified key from this storage if present. * * <p> * {@link StorageKeyFactory} is preferred to get a {@link StorageKey} instance for primitive types * </p> * * @param key key whose mapping is to be removed from the map * @param <T> the type of stored value */ public <T extends Serializable> void remove(StorageKey<T> key) { String data = storage.getItem(key.name()); storage.removeItem(key.name()); T value = cache.remove(key); fireEvent(StorageChangeEvent.ChangeType.REMOVE, key, null, value, null, data); } /** * Set Event Level. * * <p> * Note : set to {@link StorageChangeEvent.Level#STRING}, * will prevent the deserialization process in event creation, <br/> * This matters only if the old value (object value) is wanted in every storage change event. * </p> * * @param eventLevel the event detail level * @see StorageChangeEvent */ public void setEventLevel(StorageChangeEvent.Level eventLevel) { this.eventLevel = eventLevel; } /** * Returns the number of items in this Storage. * * @return number of items in this Storage * @see <a href="http://www.w3.org/TR/webstorage/#dom-storage-l">W3C Web * Storage - Storage.length()</a> */ public int size() { return storage.getLength(); } /** * Ensure {@link StorageChangeEvent.Handler} registration set instance */ private Set<StorageChangeEvent.Handler> ensureHandlerSet() { if (handlers == null) { handlers = new HashSet<>(); } return handlers; } /** * Fire {@link StorageChangeEvent} */ private <T> void fireEvent(StorageChangeEvent.ChangeType changeType, StorageKey<T> key, T value, T oldVal, String data, String oldData) { UncaughtExceptionHandler ueh = GWT.getUncaughtExceptionHandler(); if (handlers != null && !handlers.isEmpty()) { T oldValue = oldVal; if (oldValue == null && oldData != null && StorageChangeEvent.Level.OBJECT.equals(eventLevel)) { try { oldValue = TYPE_SERIALIZER.deserialize(key.getClazz(), data); } catch (SerializationException e) { if (ueh != null) ueh.onUncaughtException(e); oldValue = null; } } final StorageChangeEvent event = new StorageChangeEvent(changeType, key, value, oldValue, data, oldData); for (StorageChangeEvent.Handler handler : handlers) { try { handler.onStorageChange(event); } catch (Exception e) { if (ueh != null) { ueh.onUncaughtException(e); } } } } } }