source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java@ 4300

Last change on this file since 4300 was 4300, checked in by stoecker, 13 years ago

fix #6306 - patch by simon04 - enable autocompletion with name-values of highways for addr:street

  • Property svn:eol-style set to native
File size: 12.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.tagging.ac;
3
4import java.util.ArrayList;
5import java.util.Arrays;
6import java.util.Collection;
7import java.util.Collections;
8import java.util.HashSet;
9import java.util.List;
10import java.util.Map;
11import java.util.Set;
12import java.util.Map.Entry;
13
14import org.openstreetmap.josm.data.osm.DataSet;
15import org.openstreetmap.josm.data.osm.OsmPrimitive;
16import org.openstreetmap.josm.data.osm.OsmUtils;
17import org.openstreetmap.josm.data.osm.Relation;
18import org.openstreetmap.josm.data.osm.RelationMember;
19import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
20import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
21import org.openstreetmap.josm.data.osm.event.DataSetListener;
22import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
23import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
24import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
25import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
26import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
27import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
28import org.openstreetmap.josm.gui.tagging.TaggingPreset;
29import org.openstreetmap.josm.tools.MultiMap;
30
31/**
32 * AutoCompletionManager holds a cache of keys with a list of
33 * possible auto completion values for each key.
34 *
35 * Each DataSet is assigned one AutoCompletionManager instance such that
36 * <ol>
37 * <li>any key used in a tag in the data set is part of the key list in the cache</li>
38 * <li>any value used in a tag for a specific key is part of the autocompletion list of
39 * this key</li>
40 * </ol>
41 *
42 * Building up auto completion lists should not
43 * slow down tabbing from input field to input field. Looping through the complete
44 * data set in order to build up the auto completion list for a specific input
45 * field is not efficient enough, hence this cache.
46 *
47 * TODO: respect the relation type for member role autocompletion
48 */
49public class AutoCompletionManager implements DataSetListener {
50
51 /** If the dirty flag is set true, a rebuild is necessary. */
52 protected boolean dirty;
53 /** The data set that is managed */
54 protected DataSet ds;
55
56 /**
57 * the cached tags given by a tag key and a list of values for this tag
58 * only accessed by getTagCache(), rebuild() and cachePrimitiveTags()
59 * use getTagCache() accessor
60 */
61 protected MultiMap<String, String> tagCache;
62 /**
63 * the same as tagCache but for the preset keys and values
64 * can be accessed directly
65 */
66 protected static MultiMap<String, String> presetTagCache = new MultiMap<String, String>();
67 /**
68 * the cached list of member roles
69 * only accessed by getRoleCache(), rebuild() and cacheRelationMemberRoles()
70 * use getRoleCache() accessor
71 */
72 protected Set<String> roleCache;
73 /**
74 * the same as roleCache but for the preset roles
75 * can be accessed directly
76 */
77 protected static Set<String> presetRoleCache = new HashSet<String>();
78
79 public AutoCompletionManager(DataSet ds) {
80 this.ds = ds;
81 dirty = true;
82 }
83
84 protected MultiMap<String, String> getTagCache() {
85 if (dirty) {
86 rebuild();
87 dirty = false;
88 }
89 return tagCache;
90 }
91
92 protected Set<String> getRoleCache() {
93 if (dirty) {
94 rebuild();
95 dirty = false;
96 }
97 return roleCache;
98 }
99
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.Check) {
152 TaggingPreset.Check ch = (TaggingPreset.Check) item;
153 if (ch.key == null) {
154 continue;
155 }
156 presetTagCache.put(ch.key, OsmUtils.falseval);
157 presetTagCache.put(ch.key, OsmUtils.trueval);
158 } else if (item instanceof TaggingPreset.Combo) {
159 TaggingPreset.Combo co = (TaggingPreset.Combo) item;
160 if (co.key == null || co.values == null) {
161 continue;
162 }
163 for (String value : co.values.split(",")) {
164 presetTagCache.put(co.key, value);
165 }
166 } else if (item instanceof TaggingPreset.Key) {
167 TaggingPreset.Key ky = (TaggingPreset.Key) item;
168 if (ky.key == null || ky.value == null) {
169 continue;
170 }
171 presetTagCache.put(ky.key, ky.value);
172 } else if (item instanceof TaggingPreset.Text) {
173 TaggingPreset.Text tt = (TaggingPreset.Text) item;
174 if (tt.key == null) {
175 continue;
176 }
177 presetTagCache.putVoid(tt.key);
178 if (tt.default_ != null && !tt.default_.equals("")) {
179 presetTagCache.put(tt.key, tt.default_);
180 }
181 } else if (item instanceof TaggingPreset.Roles) {
182 TaggingPreset.Roles r = (TaggingPreset.Roles) item;
183 for (TaggingPreset.Role i : r.roles) {
184 if (i.key != null) {
185 presetRoleCache.add(i.key);
186 }
187 }
188 }
189 }
190 }
191 }
192
193 /**
194 * replies the keys held by the cache
195 *
196 * @return the list of keys held by the cache
197 */
198 protected List<String> getDataKeys() {
199 return new ArrayList<String>(getTagCache().keySet());
200 }
201
202 protected List<String> getPresetKeys() {
203 return new ArrayList<String>(presetTagCache.keySet());
204 }
205
206 /**
207 * replies the auto completion values allowed for a specific key. Replies
208 * an empty list if key is null or if key is not in {@link #getKeys()}.
209 *
210 * @param key
211 * @return the list of auto completion values
212 */
213 protected List<String> getDataValues(String key) {
214 return new ArrayList<String>(getTagCache().getValues(key));
215 }
216
217 protected static List<String> getPresetValues(String key) {
218 return new ArrayList<String>(presetTagCache.getValues(key));
219 }
220
221 /**
222 * Replies the list of member roles
223 *
224 * @return the list of member roles
225 */
226 public List<String> getMemberRoles() {
227 return new ArrayList<String>(getRoleCache());
228 }
229
230 /**
231 * Populates the an {@see AutoCompletionList} with the currently cached
232 * member roles.
233 *
234 * @param list the list to populate
235 */
236 public void populateWithMemberRoles(AutoCompletionList list) {
237 list.add(presetRoleCache, AutoCompletionItemPritority.IS_IN_STANDARD);
238 list.add(getRoleCache(), AutoCompletionItemPritority.IS_IN_DATASET);
239 }
240
241 /**
242 * Populates the an {@see AutoCompletionList} with the currently cached
243 * tag keys
244 *
245 * @param list the list to populate
246 */
247 public void populateWithKeys(AutoCompletionList list) {
248 list.add(getPresetKeys(), AutoCompletionItemPritority.IS_IN_STANDARD);
249 list.add(getDataKeys(), AutoCompletionItemPritority.IS_IN_DATASET);
250 }
251
252 /**
253 * Populates the an {@see AutoCompletionList} with the currently cached
254 * values for a tag
255 *
256 * @param list the list to populate
257 * @param key the tag key
258 */
259 public void populateWithTagValues(AutoCompletionList list, String key) {
260 populateWithTagValues(list, Arrays.asList(key));
261 }
262
263 /**
264 * Populates the an {@see AutoCompletionList} with the currently cached
265 * values for some given tags
266 *
267 * @param list the list to populate
268 * @param key the tag keys
269 */
270 public void populateWithTagValues(AutoCompletionList list, List<String> keys) {
271 for (String key : keys) {
272 list.add(getPresetValues(key), AutoCompletionItemPritority.IS_IN_STANDARD);
273 list.add(getDataValues(key), AutoCompletionItemPritority.IS_IN_DATASET);
274 }
275 }
276
277 /**
278 * Returns the currently cached tag keys.
279 * @return a list of tag keys
280 */
281 public List<AutoCompletionListItem> getKeys() {
282 AutoCompletionList list = new AutoCompletionList();
283 populateWithKeys(list);
284 return list.getList();
285 }
286
287 /**
288 * Returns the currently cached tag values for a given tag key.
289 * @param key the tag key
290 * @return a list of tag values
291 */
292 public List<AutoCompletionListItem> getValues(String key) {
293 return getValues(Arrays.asList(key));
294 }
295
296 /**
297 * Returns the currently cached tag values for a given list of tag keys.
298 * @param keys the tag keys
299 * @return a list of tag values
300 */
301 public List<AutoCompletionListItem> getValues(List<String> keys) {
302 AutoCompletionList list = new AutoCompletionList();
303 populateWithTagValues(list, keys);
304 return list.getList();
305 }
306
307 /*********************************************************
308 * Implementation of the DataSetListener interface
309 *
310 **/
311
312 public void primitivesAdded(PrimitivesAddedEvent event) {
313 if (dirty)
314 return;
315 cachePrimitives(event.getPrimitives());
316 }
317
318 public void primitivesRemoved(PrimitivesRemovedEvent event) {
319 dirty = true;
320 }
321
322 public void tagsChanged(TagsChangedEvent event) {
323 if (dirty)
324 return;
325 Map<String, String> newKeys = event.getPrimitive().getKeys();
326 Map<String, String> oldKeys = event.getOriginalKeys();
327
328 if (!newKeys.keySet().containsAll(oldKeys.keySet())) {
329 // Some keys removed, might be the last instance of key, rebuild necessary
330 dirty = true;
331 } else {
332 for (Entry<String, String> oldEntry: oldKeys.entrySet()) {
333 if (!oldEntry.getValue().equals(newKeys.get(oldEntry.getKey()))) {
334 // Value changed, might be last instance of value, rebuild necessary
335 dirty = true;
336 return;
337 }
338 }
339 cachePrimitives(Collections.singleton(event.getPrimitive()));
340 }
341 }
342
343 public void nodeMoved(NodeMovedEvent event) {/* ignored */}
344
345 public void wayNodesChanged(WayNodesChangedEvent event) {/* ignored */}
346
347 public void relationMembersChanged(RelationMembersChangedEvent event) {
348 dirty = true; // TODO: not necessary to rebuid if a member is added
349 }
350
351 public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignored */}
352
353 public void dataChanged(DataChangedEvent event) {
354 dirty = true;
355 }
356}
Note: See TracBrowser for help on using the repository browser.