package org.openstreetmap.josm.gui.tagging.ac;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.OsmUtils;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.gui.MapView;
import org.openstreetmap.josm.gui.layer.Layer;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.gui.tagging.TaggingPreset;
import org.openstreetmap.josm.tools.MultiMap;
/**
* AutoCompletionCache temporarily holds a cache of keys with a list of
* possible auto completion values for each key.
*
* The cache can initialize itself from the current JOSM data set such that
* <ol>
* <li>any key used in a tag in the data set is part of the key list in the cache</li>
* <li>any value used in a tag for a specific key is part of the autocompletion list of
* this key</li>
* </ol>
*
* Building up auto completion lists should not
* slow down tabbing from input field to input field. Looping through the complete
* data set in order to build up the auto completion list for a specific input
* field is not efficient enough, hence this cache.
*
*/
public class AutoCompletionCache {
@SuppressWarnings("unused")
static private final Logger logger = Logger.getLogger(AutoCompletionCache.class.getName());
private static HashMap<OsmDataLayer, AutoCompletionCache> caches;
static {
caches = new HashMap<OsmDataLayer, AutoCompletionCache>();
MapView.addLayerChangeListener(new MapView.LayerChangeListener() {
public void activeLayerChange(Layer oldLayer, Layer newLayer) {
// do nothing
}
public void layerAdded(Layer newLayer) {
// do noting
}
public void layerRemoved(Layer oldLayer) {
if (oldLayer instanceof OsmDataLayer) {
caches.remove(oldLayer);
}
}
}
);
}
static public AutoCompletionCache getCacheForLayer(OsmDataLayer layer) {
AutoCompletionCache cache = caches.get(layer);
if (cache == null) {
cache = new AutoCompletionCache(layer);
caches.put(layer, cache);
}
return cache;
}
/** the cached tags given by a tag key and a list of values for this tag */
private MultiMap<String, String> tagCache;
/** the layer this cache is built for */
private OsmDataLayer layer;
/** the same as tagCache but for the preset keys and values */
private static MultiMap<String, String> presetTagCache = new MultiMap<String, String>();
/** the cached list of member roles */
private Set<String> roleCache;
/**
* constructor
*/
public AutoCompletionCache(OsmDataLayer layer) {
tagCache = new MultiMap<String, String>();
roleCache = new HashSet<String>();
this.layer = layer;
}
public AutoCompletionCache() {
this(null);
}
/**
* make sure, the keys and values of all tags held by primitive are
* in the auto completion cache
*
* @param primitive an OSM primitive
*/
protected void cachePrimitive(OsmPrimitive primitive) {
for (String key: primitive.keySet()) {
String value = primitive.get(key);
tagCache.put(key, value);
}
}
/**
* Caches all member roles of the relation <code>relation</code>
*
* @param relation the relation
*/
protected void cacheRelationMemberRoles(Relation relation){
for (RelationMember m: relation.getMembers()) {
if (m.hasRole() && !roleCache.contains(m.getRole())) {
roleCache.add(m.getRole());
}
}
}
/**
* initializes the cache from the primitives in the dataset of
* {@see #layer}
*
*/
public void initFromDataSet() {
tagCache = new MultiMap<String, String>();
if (layer == null)
return;
Collection<OsmPrimitive> ds = layer.data.allNonDeletedPrimitives();
for (OsmPrimitive primitive : ds) {
cachePrimitive(primitive);
}
for (Relation relation : layer.data.getRelations()) {
if (relation.isIncomplete() || relation.isDeleted()) {
continue;
}
cacheRelationMemberRoles(relation);
}
}
/**
* Initialize the cache for presets. This is done only once.
*/
public static void cachePresets(Collection<TaggingPreset> presets) {
for (final TaggingPreset p : presets) {
for (TaggingPreset.Item item : p.data) {
if (item instanceof TaggingPreset.Check) {
TaggingPreset.Check ch = (TaggingPreset.Check) item;
if (ch.key == null) {
continue;
}
presetTagCache.put(ch.key, OsmUtils.falseval);
presetTagCache.put(ch.key, OsmUtils.trueval);
} else if (item instanceof TaggingPreset.Combo) {
TaggingPreset.Combo co = (TaggingPreset.Combo) item;
if (co.key == null || co.values == null) {
continue;
}
for (String value : co.values.split(",")) {
presetTagCache.put(co.key, value);
}
} else if (item instanceof TaggingPreset.Key) {
TaggingPreset.Key ky = (TaggingPreset.Key) item;
if (ky.key == null || ky.value == null) {
continue;
}
presetTagCache.put(ky.key, ky.value);
} else if (item instanceof TaggingPreset.Text) {
TaggingPreset.Text tt = (TaggingPreset.Text) item;
if (tt.key == null) {
continue;
}
presetTagCache.putVoid(tt.key);
if (tt.default_ != null && !tt.default_.equals("")) {
presetTagCache.put(tt.key, tt.default_);
}
}
}
}
}
/**
* replies the keys held by the cache
*
* @return the list of keys held by the cache
*/
protected List<String> getDataKeys() {
return new ArrayList<String>(tagCache.keySet());
}
protected List<String> getPresetKeys() {
return new ArrayList<String>(presetTagCache.keySet());
}
/**
* replies the auto completion values allowed for a specific key. Replies
* an empty list if key is null or if key is not in {@link #getKeys()}.
*
* @param key
* @return the list of auto completion values
*/
protected List<String> getDataValues(String key) {
return new ArrayList<String>(tagCache.getValues(key));
}
protected static List<String> getPresetValues(String key) {
return new ArrayList<String>(presetTagCache.getValues(key));
}
/**
* Replies the list of member roles
*
* @return the list of member roles
*/
public List<String> getMemberRoles() {
return new ArrayList<String>(roleCache);
}
/**
* Populates the an {@see AutoCompletionList} with the currently cached
* member roles.
*
* @param list the list to populate
*/
public void populateWithMemberRoles(AutoCompletionList list) {
list.clear();
list.add(roleCache, AutoCompletionItemPritority.IS_IN_DATASET);
}
/**
* Populates the an {@see AutoCompletionList} with the currently cached
* values for a tag
*
* @param list the list to populate
* @param key the tag key
* @param append true to add the values to the list; false, to replace the values
* in the list by the tag values
*/
public void populateWithTagValues(AutoCompletionList list, String key, boolean append) {
if (!append) {
list.clear();
}
list.add(getDataValues(key), AutoCompletionItemPritority.IS_IN_DATASET);
list.add(getPresetValues(key), AutoCompletionItemPritority.IS_IN_STANDARD);
}
/**
* Populates the an {@see AutoCompletionList} with the currently cached
* tag keys
*
* @param list the list to populate
* @param append true to add the keys to the list; false, to replace the keys
* in the list by the keys in the cache
*/
public void populateWithKeys(AutoCompletionList list, boolean append) {
if (!append) {
list.clear();
}
list.add(getDataKeys(), AutoCompletionItemPritority.IS_IN_DATASET);
list.add(getPresetKeys(), AutoCompletionItemPritority.IS_IN_STANDARD);
}
}