001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.tagging.ac; 003 004 import java.util.ArrayList; 005 import java.util.Arrays; 006 import java.util.Collection; 007 import java.util.Collections; 008 import java.util.HashSet; 009 import java.util.List; 010 import java.util.Map; 011 import java.util.Map.Entry; 012 import java.util.Set; 013 014 import org.openstreetmap.josm.data.osm.DataSet; 015 import org.openstreetmap.josm.data.osm.OsmPrimitive; 016 import org.openstreetmap.josm.data.osm.OsmUtils; 017 import org.openstreetmap.josm.data.osm.Relation; 018 import org.openstreetmap.josm.data.osm.RelationMember; 019 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 020 import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 021 import org.openstreetmap.josm.data.osm.event.DataSetListener; 022 import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 023 import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 024 import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 025 import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 026 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 027 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 028 import org.openstreetmap.josm.gui.tagging.TaggingPreset; 029 import org.openstreetmap.josm.tools.MultiMap; 030 031 /** 032 * AutoCompletionManager holds a cache of keys with a list of 033 * possible auto completion values for each key. 034 * 035 * Each DataSet is assigned one AutoCompletionManager instance such that 036 * <ol> 037 * <li>any key used in a tag in the data set is part of the key list in the cache</li> 038 * <li>any value used in a tag for a specific key is part of the autocompletion list of 039 * this key</li> 040 * </ol> 041 * 042 * Building up auto completion lists should not 043 * slow down tabbing from input field to input field. Looping through the complete 044 * data set in order to build up the auto completion list for a specific input 045 * field is not efficient enough, hence this cache. 046 * 047 * TODO: respect the relation type for member role autocompletion 048 */ 049 public class AutoCompletionManager implements DataSetListener { 050 051 /** If the dirty flag is set true, a rebuild is necessary. */ 052 protected boolean dirty; 053 /** The data set that is managed */ 054 protected DataSet ds; 055 056 /** 057 * the cached tags given by a tag key and a list of values for this tag 058 * only accessed by getTagCache(), rebuild() and cachePrimitiveTags() 059 * use getTagCache() accessor 060 */ 061 protected MultiMap<String, String> tagCache; 062 /** 063 * the same as tagCache but for the preset keys and values 064 * can be accessed directly 065 */ 066 protected static final MultiMap<String, String> presetTagCache = new MultiMap<String, String>(); 067 /** 068 * the cached list of member roles 069 * only accessed by getRoleCache(), rebuild() and cacheRelationMemberRoles() 070 * use getRoleCache() accessor 071 */ 072 protected Set<String> roleCache; 073 /** 074 * the same as roleCache but for the preset roles 075 * can be accessed directly 076 */ 077 protected static final Set<String> presetRoleCache = new HashSet<String>(); 078 079 public AutoCompletionManager(DataSet ds) { 080 this.ds = ds; 081 dirty = true; 082 } 083 084 protected MultiMap<String, String> getTagCache() { 085 if (dirty) { 086 rebuild(); 087 dirty = false; 088 } 089 return tagCache; 090 } 091 092 protected Set<String> getRoleCache() { 093 if (dirty) { 094 rebuild(); 095 dirty = false; 096 } 097 return roleCache; 098 } 099 100 /** 101 * initializes the cache from the primitives in the dataset 102 * 103 */ 104 protected void rebuild() { 105 tagCache = new MultiMap<String, String>(); 106 roleCache = new HashSet<String>(); 107 cachePrimitives(ds.allNonDeletedCompletePrimitives()); 108 } 109 110 protected void cachePrimitives(Collection<? extends OsmPrimitive> primitives) { 111 for (OsmPrimitive primitive : primitives) { 112 cachePrimitiveTags(primitive); 113 if (primitive instanceof Relation) { 114 cacheRelationMemberRoles((Relation) primitive); 115 } 116 } 117 } 118 119 /** 120 * make sure, the keys and values of all tags held by primitive are 121 * in the auto completion cache 122 * 123 * @param primitive an OSM primitive 124 */ 125 protected void cachePrimitiveTags(OsmPrimitive primitive) { 126 for (String key: primitive.keySet()) { 127 String value = primitive.get(key); 128 tagCache.put(key, value); 129 } 130 } 131 132 /** 133 * Caches all member roles of the relation <code>relation</code> 134 * 135 * @param relation the relation 136 */ 137 protected void cacheRelationMemberRoles(Relation relation){ 138 for (RelationMember m: relation.getMembers()) { 139 if (m.hasRole()) { 140 roleCache.add(m.getRole()); 141 } 142 } 143 } 144 145 /** 146 * Initialize the cache for presets. This is done only once. 147 */ 148 public static void cachePresets(Collection<TaggingPreset> presets) { 149 for (final TaggingPreset p : presets) { 150 for (TaggingPreset.Item item : p.data) { 151 if (item instanceof TaggingPreset.KeyedItem) { 152 TaggingPreset.KeyedItem ki = (TaggingPreset.KeyedItem) item; 153 if (ki.key == null) { 154 continue; 155 } 156 presetTagCache.putAll(ki.key, ki.getValues()); 157 } else if (item instanceof TaggingPreset.Roles) { 158 TaggingPreset.Roles r = (TaggingPreset.Roles) item; 159 for (TaggingPreset.Role i : r.roles) { 160 if (i.key != null) { 161 presetRoleCache.add(i.key); 162 } 163 } 164 } 165 } 166 } 167 } 168 169 /** 170 * replies the keys held by the cache 171 * 172 * @return the list of keys held by the cache 173 */ 174 protected List<String> getDataKeys() { 175 return new ArrayList<String>(getTagCache().keySet()); 176 } 177 178 protected List<String> getPresetKeys() { 179 return new ArrayList<String>(presetTagCache.keySet()); 180 } 181 182 /** 183 * replies the auto completion values allowed for a specific key. Replies 184 * an empty list if key is null or if key is not in {@link #getKeys()}. 185 * 186 * @param key 187 * @return the list of auto completion values 188 */ 189 protected List<String> getDataValues(String key) { 190 return new ArrayList<String>(getTagCache().getValues(key)); 191 } 192 193 protected static List<String> getPresetValues(String key) { 194 return new ArrayList<String>(presetTagCache.getValues(key)); 195 } 196 197 /** 198 * Replies the list of member roles 199 * 200 * @return the list of member roles 201 */ 202 public List<String> getMemberRoles() { 203 return new ArrayList<String>(getRoleCache()); 204 } 205 206 /** 207 * Populates the an {@link AutoCompletionList} with the currently cached 208 * member roles. 209 * 210 * @param list the list to populate 211 */ 212 public void populateWithMemberRoles(AutoCompletionList list) { 213 list.add(presetRoleCache, AutoCompletionItemPritority.IS_IN_STANDARD); 214 list.add(getRoleCache(), AutoCompletionItemPritority.IS_IN_DATASET); 215 } 216 217 /** 218 * Populates the an {@link AutoCompletionList} with the currently cached 219 * tag keys 220 * 221 * @param list the list to populate 222 */ 223 public void populateWithKeys(AutoCompletionList list) { 224 list.add(getPresetKeys(), AutoCompletionItemPritority.IS_IN_STANDARD); 225 list.add(getDataKeys(), AutoCompletionItemPritority.IS_IN_DATASET); 226 } 227 228 /** 229 * Populates the an {@link AutoCompletionList} with the currently cached 230 * values for a tag 231 * 232 * @param list the list to populate 233 * @param key the tag key 234 */ 235 public void populateWithTagValues(AutoCompletionList list, String key) { 236 populateWithTagValues(list, Arrays.asList(key)); 237 } 238 239 /** 240 * Populates the an {@link AutoCompletionList} with the currently cached 241 * values for some given tags 242 * 243 * @param list the list to populate 244 * @param keys the tag keys 245 */ 246 public void populateWithTagValues(AutoCompletionList list, List<String> keys) { 247 for (String key : keys) { 248 list.add(getPresetValues(key), AutoCompletionItemPritority.IS_IN_STANDARD); 249 list.add(getDataValues(key), AutoCompletionItemPritority.IS_IN_DATASET); 250 } 251 } 252 253 /** 254 * Returns the currently cached tag keys. 255 * @return a list of tag keys 256 */ 257 public List<AutoCompletionListItem> getKeys() { 258 AutoCompletionList list = new AutoCompletionList(); 259 populateWithKeys(list); 260 return list.getList(); 261 } 262 263 /** 264 * Returns the currently cached tag values for a given tag key. 265 * @param key the tag key 266 * @return a list of tag values 267 */ 268 public List<AutoCompletionListItem> getValues(String key) { 269 return getValues(Arrays.asList(key)); 270 } 271 272 /** 273 * Returns the currently cached tag values for a given list of tag keys. 274 * @param keys the tag keys 275 * @return a list of tag values 276 */ 277 public List<AutoCompletionListItem> getValues(List<String> keys) { 278 AutoCompletionList list = new AutoCompletionList(); 279 populateWithTagValues(list, keys); 280 return list.getList(); 281 } 282 283 /********************************************************* 284 * Implementation of the DataSetListener interface 285 * 286 **/ 287 288 public void primitivesAdded(PrimitivesAddedEvent event) { 289 if (dirty) 290 return; 291 cachePrimitives(event.getPrimitives()); 292 } 293 294 public void primitivesRemoved(PrimitivesRemovedEvent event) { 295 dirty = true; 296 } 297 298 public void tagsChanged(TagsChangedEvent event) { 299 if (dirty) 300 return; 301 Map<String, String> newKeys = event.getPrimitive().getKeys(); 302 Map<String, String> oldKeys = event.getOriginalKeys(); 303 304 if (!newKeys.keySet().containsAll(oldKeys.keySet())) { 305 // Some keys removed, might be the last instance of key, rebuild necessary 306 dirty = true; 307 } else { 308 for (Entry<String, String> oldEntry: oldKeys.entrySet()) { 309 if (!oldEntry.getValue().equals(newKeys.get(oldEntry.getKey()))) { 310 // Value changed, might be last instance of value, rebuild necessary 311 dirty = true; 312 return; 313 } 314 } 315 cachePrimitives(Collections.singleton(event.getPrimitive())); 316 } 317 } 318 319 public void nodeMoved(NodeMovedEvent event) {/* ignored */} 320 321 public void wayNodesChanged(WayNodesChangedEvent event) {/* ignored */} 322 323 public void relationMembersChanged(RelationMembersChangedEvent event) { 324 dirty = true; // TODO: not necessary to rebuid if a member is added 325 } 326 327 public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignored */} 328 329 public void dataChanged(DataChangedEvent event) { 330 dirty = true; 331 } 332 }