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

Last change on this file since 6068 was 6068, checked in by akks, 11 years ago

see #8853: Massive (and dumb) refactoring of TaggingPreset class (splitting into smaller files)
Separate preset-choosing panel (TaggingPresetSelector class) for reuse not only in F3.

  • Property svn:eol-style set to native
File size: 11.3 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.Map.Entry;
12import java.util.Set;
13
14import org.openstreetmap.josm.data.osm.DataSet;
15import org.openstreetmap.josm.data.osm.OsmPrimitive;
16import org.openstreetmap.josm.data.osm.Relation;
17import org.openstreetmap.josm.data.osm.RelationMember;
18import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
19import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
20import org.openstreetmap.josm.data.osm.event.DataSetListener;
21import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
22import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
23import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
24import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
25import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
26import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
27import org.openstreetmap.josm.gui.tagging.TaggingPreset;
28import org.openstreetmap.josm.gui.tagging.TaggingPresetItem;
29import org.openstreetmap.josm.gui.tagging.TaggingPresetItems;
30import org.openstreetmap.josm.tools.MultiMap;
31
32/**
33 * AutoCompletionManager holds a cache of keys with a list of
34 * possible auto completion values for each key.
35 *
36 * Each DataSet is assigned one AutoCompletionManager instance such that
37 * <ol>
38 * <li>any key used in a tag in the data set is part of the key list in the cache</li>
39 * <li>any value used in a tag for a specific key is part of the autocompletion list of
40 * this key</li>
41 * </ol>
42 *
43 * Building up auto completion lists should not
44 * slow down tabbing from input field to input field. Looping through the complete
45 * data set in order to build up the auto completion list for a specific input
46 * field is not efficient enough, hence this cache.
47 *
48 * TODO: respect the relation type for member role autocompletion
49 */
50public class AutoCompletionManager implements DataSetListener {
51
52 /** If the dirty flag is set true, a rebuild is necessary. */
53 protected boolean dirty;
54 /** The data set that is managed */
55 protected DataSet ds;
56
57 /**
58 * the cached tags given by a tag key and a list of values for this tag
59 * only accessed by getTagCache(), rebuild() and cachePrimitiveTags()
60 * use getTagCache() accessor
61 */
62 protected MultiMap<String, String> tagCache;
63 /**
64 * the same as tagCache but for the preset keys and values
65 * can be accessed directly
66 */
67 protected static final MultiMap<String, String> presetTagCache = new MultiMap<String, String>();
68 /**
69 * the cached list of member roles
70 * only accessed by getRoleCache(), rebuild() and cacheRelationMemberRoles()
71 * use getRoleCache() accessor
72 */
73 protected Set<String> roleCache;
74 /**
75 * the same as roleCache but for the preset roles
76 * can be accessed directly
77 */
78 protected static final Set<String> presetRoleCache = new HashSet<String>();
79
80 public AutoCompletionManager(DataSet ds) {
81 this.ds = ds;
82 dirty = true;
83 }
84
85 protected MultiMap<String, String> getTagCache() {
86 if (dirty) {
87 rebuild();
88 dirty = false;
89 }
90 return tagCache;
91 }
92
93 protected Set<String> getRoleCache() {
94 if (dirty) {
95 rebuild();
96 dirty = false;
97 }
98 return roleCache;
99 }
100
101 /**
102 * initializes the cache from the primitives in the dataset
103 *
104 */
105 protected void rebuild() {
106 tagCache = new MultiMap<String, String>();
107 roleCache = new HashSet<String>();
108 cachePrimitives(ds.allNonDeletedCompletePrimitives());
109 }
110
111 protected void cachePrimitives(Collection<? extends OsmPrimitive> primitives) {
112 for (OsmPrimitive primitive : primitives) {
113 cachePrimitiveTags(primitive);
114 if (primitive instanceof Relation) {
115 cacheRelationMemberRoles((Relation) primitive);
116 }
117 }
118 }
119
120 /**
121 * make sure, the keys and values of all tags held by primitive are
122 * in the auto completion cache
123 *
124 * @param primitive an OSM primitive
125 */
126 protected void cachePrimitiveTags(OsmPrimitive primitive) {
127 for (String key: primitive.keySet()) {
128 String value = primitive.get(key);
129 tagCache.put(key, value);
130 }
131 }
132
133 /**
134 * Caches all member roles of the relation <code>relation</code>
135 *
136 * @param relation the relation
137 */
138 protected void cacheRelationMemberRoles(Relation relation){
139 for (RelationMember m: relation.getMembers()) {
140 if (m.hasRole()) {
141 roleCache.add(m.getRole());
142 }
143 }
144 }
145
146 /**
147 * Initialize the cache for presets. This is done only once.
148 */
149 public static void cachePresets(Collection<TaggingPreset> presets) {
150 for (final TaggingPreset p : presets) {
151 for (TaggingPresetItem item : p.data) {
152 if (item instanceof TaggingPresetItems.KeyedItem) {
153 TaggingPresetItems.KeyedItem ki = (TaggingPresetItems.KeyedItem) item;
154 if (ki.key != null && ki.getValues() != null) {
155 try {
156 presetTagCache.putAll(ki.key, ki.getValues());
157 } catch (NullPointerException e) {
158 System.err.println(p+": Unable to cache "+ki);
159 }
160 }
161 } else if (item instanceof TaggingPresetItems.Roles) {
162 TaggingPresetItems.Roles r = (TaggingPresetItems.Roles) item;
163 for (TaggingPresetItems.Role i : r.roles) {
164 if (i.key != null) {
165 presetRoleCache.add(i.key);
166 }
167 }
168 }
169 }
170 }
171 }
172
173 /**
174 * replies the keys held by the cache
175 *
176 * @return the list of keys held by the cache
177 */
178 protected List<String> getDataKeys() {
179 return new ArrayList<String>(getTagCache().keySet());
180 }
181
182 protected List<String> getPresetKeys() {
183 return new ArrayList<String>(presetTagCache.keySet());
184 }
185
186 /**
187 * replies the auto completion values allowed for a specific key. Replies
188 * an empty list if key is null or if key is not in {@link #getKeys()}.
189 *
190 * @param key
191 * @return the list of auto completion values
192 */
193 protected List<String> getDataValues(String key) {
194 return new ArrayList<String>(getTagCache().getValues(key));
195 }
196
197 protected static List<String> getPresetValues(String key) {
198 return new ArrayList<String>(presetTagCache.getValues(key));
199 }
200
201 /**
202 * Replies the list of member roles
203 *
204 * @return the list of member roles
205 */
206 public List<String> getMemberRoles() {
207 return new ArrayList<String>(getRoleCache());
208 }
209
210 /**
211 * Populates the an {@link AutoCompletionList} with the currently cached
212 * member roles.
213 *
214 * @param list the list to populate
215 */
216 public void populateWithMemberRoles(AutoCompletionList list) {
217 list.add(presetRoleCache, AutoCompletionItemPritority.IS_IN_STANDARD);
218 list.add(getRoleCache(), AutoCompletionItemPritority.IS_IN_DATASET);
219 }
220
221 /**
222 * Populates the an {@link AutoCompletionList} with the currently cached
223 * tag keys
224 *
225 * @param list the list to populate
226 */
227 public void populateWithKeys(AutoCompletionList list) {
228 list.add(getPresetKeys(), AutoCompletionItemPritority.IS_IN_STANDARD);
229 list.add(getDataKeys(), AutoCompletionItemPritority.IS_IN_DATASET);
230 }
231
232 /**
233 * Populates the an {@link AutoCompletionList} with the currently cached
234 * values for a tag
235 *
236 * @param list the list to populate
237 * @param key the tag key
238 */
239 public void populateWithTagValues(AutoCompletionList list, String key) {
240 populateWithTagValues(list, Arrays.asList(key));
241 }
242
243 /**
244 * Populates the an {@link AutoCompletionList} with the currently cached
245 * values for some given tags
246 *
247 * @param list the list to populate
248 * @param keys the tag keys
249 */
250 public void populateWithTagValues(AutoCompletionList list, List<String> keys) {
251 for (String key : keys) {
252 list.add(getPresetValues(key), AutoCompletionItemPritority.IS_IN_STANDARD);
253 list.add(getDataValues(key), AutoCompletionItemPritority.IS_IN_DATASET);
254 }
255 }
256
257 /**
258 * Returns the currently cached tag keys.
259 * @return a list of tag keys
260 */
261 public List<AutoCompletionListItem> getKeys() {
262 AutoCompletionList list = new AutoCompletionList();
263 populateWithKeys(list);
264 return list.getList();
265 }
266
267 /**
268 * Returns the currently cached tag values for a given tag key.
269 * @param key the tag key
270 * @return a list of tag values
271 */
272 public List<AutoCompletionListItem> getValues(String key) {
273 return getValues(Arrays.asList(key));
274 }
275
276 /**
277 * Returns the currently cached tag values for a given list of tag keys.
278 * @param keys the tag keys
279 * @return a list of tag values
280 */
281 public List<AutoCompletionListItem> getValues(List<String> keys) {
282 AutoCompletionList list = new AutoCompletionList();
283 populateWithTagValues(list, keys);
284 return list.getList();
285 }
286
287 /*********************************************************
288 * Implementation of the DataSetListener interface
289 *
290 **/
291
292 @Override
293 public void primitivesAdded(PrimitivesAddedEvent event) {
294 if (dirty)
295 return;
296 cachePrimitives(event.getPrimitives());
297 }
298
299 @Override
300 public void primitivesRemoved(PrimitivesRemovedEvent event) {
301 dirty = true;
302 }
303
304 @Override
305 public void tagsChanged(TagsChangedEvent event) {
306 if (dirty)
307 return;
308 Map<String, String> newKeys = event.getPrimitive().getKeys();
309 Map<String, String> oldKeys = event.getOriginalKeys();
310
311 if (!newKeys.keySet().containsAll(oldKeys.keySet())) {
312 // Some keys removed, might be the last instance of key, rebuild necessary
313 dirty = true;
314 } else {
315 for (Entry<String, String> oldEntry: oldKeys.entrySet()) {
316 if (!oldEntry.getValue().equals(newKeys.get(oldEntry.getKey()))) {
317 // Value changed, might be last instance of value, rebuild necessary
318 dirty = true;
319 return;
320 }
321 }
322 cachePrimitives(Collections.singleton(event.getPrimitive()));
323 }
324 }
325
326 @Override
327 public void nodeMoved(NodeMovedEvent event) {/* ignored */}
328
329 @Override
330 public void wayNodesChanged(WayNodesChangedEvent event) {/* ignored */}
331
332 @Override
333 public void relationMembersChanged(RelationMembersChangedEvent event) {
334 dirty = true; // TODO: not necessary to rebuid if a member is added
335 }
336
337 @Override
338 public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignored */}
339
340 @Override
341 public void dataChanged(DataChangedEvent event) {
342 dirty = true;
343 }
344}
Note: See TracBrowser for help on using the repository browser.