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 }