| 1 | // License: GPL. For details, see LICENSE file. |
|---|
| 2 | package org.openstreetmap.josm.data.osm; |
|---|
| 3 | |
|---|
| 4 | import java.util.ArrayList; |
|---|
| 5 | import java.util.Collection; |
|---|
| 6 | import java.util.HashMap; |
|---|
| 7 | import java.util.HashSet; |
|---|
| 8 | import java.util.List; |
|---|
| 9 | import java.util.Map; |
|---|
| 10 | import java.util.Set; |
|---|
| 11 | import java.util.concurrent.CopyOnWriteArrayList; |
|---|
| 12 | |
|---|
| 13 | import javax.swing.SwingUtilities; |
|---|
| 14 | |
|---|
| 15 | import org.openstreetmap.josm.Main; |
|---|
| 16 | import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; |
|---|
| 17 | import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; |
|---|
| 18 | |
|---|
| 19 | /** |
|---|
| 20 | * ChangesetCache is global in-memory cache for changesets downloaded from |
|---|
| 21 | * an OSM API server. The unique instance is available as singleton, see |
|---|
| 22 | * {@see #getInstance()}. |
|---|
| 23 | * |
|---|
| 24 | * Clients interested in cache updates can register for {@see ChangesetCacheEvent}s |
|---|
| 25 | * using {@see #addChangesetCacheListener(ChangesetCacheListener)}. They can use |
|---|
| 26 | * {@see #removeChangesetCacheListener(ChangesetCacheListener)} to unregister as |
|---|
| 27 | * cache event listener. |
|---|
| 28 | * |
|---|
| 29 | * The cache itself listens to {@see java.util.prefs.PreferenceChangeEvent}s. It |
|---|
| 30 | * clears itself if the OSM API URL is changed in the preferences. |
|---|
| 31 | * |
|---|
| 32 | * {@see ChangesetCacheEvent}s are delivered on the EDT. |
|---|
| 33 | * |
|---|
| 34 | */ |
|---|
| 35 | public class ChangesetCache implements PreferenceChangedListener{ |
|---|
| 36 | /** the unique instance */ |
|---|
| 37 | static private final ChangesetCache instance = new ChangesetCache(); |
|---|
| 38 | |
|---|
| 39 | /** |
|---|
| 40 | * Replies the unique instance of the cache |
|---|
| 41 | * |
|---|
| 42 | * @return the unique instance of the cache |
|---|
| 43 | */ |
|---|
| 44 | public static ChangesetCache getInstance() { |
|---|
| 45 | return instance; |
|---|
| 46 | } |
|---|
| 47 | |
|---|
| 48 | /** the cached changesets */ |
|---|
| 49 | private final Map<Integer, Changeset> cache = new HashMap<Integer, Changeset>(); |
|---|
| 50 | |
|---|
| 51 | private final CopyOnWriteArrayList<ChangesetCacheListener> listeners = |
|---|
| 52 | new CopyOnWriteArrayList<ChangesetCacheListener>(); |
|---|
| 53 | |
|---|
| 54 | private ChangesetCache() { |
|---|
| 55 | Main.pref.addPreferenceChangeListener(this); |
|---|
| 56 | } |
|---|
| 57 | |
|---|
| 58 | public void addChangesetCacheListener(ChangesetCacheListener listener) { |
|---|
| 59 | listeners.addIfAbsent(listener); |
|---|
| 60 | } |
|---|
| 61 | |
|---|
| 62 | public void removeChangesetCacheListener(ChangesetCacheListener listener) { |
|---|
| 63 | listeners.remove(listener); |
|---|
| 64 | } |
|---|
| 65 | |
|---|
| 66 | protected void fireChangesetCacheEvent(final ChangesetCacheEvent e) { |
|---|
| 67 | Runnable r = new Runnable() { |
|---|
| 68 | public void run() { |
|---|
| 69 | for(ChangesetCacheListener l: listeners) { |
|---|
| 70 | l.changesetCacheUpdated(e); |
|---|
| 71 | } |
|---|
| 72 | } |
|---|
| 73 | }; |
|---|
| 74 | if (SwingUtilities.isEventDispatchThread()) { |
|---|
| 75 | r.run(); |
|---|
| 76 | } else { |
|---|
| 77 | SwingUtilities.invokeLater(r); |
|---|
| 78 | } |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | protected void update(Changeset cs, DefaultChangesetCacheEvent e) { |
|---|
| 82 | if (cs == null) return; |
|---|
| 83 | if (cs.isNew()) return; |
|---|
| 84 | Changeset inCache = cache.get(cs.getId()); |
|---|
| 85 | if (inCache != null) { |
|---|
| 86 | inCache.mergeFrom(cs); |
|---|
| 87 | e.rememberUpdatedChangeset(inCache); |
|---|
| 88 | } else { |
|---|
| 89 | e.rememberAddedChangeset(cs); |
|---|
| 90 | cache.put(cs.getId(), cs); |
|---|
| 91 | } |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | public void update(Changeset cs) { |
|---|
| 95 | DefaultChangesetCacheEvent e = new DefaultChangesetCacheEvent(this); |
|---|
| 96 | update(cs, e); |
|---|
| 97 | fireChangesetCacheEvent(e); |
|---|
| 98 | } |
|---|
| 99 | |
|---|
| 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<Changeset>(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 | * {@see 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 | public List<Changeset> getOpenChangesets() { |
|---|
| 184 | List<Changeset> ret = new ArrayList<Changeset>(); |
|---|
| 185 | for (Changeset cs: cache.values()) { |
|---|
| 186 | if (cs.isOpen()) { |
|---|
| 187 | ret.add(cs); |
|---|
| 188 | } |
|---|
| 189 | } |
|---|
| 190 | return ret; |
|---|
| 191 | } |
|---|
| 192 | |
|---|
| 193 | /* ------------------------------------------------------------------------- */ |
|---|
| 194 | /* interface PreferenceChangedListener */ |
|---|
| 195 | /* ------------------------------------------------------------------------- */ |
|---|
| 196 | public void preferenceChanged(PreferenceChangeEvent e) { |
|---|
| 197 | if (e.getKey() == null || ! e.getKey().equals("osm-server.url")) |
|---|
| 198 | return; |
|---|
| 199 | |
|---|
| 200 | // clear the cache when the API url changes |
|---|
| 201 | if (e.getOldValue() == null || e.getNewValue() == null || !e.getOldValue().equals(e.getNewValue())) { |
|---|
| 202 | clear(); |
|---|
| 203 | } |
|---|
| 204 | } |
|---|
| 205 | } |
|---|