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

Last change on this file since 5791 was 5791, checked in by Don-vip, 11 years ago

see #8530 - workaround for NPE when loading "Steps properties" presets ("No handrail" item) + various minor stuff

  • Property svn:eol-style set to native
File size: 11.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.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.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 final 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 final 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.KeyedItem) {
152 TaggingPreset.KeyedItem ki = (TaggingPreset.KeyedItem) item;
153 if (ki.key != null && ki.getValues() != null) {
154 try {
155 presetTagCache.putAll(ki.key, ki.getValues());
156 } catch (NullPointerException e) {
157 System.err.println(p+": Unable to cache "+ki);
158 }
159 }
160 } else if (item instanceof TaggingPreset.Roles) {
161 TaggingPreset.Roles r = (TaggingPreset.Roles) item;
162 for (TaggingPreset.Role i : r.roles) {
163 if (i.key != null) {
164 presetRoleCache.add(i.key);
165 }
166 }
167 }
168 }
169 }
170 }
171
172 /**
173 * replies the keys held by the cache
174 *
175 * @return the list of keys held by the cache
176 */
177 protected List<String> getDataKeys() {
178 return new ArrayList<String>(getTagCache().keySet());
179 }
180
181 protected List<String> getPresetKeys() {
182 return new ArrayList<String>(presetTagCache.keySet());
183 }
184
185 /**
186 * replies the auto completion values allowed for a specific key. Replies
187 * an empty list if key is null or if key is not in {@link #getKeys()}.
188 *
189 * @param key
190 * @return the list of auto completion values
191 */
192 protected List<String> getDataValues(String key) {
193 return new ArrayList<String>(getTagCache().getValues(key));
194 }
195
196 protected static List<String> getPresetValues(String key) {
197 return new ArrayList<String>(presetTagCache.getValues(key));
198 }
199
200 /**
201 * Replies the list of member roles
202 *
203 * @return the list of member roles
204 */
205 public List<String> getMemberRoles() {
206 return new ArrayList<String>(getRoleCache());
207 }
208
209 /**
210 * Populates the an {@link AutoCompletionList} with the currently cached
211 * member roles.
212 *
213 * @param list the list to populate
214 */
215 public void populateWithMemberRoles(AutoCompletionList list) {
216 list.add(presetRoleCache, AutoCompletionItemPritority.IS_IN_STANDARD);
217 list.add(getRoleCache(), AutoCompletionItemPritority.IS_IN_DATASET);
218 }
219
220 /**
221 * Populates the an {@link AutoCompletionList} with the currently cached
222 * tag keys
223 *
224 * @param list the list to populate
225 */
226 public void populateWithKeys(AutoCompletionList list) {
227 list.add(getPresetKeys(), AutoCompletionItemPritority.IS_IN_STANDARD);
228 list.add(getDataKeys(), AutoCompletionItemPritority.IS_IN_DATASET);
229 }
230
231 /**
232 * Populates the an {@link AutoCompletionList} with the currently cached
233 * values for a tag
234 *
235 * @param list the list to populate
236 * @param key the tag key
237 */
238 public void populateWithTagValues(AutoCompletionList list, String key) {
239 populateWithTagValues(list, Arrays.asList(key));
240 }
241
242 /**
243 * Populates the an {@link AutoCompletionList} with the currently cached
244 * values for some given tags
245 *
246 * @param list the list to populate
247 * @param keys the tag keys
248 */
249 public void populateWithTagValues(AutoCompletionList list, List<String> keys) {
250 for (String key : keys) {
251 list.add(getPresetValues(key), AutoCompletionItemPritority.IS_IN_STANDARD);
252 list.add(getDataValues(key), AutoCompletionItemPritority.IS_IN_DATASET);
253 }
254 }
255
256 /**
257 * Returns the currently cached tag keys.
258 * @return a list of tag keys
259 */
260 public List<AutoCompletionListItem> getKeys() {
261 AutoCompletionList list = new AutoCompletionList();
262 populateWithKeys(list);
263 return list.getList();
264 }
265
266 /**
267 * Returns the currently cached tag values for a given tag key.
268 * @param key the tag key
269 * @return a list of tag values
270 */
271 public List<AutoCompletionListItem> getValues(String key) {
272 return getValues(Arrays.asList(key));
273 }
274
275 /**
276 * Returns the currently cached tag values for a given list of tag keys.
277 * @param keys the tag keys
278 * @return a list of tag values
279 */
280 public List<AutoCompletionListItem> getValues(List<String> keys) {
281 AutoCompletionList list = new AutoCompletionList();
282 populateWithTagValues(list, keys);
283 return list.getList();
284 }
285
286 /*********************************************************
287 * Implementation of the DataSetListener interface
288 *
289 **/
290
291 public void primitivesAdded(PrimitivesAddedEvent event) {
292 if (dirty)
293 return;
294 cachePrimitives(event.getPrimitives());
295 }
296
297 public void primitivesRemoved(PrimitivesRemovedEvent event) {
298 dirty = true;
299 }
300
301 public void tagsChanged(TagsChangedEvent event) {
302 if (dirty)
303 return;
304 Map<String, String> newKeys = event.getPrimitive().getKeys();
305 Map<String, String> oldKeys = event.getOriginalKeys();
306
307 if (!newKeys.keySet().containsAll(oldKeys.keySet())) {
308 // Some keys removed, might be the last instance of key, rebuild necessary
309 dirty = true;
310 } else {
311 for (Entry<String, String> oldEntry: oldKeys.entrySet()) {
312 if (!oldEntry.getValue().equals(newKeys.get(oldEntry.getKey()))) {
313 // Value changed, might be last instance of value, rebuild necessary
314 dirty = true;
315 return;
316 }
317 }
318 cachePrimitives(Collections.singleton(event.getPrimitive()));
319 }
320 }
321
322 public void nodeMoved(NodeMovedEvent event) {/* ignored */}
323
324 public void wayNodesChanged(WayNodesChangedEvent event) {/* ignored */}
325
326 public void relationMembersChanged(RelationMembersChangedEvent event) {
327 dirty = true; // TODO: not necessary to rebuid if a member is added
328 }
329
330 public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignored */}
331
332 public void dataChanged(DataChangedEvent event) {
333 dirty = true;
334 }
335}
Note: See TracBrowser for help on using the repository browser.