001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.List;
009import java.util.Map;
010import java.util.Set;
011import java.util.concurrent.CopyOnWriteArrayList;
012
013import org.openstreetmap.josm.Main;
014import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
015import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
016import 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 */
034public final 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            @Override 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    @Override
191    public void preferenceChanged(PreferenceChangeEvent e) {
192        if (e.getKey() == null || ! e.getKey().equals("osm-server.url"))
193            return;
194
195        // clear the cache when the API url changes
196        if (e.getOldValue() == null || e.getNewValue() == null || !e.getOldValue().equals(e.getNewValue())) {
197            clear();
198        }
199    }
200}