eu.maxschuster.vaadin.localstorage.LocalStorage.java Source code

Java tutorial

Introduction

Here is the source code for eu.maxschuster.vaadin.localstorage.LocalStorage.java

Source

/*
 * eu.maxschuster.vaadin.localstorage.LocalStorage.java
 * 
 * Copyright 2013 Max Schuster
 *
 * 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 eu.maxschuster.vaadin.localstorage;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import com.google.gwt.storage.client.Storage;
import com.google.gwt.storage.client.StorageEvent;
import com.vaadin.server.AbstractClientConnector;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.Extension;
import com.vaadin.shared.communication.ServerRpc;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.Component;
import com.vaadin.ui.UI;
import com.vaadin.util.ReflectTools;

import eu.maxschuster.vaadin.localstorage.client.LocalStorageClientRpc;
import eu.maxschuster.vaadin.localstorage.shared.LocalStorageServerRpc;
import eu.maxschuster.vaadin.localstorage.shared.LocalStorageState;

/**
 * Allows limited access to the browsers localStorage.
 * 
 * You have to use {@link LocalStorage#getCurrent()} or {@link LocalStorage#getCurrent(UI)} to get an instance of {@link LocalStorage}
 * 
 * @author Max Schuster <dev@maxschutser.eu>
 * @see Storage
 */
@SuppressWarnings("serial")
public class LocalStorage extends AbstractExtension {

    /**
     * Java logger
     */
    private final static Logger LOGGER = Logger.getLogger(LocalStorage.class.getName());

    /**
     * Map of callbacks
     */
    private LocalStorageItemCallbacks itemCallbacks = new LocalStorageItemCallbacks();

    /**
     * {@link ServerRpc} that contains methods who get invoked by the client side.
     */
    private LocalStorageServerRpc serverRpc = new LocalStorageServerRpc() {

        /*
         * (non-Javadoc)
         * @see eu.maxschuster.vaadin.localstorage.shared.LocalStorageServerRpc#callLocalStorageItemCallback(eu.maxschuster.vaadin.localstorage.shared.LocalStorageItemCallback, eu.maxschuster.vaadin.localstorage.shared.LocalStorageItem)
         */
        @Override
        public void callLocalStorageItemCallback(int callback, boolean success, String key, String oldData,
                String data) {
            synchronized (itemCallbacks) {
                LocalStorageItemCallback callbackImpl = itemCallbacks.get(callback);
                if (callbackImpl != null) {
                    if (success) {
                        callbackImpl.onSuccess(new LocalStorageItem(key, oldData, data));
                    } else {
                        callbackImpl.onError(key);
                    }
                    itemCallbacks.remove(callback);
                }
            }
        }

        @Override
        public void triggerItemUpdateEvent(String key, String oldData, String data) {
            fireItemUpdateEvent(new LocalStorageItem(key, oldData, data));
        }
    };

    /**
     * Main constructor.
     * Extends the given {@link AbstractClientConnector}.
     * @param clientConnector {@link AbstractClientConnector} that should get extended.
     */
    private LocalStorage(AbstractComponent componentToExtend) {
        registerRpc(serverRpc, LocalStorageServerRpc.class);
        extend(componentToExtend);
    }

    /**
     * Gets or creates the {@link LocalStorage} instance of the currently active {@link UI}.
     * @return {@link LocalStorage} instance of the currently active {@link UI}.
     */
    public static LocalStorage getCurrent() {
        return getCurrent(UI.getCurrent());
    }

    /**
     * Gets or creates the {@link LocalStorage} instance of the given parent {@link UI}.
     * @param parent Parent {@link UI}
     * @return {@link LocalStorage} instance of the given parent {@link UI}.
     */
    public static LocalStorage getCurrent(UI parent) {
        if (parent == null) {
            throw new NullPointerException();
        }

        for (Extension extension : parent.getExtensions()) {
            if (extension instanceof LocalStorage) {
                return (LocalStorage) extension;
            }
        }

        return new LocalStorage(parent);
    }

