001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2012 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.client.persistence.javafx;
022    
023    import java.io.Externalizable;
024    import java.io.IOException;
025    import java.io.ObjectInput;
026    import java.io.ObjectOutput;
027    import java.util.Collection;
028    import java.util.HashMap;
029    import java.util.IdentityHashMap;
030    import java.util.Map;
031    import java.util.Set;
032    
033    import javafx.beans.InvalidationListener;
034    import javafx.collections.FXCollections;
035    import javafx.collections.MapChangeListener;
036    import javafx.collections.ObservableMap;
037    
038    import org.granite.logging.Logger;
039    import org.granite.messaging.amf.RemoteClass;
040    import org.granite.client.persistence.LazyableCollection;
041    
042    /**
043     * @author William DRAI
044     */
045    @RemoteClass("org.granite.messaging.persistence.ExternalizablePersistentMap")
046    public class PersistentMap<K, V> implements ObservableMap<K, V>, LazyableCollection, Externalizable {
047            
048            @SuppressWarnings("unused")
049            private static final Logger log = Logger.getLogger(PersistentMap.class);
050    
051        @SuppressWarnings("unused")
052            private boolean initializing = false;
053        private boolean initialized = false;
054        private String metadata = null;
055        private boolean dirty = false;
056        
057        private ObservableMap<K, V> omap;
058       
059        private MapChangeListener<K, V> listener = new MapChangeListener<K, V>() {
060            @Override
061            public void onChanged(MapChangeListener.Change<? extends K, ? extends V> change) {
062                if (!initialized)
063                    return;
064                if (change.wasAdded() || change.wasRemoved())
065                    dirty = true;
066            }
067        };
068    
069        
070        public PersistentMap() {
071            this.omap = FXCollections.observableHashMap();
072            this.initialized = true;
073            addListener(listener);
074        }
075    
076        public PersistentMap(Map<K, V> map) {
077            this.omap = FXCollections.observableMap(map);
078            this.initialized = true;
079            addListener(listener);
080        }
081        
082        public PersistentMap(boolean initialized) {
083            this.omap = FXCollections.observableHashMap();
084            this.initialized = initialized;         
085            if (initialized)
086                addListener(listener);
087        }
088    
089    
090        public final boolean isInitialized() {
091            return initialized;
092        }
093    
094        public void initializing() {
095            clear();
096            initializing = true;
097            dirty = false;
098            removeListener(listener);
099        }
100    
101        public void initialize() {
102            initializing = false;
103            initialized = true;
104            dirty = false;
105            addListener(listener);
106        }
107    
108        public void uninitialize() {
109            removeListener(listener);
110            initialized = false;
111            clear();
112            dirty = false;
113        }
114        
115        public PersistentMap<K, V> clone(boolean uninitialize) {
116            PersistentMap<K, V> map = new PersistentMap<K, V>(initialized && !uninitialize);
117            map.metadata = metadata;
118            if (initialized) {
119                for (Entry<K, V> me : omap.entrySet())
120                    map.put(me.getKey(), me.getValue());
121            }
122            map.dirty = dirty;
123            return map; 
124        }
125        
126    
127        public void addListener(InvalidationListener listener) {
128            omap.addListener(listener);
129        }
130    
131        public void removeListener(InvalidationListener listener) {
132            omap.removeListener(listener);
133        }
134        
135        private Map<MapChangeListener<? super K, ? super V>, MapChangeListener<? super K, ? super V>> listenerWrappers = 
136            new IdentityHashMap<MapChangeListener<? super K, ? super V>, MapChangeListener<? super K, ? super V>>();
137    
138        @SuppressWarnings({ "unchecked", "rawtypes" })
139            public void addListener(MapChangeListener<? super K, ? super V> listener) {
140            MapChangeListener<? super K, ? super V> listenerWrapper = new MapChangeListenerWrapper(this, listener);
141            listenerWrappers.put(listener, listenerWrapper);
142            omap.addListener(listenerWrapper);
143        }
144    
145        public void removeListener(MapChangeListener<? super K, ? super V> listener) {
146            MapChangeListener<? super K, ? super V> listenerWrapper = listenerWrappers.remove(listener);
147            if (listenerWrapper != null)
148                    omap.removeListener(listenerWrapper);
149        }
150        
151    
152        public boolean isEmpty() {
153            return omap.isEmpty();
154        }
155        
156        public Set<K> keySet() {
157            return omap.keySet();
158        }
159        
160        public Set<Entry<K, V>> entrySet() {
161            return omap.entrySet();
162        }
163        
164        public Collection<V> values() {
165            return omap.values();
166        }
167        
168        public boolean containsKey(Object o) {
169            return omap.containsKey(o);
170        }
171    
172        public boolean containsValue(Object o) {
173            return omap.containsValue(o);
174        }
175    
176        public V get(Object key) {
177            return omap.get(key);
178        }
179    
180        public V put(K key, V value) {
181            return omap.put(key, value);
182        }
183    
184        public void putAll(Map<? extends K, ? extends V> map) {
185            omap.putAll(map);
186        }
187    
188        public V remove(Object o) {
189            return omap.remove(o);
190        }
191    
192        public void clear() {
193            omap.clear();
194        }
195    
196        public int size() {
197            return omap.size();
198        }
199    
200        public boolean equals(Object o) {
201            return omap.equals(o);
202        }
203    
204        public int hashCode() {
205            return omap.hashCode();
206        }
207        
208        @Override
209        public String toString() {
210            return getClass().getSimpleName() + (initialized ? "" : " (uninitialized)") + (dirty ? " (dirty)" : "") + ":" + omap.toString();
211        }
212    
213        @SuppressWarnings("unchecked")
214        public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException {
215            initialized = ((Boolean)input.readObject()).booleanValue();
216            metadata = (String)input.readObject();
217            if (initialized) {
218                dirty = ((Boolean)input.readObject()).booleanValue();
219                Object[] array = (Object[])input.readObject();
220                omap = FXCollections.observableMap(new HashMap<K, V>(array.length));
221                for (Object element : array) {
222                    Object[] entry = (Object[])element;
223                    omap.put((K)entry[0], (V)entry[1]);
224                }
225            }
226        }
227    
228        public void writeExternal(ObjectOutput output) throws IOException {
229            output.writeObject(Boolean.valueOf(initialized));
230            output.writeObject(metadata);
231            if (initialized) {
232                output.writeObject(Boolean.valueOf(dirty));
233                Object[] array = new Object[omap.size()];
234                int idx = 0;
235                for (Entry<K, V> entry : omap.entrySet())
236                    array[idx++] = new Object[] { entry.getKey(), entry.getValue() };
237                output.writeObject(array);
238            }
239        }
240    }