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    private static 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<>();
049
050    private final CopyOnWriteArrayList<ChangesetCacheListener> listeners = new CopyOnWriteArrayList<>();
051
052    private ChangesetCache() {
053        Main.pref.addPreferenceChangeListener(this);
054    }
055
056    public void addChangesetCacheListener(ChangesetCacheListener listener) {
057        if (listener != null) {
058            listeners.addIfAbsent(listener);
059        }
060    }
061
062    public void removeChangesetCacheListener(ChangesetCacheListener listener) {
063        if (listener != null) {
064            listeners.remove(listener);
065        }
066    }
067
068    protected void fireChangesetCacheEvent(final ChangesetCacheEvent e) {
069        GuiHelper.runInEDT(new Runnable() {
070            @Override public void run() {
071                for (ChangesetCacheListener l: listeners) {
072                    l.changesetCacheUpdated(e);
073                }
074            }
075        });
076    }
077
078    protected void update(Changeset cs, DefaultChangesetCacheEvent e) {
079        if (cs == null) return;
080        if (cs.isNew()) return;
081        Changeset inCache = cache.get(cs.getId());
082        if (inCache != null) {
083            inCache.mergeFrom(cs);
084            e.rememberUpdatedChangeset(inCache);
085        } else {
086            e.rememberAddedChangeset(cs);
087            cache.put(cs.getId(), cs);
088        }
089    }
090
091    public void update(Changeset cs) {
092        DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this);
093        update(cs, e);
094        fireChangesetCacheEvent(e);
095    }
096
097    public void update(Collection<Changeset> changesets) {
098        if (changesets == null || changesets.isEmpty()) return;
099        DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this);
100        for (Changeset cs: changesets) {
101            update(cs, e);
102        }
103        fireChangesetCacheEvent(e);
104    }
105
106    public boolean contains(int id) {
107        if (id <= 0) return false;
108        return cache.get(id) != null;
109    }
110
111    public boolean contains(Changeset cs) {
112        if (cs == null) return false;
113        if (cs.isNew()) return false;
114        return contains(cs.getId());
115    }
116
117    public Changeset get(int id) {
118        return cache.get(id);
119    }
120
121    public Set<Changeset> getChangesets() {
122        return new HashSet<>(cache.values());
123    }
124
125    protected void remove(int id, DefaultChangesetCacheEvent e) {
126        if (id <= 0) return;
127        Changeset cs = cache.get(id);
128        if (cs == null) return;
129        cache.remove(id);
130        e.rememberRemovedChangeset(cs);
131    }
132
133    public void remove(int id) {
134        DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this);
135        remove(id, e);
136        if (!e.isEmpty()) {
137            fireChangesetCacheEvent(e);
138        }
139    }
140
141    public void remove(Changeset cs) {
142        if (cs == null) return;
143        if (cs.isNew()) return;
144        remove(cs.getId());
145    }
146
147    /**
148     * Removes the changesets in <code>changesets</code> from the cache. A
149     * {@link ChangesetCacheEvent} is fired.
150     *
151     * @param changesets the changesets to remove. Ignored if null.
152     */
153    public void remove(Collection<Changeset> changesets) {
154        if (changesets == null) return;
155        DefaultChangesetCacheEvent evt = new DefaultChangesetCacheEvent(this);
156        for (Changeset cs : changesets) {
157            if (cs == null || cs.isNew()) {
158                continue;
159            }
160            remove(cs.getId(), evt);
161        }
162        if (!evt.isEmpty()) {
163            fireChangesetCacheEvent(evt);
164        }
165    }
166
167    public int size() {
168        return cache.size();
169    }
170
171    public void clear() {
172        DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this);
173        for (Changeset cs: cache.values()) {
174            e.rememberRemovedChangeset(cs);
175        }
176        cache.clear();
177        fireChangesetCacheEvent(e);
178    }
179
180    /**
181     * Replies the list of open changesets.
182     * @return The list of open changesets
183     */
184    public List<Changeset> getOpenChangesets() {
185        List<Changeset> ret = new ArrayList<>();
186        for (Changeset cs: cache.values()) {
187            if (cs.isOpen()) {
188                ret.add(cs);
189            }
190        }
191        return ret;
192    }
193
194    /* ------------------------------------------------------------------------- */
195    /* interface PreferenceChangedListener                                       */
196    /* ------------------------------------------------------------------------- */
197    @Override
198    public void preferenceChanged(PreferenceChangeEvent e) {
199        if (e.getKey() == null || !"osm-server.url".equals(e.getKey()))
200            return;
201
202        // clear the cache when the API url changes
203        if (e.getOldValue() == null || e.getNewValue() == null || !e.getOldValue().equals(e.getNewValue())) {
204            clear();
205        }
206    }
207}