    /*
     * (non-Javadoc)
     * @see com.vaadin.server.AbstractClientConnector#getState()
     */
    @Override
    protected LocalStorageState getState() {
        return (LocalStorageState) super.getState();
    }

    /* (non-Javadoc)
     * @see com.vaadin.server.AbstractExtension#getParent()
     */
    @Override
    public UI getParent() {
        return (UI) super.getParent();
    }

    /**
     * Gets the items data from the {@link Storage} on the client-side.
     * @param key Items key
     * @param callback A callback
     * @return void
     */
    public void getItem(String key, LocalStorageItemCallback callback) {
        if (callback == null)
            throw new NullPointerException(
                    "Get an item from LocalStorage doesn't make much sense when callback is null... ;-)");
        getRpcProxy(LocalStorageClientRpc.class).getItem(key, itemCallbacks.add(callback));
    }

    /**
     * Sets the items data in the {@link Storage} on the
     * client-side and calls the given callback
     * @param key Items key
     * @param data Items new data.
     * If null the item will get removed
     * @param callback A callback
     * @return void
     */
    public void setItem(String key, String data, LocalStorageItemCallback callback) {
        getRpcProxy(LocalStorageClientRpc.class).setItem(key, data, itemCallbacks.add(callback));
    }

    /**
     * Sets the items data in the {@link Storage} on the client-side
     * @param key Items key
     * @param data Items new data.
     * If null the item will get removed
     * @return void
     */
    public void setItem(String key, String data) {
        setItem(key, data, null);
    }

    /**
     * Removes the item from the {@link Storage} on the client-side 
     * and calls the given callback.
     * @param key Items key
     * @param callback A callback
     * @return void
     */
    public void removeItem(String key, LocalStorageItemCallback callback) {
        setItem(key, null, callback);
    }

    /**
     * Removes the item from the {@link Storage} on the client-side
     * @param key Items key
     * @return void
     */
    public void removeItem(String key) {
        removeItem(key, null);
    }

    /**
     * Clears the {@link Storage} on the client-side and
     * calls the given callback
     * @param callback A callback
     * @return void
     */
    public void clear(LocalStorageItemCallback callback) {
        getRpcProxy(LocalStorageClientRpc.class).clear(itemCallbacks.add(callback));
    }

    /**
     * Clears the {@link Storage} on the client-side
     * @return void
     */
    public void clear() {
        clear(null);
    }

    /**
     * @return SimulateNotSupported is enabled.
     * <p><u>When SimulateNotSupported is enabled the client-side acts
     * as if localStorage is not supported!</u></p>
     */
    public boolean isSimulateNotSupported() {
        return getState().simulateNotSupported;
    }

    /**
     * @param simulateNotSupported SimulateNotSupported is enabled.
     * <p><u>When SimulateNotSupported is enabled the client-side acts
     * as if localStorage is not supported!</u></p>
     */
    public void setSimulateNotSupported(boolean simulateNotSupported) {
        if (getState().simulateNotSupported != simulateNotSupported) {
            if (simulateNotSupported) {
                LOGGER.warning("SimulateNotSupported mode activated!");
            } else {
                LOGGER.info("SimulateNotSupported mode deactivated!");
            }
            getState().simulateNotSupported = simulateNotSupported;
        }
    }

    /**
     * Fires an item update event
     * @param item Updated {@link LocalStorageItem}
     */
    private void fireItemUpdateEvent(LocalStorageItem item) {
        fireEvent(new ItemUpdateEvent(getParent(), this, item));
    }

    /**
     * Adds a listener for the {@link ItemUpdateEvent}
     * @param listener Listener for the {@link ItemUpdateEvent}
     * @see ItemUpdateEvent
     */
    public void addItemUpdateListener(ItemUpdateListener listener) {
        addListener(ItemUpdateEvent.ITEM_UPDATE_EVENT_IDENTIFIER, ItemUpdateEvent.class, listener,
                ItemUpdateListener.onUpdateMethod);
    }

