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.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 */ 034public final class ChangesetCache implements PreferenceChangedListener{ 035 /** the unique instance */ 036 private static 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<>(); 049 050 private final CopyOnWriteArrayList<ChangesetCacheListener> listeners = 051 new CopyOnWriteArrayList<>(); 052 053 private ChangesetCache() { 054 Main.pref.addPreferenceChangeListener(this); 055 } 056 057 public void addChangesetCacheListener(ChangesetCacheListener listener) { 058 if (listener != null) { 059 listeners.addIfAbsent(listener); 060 } 061 } 062 063 public void removeChangesetCacheListener(ChangesetCacheListener listener) { 064 if (listener != null) { 065 listeners.remove(listener); 066 } 067 } 068 069 protected void fireChangesetCacheEvent(final ChangesetCacheEvent e) { 070 GuiHelper.runInEDT(new Runnable() { 071 @Override public void run() { 072 for(ChangesetCacheListener l: listeners) { 073 l.changesetCacheUpdated(e); 074 } 075 } 076 }); 077 } 078 079 protected 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 protected 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 List<Changeset> ret = new ArrayList<>(); 187 for (Changeset cs: cache.values()) { 188 if (cs.isOpen()) { 189 ret.add(cs); 190 } 191 } 192 return ret; 193 } 194 195 /* ------------------------------------------------------------------------- */ 196 /* interface PreferenceChangedListener */ 197 /* ------------------------------------------------------------------------- */ 198 @Override 199 public void preferenceChanged(PreferenceChangeEvent e) { 200 if (e.getKey() == null || !"osm-server.url".equals(e.getKey())) 201 return; 202 203 // clear the cache when the API url changes 204 if (e.getOldValue() == null || e.getNewValue() == null || !e.getOldValue().equals(e.getNewValue())) { 205 clear(); 206 } 207 } 208}