001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.data.osm;
003    
004    import java.util.ArrayList;
005    import java.util.Collection;
006    import java.util.HashMap;
007    import java.util.HashSet;
008    import java.util.List;
009    import java.util.Map;
010    import java.util.Set;
011    import java.util.concurrent.CopyOnWriteArrayList;
012    
013    import org.openstreetmap.josm.Main;
014    import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
015    import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
016    import org.openstreetmap.josm.gui.util.GuiHelper;
017    
018    /**
019     * ChangesetCache is global in-memory cache for changesets downloaded from
020     * an OSM API server. The unique instance is available as singleton, see
021     * {@link #getInstance()}.
022     *
023     * Clients interested in cache updates can register for {@link ChangesetCacheEvent}s
024     * using {@link #addChangesetCacheListener(ChangesetCacheListener)}. They can use
025     * {@link #removeChangesetCacheListener(ChangesetCacheListener)} to unregister as
026     * cache event listener.
027     *
028     * The cache itself listens to {@link java.util.prefs.PreferenceChangeEvent}s. It
029     * clears itself if the OSM API URL is changed in the preferences.
030     *
031     * {@link ChangesetCacheEvent}s are delivered on the EDT.
032     *
033     */
034    public class ChangesetCache implements PreferenceChangedListener{
035        /** the unique instance */
036        static private final ChangesetCache instance = new ChangesetCache();
037    
038        /**
039         * Replies the unique instance of the cache
040         *
041         * @return the unique instance of the cache
042         */
043        public static ChangesetCache getInstance() {
044            return instance;
045        }
046    
047        /** the cached changesets */
048        private final Map<Integer, Changeset> cache  = new HashMap<Integer, Changeset>();
049    
050        private final CopyOnWriteArrayList<ChangesetCacheListener> listeners =
051            new CopyOnWriteArrayList<ChangesetCacheListener>();
052    
053        private ChangesetCache() {
054            Main.pref.addPreferenceChangeListener(this);
055        }
056    
057        public void addChangesetCacheListener(ChangesetCacheListener listener) {
058            listeners.addIfAbsent(listener);
059        }
060    
061        public void removeChangesetCacheListener(ChangesetCacheListener listener) {
062            listeners.remove(listener);
063        }
064    
065        protected void fireChangesetCacheEvent(final ChangesetCacheEvent e) {
066            GuiHelper.runInEDT(new Runnable() {
067                public void run() {
068                    for(ChangesetCacheListener l: listeners) {
069                        l.changesetCacheUpdated(e);
070                    }
071                }
072            });
073        }
074    
075        protected void update(Changeset cs, DefaultChangesetCacheEvent e) {
076            if (cs == null) return;
077            if (cs.isNew()) return;
078            Changeset inCache = cache.get(cs.getId());
079            if (inCache != null) {
080                inCache.mergeFrom(cs);
081                e.rememberUpdatedChangeset(inCache);
082            } else {
083                e.rememberAddedChangeset(cs);
084                cache.put(cs.getId(), cs);
085            }
086        }
087    
088        public void update(Changeset cs) {
089            DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this);
090            update(cs, e);
091            fireChangesetCacheEvent(e);
092        }
093    
094        public void update(Collection<Changeset> changesets) {
095            if (changesets == null || changesets.isEmpty()) return;
096            DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this);
097            for (Changeset cs: changesets) {
098                update(cs, e);
099            }
100            fireChangesetCacheEvent(e);
101        }
102    
103        public boolean contains(int id) {
104            if (id <=0) return false;
105            return cache.get(id) != null;
106        }
107    
108        public boolean contains(Changeset cs) {
109            if (cs == null) return false;
110            if (cs.isNew()) return false;
111            return contains(cs.getId());
112        }
113    
114        public Changeset get(int id) {
115            return cache.get(id);
116        }
117    
118        public Set<Changeset> getChangesets() {
119            return new HashSet<Changeset>(cache.values());
120        }
121    
122        protected void remove(int id, DefaultChangesetCacheEvent e) {
123            if (id <= 0) return;
124            Changeset cs = cache.get(id);
125            if (cs == null) return;
126            cache.remove(id);
127            e.rememberRemovedChangeset(cs);
128        }
129    
130        public void remove(int id) {
131            DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this);
132            remove(id, e);
133            if (! e.isEmpty()) {
134                fireChangesetCacheEvent(e);
135            }
136        }
137    
138        public void remove(Changeset cs) {
139            if (cs == null) return;
140            if (cs.isNew()) return;
141            remove(cs.getId());
142        }
143    
144        /**
145         * Removes the changesets in <code>changesets</code> from the cache. A
146         * {@link ChangesetCacheEvent} is fired.
147         *
148         * @param changesets the changesets to remove. Ignored if null.
149         */
150        public void remove(Collection<Changeset> changesets) {
151            if (changesets == null) return;
152            DefaultChangesetCacheEvent evt = new DefaultChangesetCacheEvent(this);
153            for (Changeset cs : changesets) {
154                if (cs == null || cs.isNew()) {
155                    continue;
156                }
157                remove(cs.getId(), evt);
158            }
159            if (! evt.isEmpty()) {
160                fireChangesetCacheEvent(evt);
161            }
162        }
163    
164        public int size() {
165            return cache.size();
166        }
167    
168        public void clear() {
169            DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this);
170            for (Changeset cs: cache.values()) {
171                e.rememberRemovedChangeset(cs);
172            }
173            cache.clear();
174            fireChangesetCacheEvent(e);
175        }
176    
177        public List<Changeset> getOpenChangesets() {
178            List<Changeset> ret = new ArrayList<Changeset>();
179            for (Changeset cs: cache.values()) {
180                if (cs.isOpen()) {
181                    ret.add(cs);
182                }
183            }
184            return ret;
185        }
186    
187        /* ------------------------------------------------------------------------- */
188        /* interface PreferenceChangedListener                                       */
189        /* ------------------------------------------------------------------------- */
190        public void preferenceChanged(PreferenceChangeEvent e) {
191            if (e.getKey() == null || ! e.getKey().equals("osm-server.url"))
192                return;
193    
194            // clear the cache when the API url changes
195            if (e.getOldValue() == null || e.getNewValue() == null || !e.getOldValue().equals(e.getNewValue())) {
196                clear();
197            }
198        }
199    }