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