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}