001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.presets;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.Map;
010import java.util.Set;
011
012import javax.swing.JMenu;
013import javax.swing.JMenuItem;
014import javax.swing.JSeparator;
015
016import org.openstreetmap.josm.data.osm.OsmPrimitive;
017import org.openstreetmap.josm.gui.MainApplication;
018import org.openstreetmap.josm.gui.MenuScroller;
019import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;
020import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
021import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
022import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
023import org.openstreetmap.josm.spi.preferences.Config;
024import org.openstreetmap.josm.tools.Logging;
025import org.openstreetmap.josm.tools.MultiMap;
026import org.openstreetmap.josm.tools.SubclassFilteredCollection;
027
028/**
029 * Class holding Tagging Presets and allowing to manage them.
030 * @since 7100
031 */
032public final class TaggingPresets {
033
034    /** The collection of tagging presets */
035    private static final Collection<TaggingPreset> taggingPresets = new ArrayList<>();
036
037    /** cache for key/value pairs found in the preset */
038    private static final MultiMap<String, String> PRESET_TAG_CACHE = new MultiMap<>();
039    /** cache for roles found in the preset */
040    private static final Set<String> PRESET_ROLE_CACHE = new HashSet<>();
041
042    /** The collection of listeners */
043    private static final Collection<TaggingPresetListener> listeners = new ArrayList<>();
044
045    private TaggingPresets() {
046        // Hide constructor for utility classes
047    }
048
049    /**
050     * Initializes tagging presets from preferences.
051     */
052    public static void readFromPreferences() {
053        taggingPresets.clear();
054        taggingPresets.addAll(TaggingPresetReader.readFromPreferences(false, false));
055        cachePresets(taggingPresets);
056    }
057
058    /**
059     * Initialize the tagging presets (load and may display error)
060     */
061    public static void initialize() {
062        readFromPreferences();
063        for (TaggingPreset tp: taggingPresets) {
064            if (!(tp instanceof TaggingPresetSeparator)) {
065                MainApplication.getToolbar().register(tp);
066            }
067        }
068        if (taggingPresets.isEmpty()) {
069            MainApplication.getMenu().presetsMenu.setVisible(false);
070        } else {
071            Map<TaggingPresetMenu, JMenu> submenus = new HashMap<>();
072            for (final TaggingPreset p : taggingPresets) {
073                JMenu m = p.group != null ? submenus.get(p.group) : MainApplication.getMenu().presetsMenu;
074                if (m == null && p.group != null) {
075                    Logging.error("No tagging preset submenu for " + p.group);
076                } else if (m == null) {
077                    Logging.error("No tagging preset menu. Tagging preset " + p + " won't be available there");
078                } else if (p instanceof TaggingPresetSeparator) {
079                    m.add(new JSeparator());
080                } else if (p instanceof TaggingPresetMenu) {
081                    JMenu submenu = new JMenu(p);
082                    submenu.setText(p.getLocaleName());
083                    ((TaggingPresetMenu) p).menu = submenu;
084                    submenus.put((TaggingPresetMenu) p, submenu);
085                    m.add(submenu);
086                } else {
087                    JMenuItem mi = new JMenuItem(p);
088                    mi.setText(p.getLocaleName());
089                    m.add(mi);
090                }
091            }
092            for (JMenu submenu : submenus.values()) {
093                if (submenu.getItemCount() >= Config.getPref().getInt("taggingpreset.min-elements-for-scroller", 15)) {
094                    MenuScroller.setScrollerFor(submenu);
095                }
096            }
097        }
098        if (Config.getPref().getBoolean("taggingpreset.sortmenu")) {
099            TaggingPresetMenu.sortMenu(MainApplication.getMenu().presetsMenu);
100        }
101    }
102
103    /**
104     * Initialize the cache for presets. This is done only once.
105     * @param presets Tagging presets to cache
106     */
107    public static void cachePresets(Collection<TaggingPreset> presets) {
108        for (final TaggingPreset p : presets) {
109            for (TaggingPresetItem item : p.data) {
110                cachePresetItem(p, item);
111            }
112        }
113    }
114
115    private static void cachePresetItem(TaggingPreset p, TaggingPresetItem item) {
116        if (item instanceof KeyedItem) {
117            KeyedItem ki = (KeyedItem) item;
118            if (ki.key != null && ki.getValues() != null) {
119                PRESET_TAG_CACHE.putAll(ki.key, ki.getValues());
120            }
121        } else if (item instanceof Roles) {
122            Roles r = (Roles) item;
123            for (Role i : r.roles) {
124                if (i.key != null) {
125                    PRESET_ROLE_CACHE.add(i.key);
126                }
127            }
128        } else if (item instanceof CheckGroup) {
129            for (KeyedItem check : ((CheckGroup) item).checks) {
130                cachePresetItem(p, check);
131            }
132        }
133    }
134
135    /**
136     * Replies a new collection containing all tagging presets.
137     * @return a new collection containing all tagging presets. Empty if presets are not initialized (never null)
138     */
139    public static Collection<TaggingPreset> getTaggingPresets() {
140        return Collections.unmodifiableCollection(taggingPresets);
141    }
142
143    /**
144     * Replies a set of all roles in the tagging presets.
145     * @return a set of all roles in the tagging presets.
146     */
147    public static Set<String> getPresetRoles() {
148        return Collections.unmodifiableSet(PRESET_ROLE_CACHE);
149    }
150
151    /**
152     * Replies a set of all keys in the tagging presets.
153     * @return a set of all keys in the tagging presets.
154     */
155    public static Set<String> getPresetKeys() {
156        return Collections.unmodifiableSet(PRESET_TAG_CACHE.keySet());
157    }
158
159    /**
160     * Return set of values for a key in the tagging presets
161     * @param key the key
162     * @return set of values for a key in the tagging presets or null if none is found
163     */
164    public static Set<String> getPresetValues(String key) {
165        Set<String> values = PRESET_TAG_CACHE.get(key);
166        if (values != null)
167            return Collections.unmodifiableSet(values);
168        return null;
169    }
170
171    /**
172     * Replies a new collection of all presets matching the parameters.
173     *
174     * @param t the preset types to include
175     * @param tags the tags to perform matching on, see {@link TaggingPresetItem#matches(Map)}
176     * @param onlyShowable whether only {@link TaggingPreset#isShowable() showable} presets should be returned
177     * @return a new collection of all presets matching the parameters.
178     * @see TaggingPreset#matches(Collection, Map, boolean)
179     * @since 9266
180     */
181    public static Collection<TaggingPreset> getMatchingPresets(final Collection<TaggingPresetType> t,
182                                                               final Map<String, String> tags, final boolean onlyShowable) {
183        return SubclassFilteredCollection.filter(getTaggingPresets(), preset -> preset.matches(t, tags, onlyShowable));
184    }
185
186    /**
187     * Replies a new collection of all presets matching the given preset.
188     *
189     * @param primitive the primitive
190     * @return a new collection of all presets matching the given preset.
191     * @see TaggingPreset#test(OsmPrimitive)
192     * @since 9265
193     */
194    public static Collection<TaggingPreset> getMatchingPresets(final OsmPrimitive primitive) {
195        return SubclassFilteredCollection.filter(getTaggingPresets(), preset -> preset.test(primitive));
196    }
197
198    /**
199     * Adds a list of tagging presets to the current list.
200     * @param presets The tagging presets to add
201     */
202    public static void addTaggingPresets(Collection<TaggingPreset> presets) {
203        if (presets != null && taggingPresets.addAll(presets)) {
204            for (TaggingPresetListener listener : listeners) {
205                listener.taggingPresetsModified();
206            }
207        }
208    }
209
210    /**
211     * Adds a tagging preset listener.
212     * @param listener The listener to add
213     */
214    public static void addListener(TaggingPresetListener listener) {
215        if (listener != null) {
216            listeners.add(listener);
217        }
218    }
219
220    /**
221     * Removes a tagging preset listener.
222     * @param listener The listener to remove
223     */
224    public static void removeListener(TaggingPresetListener listener) {
225        if (listener != null) {
226            listeners.remove(listener);
227        }
228    }
229}