    /**
     * Removes a listener for the {@link ItemUpdateEvent}
     * @param listener Listener for the {@link ItemUpdateEvent}
     */
    public void removeItemUpdateListener(ItemUpdateListener listener) {
        removeListener(ItemUpdateEvent.ITEM_UPDATE_EVENT_IDENTIFIER, ItemUpdateEvent.class, listener);
    }

    /**
     * Contains all callbacks of a {@link LocalStorage} instance
     * @author Max Schuster <dev@maxschutser.eu>
     */
    private class LocalStorageItemCallbacks extends HashMap<Integer, LocalStorageItemCallback> {

        /**
         * Current key counter
         */
        private int currentKey = 0;

        /**
         * Adds a callback and returns a callback id or
         * -1 if callback is null and has not been added
         * @param callback A callback
         * @return Callback id
         */
        public int add(LocalStorageItemCallback callback) {
            if (callback == null)
                return -1;
            int key = currentKey++;
            super.put(key, callback);
            return key;
        }

        /* (non-Javadoc)
         * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
         */
        @Override
        public LocalStorageItemCallback put(Integer key, LocalStorageItemCallback value)
                throws UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }

        /* (non-Javadoc)
         * @see java.util.HashMap#putAll(java.util.Map)
         */
        @Override
        public void putAll(Map<? extends Integer, ? extends LocalStorageItemCallback> m)
                throws UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }

    }

    /**
     * Listener for Local {@link ItemUpdateEvent}
     * @author Max Schuster <dev@maxschutser.eu>
     * @see ItemUpdateEvent
     */
    public static interface ItemUpdateListener {

        public final static Method onUpdateMethod = ReflectTools.findMethod(ItemUpdateListener.class, "onUpdate",
                ItemUpdateEvent.class);

        /**
         * @param event {@link ItemUpdateEvent}
         */
        public void onUpdate(ItemUpdateEvent event);

    }

    /**
     * Basic {@link LocalStorage} event
     * @author Max Schuster <dev@maxschutser.eu>
     */
    public abstract static class LocalStorageEvent extends Component.Event {

        /**
         * The source {@link LocalStorage} instance
         */
        private final LocalStorage localStorage;

        /**
         * @param source Source {@link UI} instance.
         * @param localStorage Source {@link LocalStorage} instance.
         */
        public LocalStorageEvent(UI source, LocalStorage localStorage) {
            super(source);
            this.localStorage = localStorage;
        }

        /**
         * @return The source {@link LocalStorage} instance
         */
        public final LocalStorage getLocalStorage() {
            return localStorage;
        }

        /* (non-Javadoc)
         * @see com.vaadin.ui.Component.Event#getComponent()
         */
        @Override
        public UI getComponent() {
            return (UI) super.getComponent();
        }

    }

    /**
     * Fires when an item of the localStorage on the client-side has changed.
     * @author Max Schuster <dev@maxschutser.eu>
     * @see StorageEvent
     */
    public static class ItemUpdateEvent extends LocalStorageEvent {

        public static final String ITEM_UPDATE_EVENT_IDENTIFIER = "localStorageItemUpdate";

        /**
         * Item update type
         * @author Max Schuster <dev@maxschutser.eu>
         */
        public enum Type {
            CLEAR, REMOVE, UPDATE
        }

        /**
         * The updated item
         */
        private final LocalStorageItem item;

        /**
         * The update type
         */
        private final Type type;

        /**
         * @param source Source {@link UI} instance.
         * @param localStorage Source {@link LocalStorage} instance.
         * @param item Updated {@link LocalStorageItem}
         */
        public ItemUpdateEvent(UI source, LocalStorage localStorage, LocalStorageItem item) {
            super(source, localStorage);
            this.item = item;
            if (item != null && item.getKey() == null) {
                type = Type.CLEAR;
            } else if (item != null && item.getData() == null) {
                type = Type.REMOVE;
            } else {
                type = Type.UPDATE;
            }
        }

        /**
         * @return The updated {@link LocalStorageItem}
         */
        public LocalStorageItem getItem() {
            return item;
        }

        /**
         * @return The update type
         */
        public Type getType() {
            return type;
        }

    }

}