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