001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.properties;
003
004import java.util.ArrayList;
005import java.util.Iterator;
006import java.util.LinkedHashMap;
007import java.util.List;
008import java.util.Map;
009
010import org.openstreetmap.josm.data.osm.Tag;
011import org.openstreetmap.josm.data.osm.search.SearchCompiler;
012import org.openstreetmap.josm.data.osm.search.SearchParseError;
013import org.openstreetmap.josm.data.osm.search.SearchSetting;
014import org.openstreetmap.josm.data.preferences.ListProperty;
015import org.openstreetmap.josm.tools.Logging;
016
017/**
018 * Manages list of recently used tags that will be displayed in the {@link PropertiesDialog}.
019 */
020class RecentTagCollection {
021
022    /**
023     * LRU cache for recently added tags (http://java-planet.blogspot.com/2005/08/how-to-set-up-simple-lru-cache-using.html)
024     */
025    static final class LruCache extends LinkedHashMap<Tag, Void> {
026        private static final long serialVersionUID = 1L;
027        private final int capacity;
028
029        LruCache(int capacity) {
030            super(capacity + 1, 1.1f, true);
031            this.capacity = capacity;
032        }
033
034        @Override
035        protected boolean removeEldestEntry(Map.Entry<Tag, Void> eldest) {
036            return size() > capacity;
037        }
038    }
039
040    private final Map<Tag, Void> recentTags;
041    private SearchCompiler.Match tagsToIgnore;
042
043    RecentTagCollection(final int capacity) {
044        recentTags = new LruCache(capacity);
045        tagsToIgnore = SearchCompiler.Never.INSTANCE;
046    }
047
048    public void loadFromPreference(ListProperty property) {
049        recentTags.clear();
050        List<String> list = property.get();
051        Iterator<String> it = list.iterator();
052        while (it.hasNext()) {
053            String key = it.next();
054            if (it.hasNext()) {
055                String value = it.next();
056                add(new Tag(key, value));
057            } else {
058                Logging.error("Invalid or incomplete list property: " + list);
059                break;
060            }
061        }
062    }
063
064    public void saveToPreference(ListProperty property) {
065        List<String> c = new ArrayList<>(recentTags.size() * 2);
066        for (Tag t : recentTags.keySet()) {
067            c.add(t.getKey());
068            c.add(t.getValue());
069        }
070        property.put(c);
071    }
072
073    public void add(Tag tag) {
074        if (!tagsToIgnore.match(tag)) {
075            recentTags.put(tag, null);
076        }
077    }
078
079    public boolean isEmpty() {
080        return recentTags.isEmpty();
081    }
082
083    public List<Tag> toList() {
084        return new ArrayList<>(recentTags.keySet());
085    }
086
087    public void setTagsToIgnore(SearchCompiler.Match tagsToIgnore) {
088        this.tagsToIgnore = tagsToIgnore;
089        recentTags.keySet().removeIf(tagsToIgnore::match);
090    }
091
092    public void setTagsToIgnore(SearchSetting tagsToIgnore) throws SearchParseError {
093        setTagsToIgnore(tagsToIgnore.text.isEmpty() ? SearchCompiler.Never.INSTANCE : SearchCompiler.compile(tagsToIgnore));
094    }
095
096    public SearchSetting ignoreTag(Tag tagToIgnore, SearchSetting settingToUpdate) throws SearchParseError {
097        final String forTag = SearchCompiler.buildSearchStringForTag(tagToIgnore.getKey(), tagToIgnore.getValue());
098        settingToUpdate.text = settingToUpdate.text.isEmpty()
099                ? forTag
100                : settingToUpdate.text + " OR " + forTag;
101        setTagsToIgnore(settingToUpdate);
102        return settingToUpdate;
103    }
104}