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; 012import java.util.stream.Collectors; 013 014import org.openstreetmap.josm.Main; 015import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; 016import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; 017import org.openstreetmap.josm.gui.JosmUserIdentityManager; 018import org.openstreetmap.josm.gui.util.GuiHelper; 019import org.openstreetmap.josm.tools.SubclassFilteredCollection; 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 private void fireChangesetCacheEvent(final ChangesetCacheEvent e) { 072 GuiHelper.runInEDT(() -> { 073 for (ChangesetCacheListener l: listeners) { 074 l.changesetCacheUpdated(e); 075 } 076 }); 077 } 078 079 private void update(Changeset cs, DefaultChangesetCacheEvent e) { 080 if (cs == null) return; 081 if (cs.isNew()) return; 082 Changeset inCache = cache.get(cs.getId()); 083 if (inCache != null) { 084 inCache.mergeFrom(cs); 085 e.rememberUpdatedChangeset(inCache); 086 } else { 087 e.rememberAddedChangeset(cs); 088 cache.put(cs.getId(), cs); 089 } 090 } 091 092 public void update(Changeset cs) { 093 DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this); 094 update(cs, e); 095 fireChangesetCacheEvent(e); 096 } 097 098 public void update(Collection<Changeset> changesets) { 099 if (changesets == null || changesets.isEmpty()) return; 100 DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this); 101 for (Changeset cs: changesets) { 102 update(cs, e); 103 } 104 fireChangesetCacheEvent(e); 105 } 106 107 public boolean contains(int id) { 108 if (id <= 0) return false; 109 return cache.get(id) != null; 110 } 111 112 public boolean contains(Changeset cs) { 113 if (cs == null) return false; 114 if (cs.isNew()) return false; 115 return contains(cs.getId()); 116 } 117 118 public Changeset get(int id) { 119 return cache.get(id); 120 } 121 122 public Set<Changeset> getChangesets() { 123 return new HashSet<>(cache.values()); 124 } 125 126 private void remove(int id, DefaultChangesetCacheEvent e) { 127 if (id <= 0) return; 128 Changeset cs = cache.get(id); 129 if (cs == null) return; 130 cache.remove(id); 131 e.rememberRemovedChangeset(cs); 132 } 133 134 public void remove(int id) { 135 DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this); 136 remove(id, e); 137 if (!e.isEmpty()) { 138 fireChangesetCacheEvent(e); 139 } 140 } 141 142 public void remove(Changeset cs) { 143 if (cs == null) return; 144 if (cs.isNew()) return; 145 remove(cs.getId()); 146 } 147 148 /** 149 * Removes the changesets in <code>changesets</code> from the cache. A 150 * {@link ChangesetCacheEvent} is fired. 151 * 152 * @param changesets the changesets to remove. Ignored if null. 153 */ 154 public void remove(Collection<Changeset> changesets) { 155 if (changesets == null) return; 156 DefaultChangesetCacheEvent evt = new DefaultChangesetCacheEvent(this); 157 for (Changeset cs : changesets) { 158 if (cs == null || cs.isNew()) { 159 continue; 160 } 161 remove(cs.getId(), evt); 162 } 163 if (!evt.isEmpty()) { 164 fireChangesetCacheEvent(evt); 165 } 166 } 167 168 public int size() { 169 return cache.size(); 170 } 171 172 public void clear() { 173 DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this); 174 for (Changeset cs: cache.values()) { 175 e.rememberRemovedChangeset(cs); 176 } 177 cache.clear(); 178 fireChangesetCacheEvent(e); 179 } 180 181 /** 182 * Replies the list of open changesets. 183 * @return The list of open changesets 184 */ 185 public List<Changeset> getOpenChangesets() { 186 return cache.values().stream() 187 .filter(Changeset::isOpen) 188 .collect(Collectors.toList()); 189 } 190 191 /** 192 * If the current user {@link JosmUserIdentityManager#isAnonymous() is known}, the {@link #getOpenChangesets() open changesets} 193 * for the {@link JosmUserIdentityManager#isCurrentUser(User) current user} are returned. Otherwise, 194 * the unfiltered {@link #getOpenChangesets() open changesets} are returned. 195 * 196 * @return a list of changesets 197 */ 198 public List<Changeset> getOpenChangesetsForCurrentUser() { 199 if (JosmUserIdentityManager.getInstance().isAnonymous()) { 200 return getOpenChangesets(); 201 } else { 202 return new ArrayList<>(SubclassFilteredCollection.filter(getOpenChangesets(), 203 object -> JosmUserIdentityManager.getInstance().isCurrentUser(object.getUser()))); 204 } 205 } 206 207 /* ------------------------------------------------------------------------- */ 208 /* interface PreferenceChangedListener */ 209 /* ------------------------------------------------------------------------- */ 210 @Override 211 public void preferenceChanged(PreferenceChangeEvent e) { 212 if (e.getKey() == null || !"osm-server.url".equals(e.getKey())) 213 return; 214 215 // clear the cache when the API url changes 216 if (e.getOldValue() == null || e.getNewValue() == null || !e.getOldValue().equals(e.getNewValue())) { 217 clear(); 218 } 219 } 220}