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