001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.properties;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.event.ActionEvent;
007import java.util.Map;
008import java.util.Objects;
009import java.util.function.IntFunction;
010import java.util.function.Supplier;
011
012import javax.swing.AbstractAction;
013import javax.swing.JTable;
014
015import org.openstreetmap.josm.data.osm.IRelation;
016import org.openstreetmap.josm.data.osm.Tag;
017import org.openstreetmap.josm.data.preferences.StringProperty;
018import org.openstreetmap.josm.tools.ImageProvider;
019import org.openstreetmap.josm.tools.OpenBrowser;
020import org.openstreetmap.josm.tools.Utils;
021
022/**
023 * Launch browser with Taginfo statistics for selected object.
024 * @since 13521
025 */
026public class TaginfoAction extends AbstractAction {
027
028    private static final StringProperty TAGINFO_URL_PROP = new StringProperty("taginfo.url", "https://taginfo.openstreetmap.org/");
029    private static final StringProperty TAG_HISTORY_URL_PROP = new StringProperty("taghistory.url", "https://taghistory.raifer.tech/#***");
030
031    private final Supplier<Tag> tagSupplier;
032    private final Supplier<String> relationTypeSupplier;
033    protected final String taginfoUrl;
034
035    private TaginfoAction(String name, Supplier<Tag> tagSupplier, Supplier<String> relationTypeSupplier, String taginfoUrl) {
036        super(name);
037        this.tagSupplier = Objects.requireNonNull(tagSupplier);
038        this.relationTypeSupplier = Objects.requireNonNull(relationTypeSupplier);
039        this.taginfoUrl = withoutTrailingSlash(Objects.requireNonNull(taginfoUrl));
040    }
041
042    /**
043     * Constructs a new {@code TaginfoAction}.
044     * @param tagSupplier Supplies the tag for which Taginfo should be opened
045     * @param relationTypeSupplier Supplies a relation type for which Taginfo should be opened
046     * @since 16275
047     */
048    public TaginfoAction(Supplier<Tag> tagSupplier, Supplier<String> relationTypeSupplier) {
049        this(tr("Go to Taginfo"), tagSupplier, relationTypeSupplier, TAGINFO_URL_PROP.get());
050        new ImageProvider("dialogs/taginfo").getResource().attachImageIcon(this, true);
051    }
052
053    /**
054     * Constructs a new {@code TaginfoAction} with a given URL and optional name suffix.
055     * @param tagTable The tag table. Cannot be null
056     * @param tagKeySupplier Finds the key from given row of tag table. Cannot be null
057     * @param tagValuesSupplier Finds the values from given row of tag table (map of values and number of occurrences). Cannot be null
058     * @param membershipTable The membership table. Can be null
059     * @param memberValueSupplier Finds the parent relation from given row of membership table. Can be null
060     * @since 16597
061     */
062    public TaginfoAction(JTable tagTable, IntFunction<String> tagKeySupplier, IntFunction<Map<String, Integer>> tagValuesSupplier,
063                         JTable membershipTable, IntFunction<IRelation<?>> memberValueSupplier) {
064        this(getTagSupplier(tagTable, tagKeySupplier, tagValuesSupplier),
065                getRelationTypeSupplier(membershipTable, memberValueSupplier));
066    }
067
068    private static Supplier<Tag> getTagSupplier(JTable tagTable, IntFunction<String> tagKeySupplier,
069                                                IntFunction<Map<String, Integer>> tagValuesSupplier) {
070        Objects.requireNonNull(tagTable);
071        Objects.requireNonNull(tagKeySupplier);
072        Objects.requireNonNull(tagValuesSupplier);
073        return () -> {
074            if (tagTable.getSelectedRowCount() == 1) {
075                final int row = tagTable.getSelectedRow();
076                final String key = tagKeySupplier.apply(row);
077                Map<String, Integer> values = tagValuesSupplier.apply(row);
078                String value = values.size() == 1 ? values.keySet().iterator().next() : null;
079                return new Tag(key, value);
080            }
081            return null;
082        };
083    }
084
085    private static Supplier<String> getRelationTypeSupplier(JTable membershipTable, IntFunction<IRelation<?>> memberValueSupplier) {
086        return () -> membershipTable != null && membershipTable.getSelectedRowCount() == 1
087                ? memberValueSupplier.apply(membershipTable.getSelectedRow()).get("type") : null;
088    }
089
090    @Override
091    public void actionPerformed(ActionEvent e) {
092        Tag tag = tagSupplier.get();
093        if (tag != null) {
094            OpenBrowser.displayUrl(getTaginfoUrlForTag(tag));
095            return;
096        }
097        String type = relationTypeSupplier.get();
098        if (type != null) {
099            OpenBrowser.displayUrl(getTaginfoUrlForRelationType(type));
100        }
101    }
102
103    private static String withoutTrailingSlash(String url) {
104        return Utils.strip(url, "/");
105    }
106
107    /**
108     * Returns the Taginfo URL for the given tag or key (if the tag value is null)
109     * @param tag the tag
110     * @return the Taginfo URL for the given tag or key
111     * @since 16596
112     */
113    public String getTaginfoUrlForTag(Tag tag) {
114        if (tag.getValue().isEmpty()) {
115            return taginfoUrl + "/keys/" + encodeKeyValue(tag.getKey());
116        } else {
117            return taginfoUrl + "/tags/" + encodeKeyValue(tag.getKey()) + '=' + encodeKeyValue(tag.getValue());
118        }
119    }
120
121    private static String encodeKeyValue(String string) {
122        return Utils.encodeUrl(string).replaceAll("\\+", "%20");
123    }
124
125    /**
126     * Returns the Taginfo URL for the given relation type
127     * @param type the relation type
128     * @return the Taginfo URL for the given relation type
129     * @since 16596
130     */
131    public String getTaginfoUrlForRelationType(String type) {
132        return taginfoUrl + "/relations/" + type;
133    }
134
135    /**
136     * Returns a new action which launches the Taginfo instance from the given URL
137     * @param name the action's text as displayed on the menu (if it is added to a menu)
138     * @param taginfoUrl Taginfo URL
139     * @return a new action which launches the Taginfo instance from the given URL
140     * @since 16597
141     */
142    public TaginfoAction withTaginfoUrl(String name, String taginfoUrl) {
143        TaginfoAction action = new TaginfoAction(name, tagSupplier, relationTypeSupplier, taginfoUrl);
144        new ImageProvider("dialogs/taginfo").getResource().attachImageIcon(action, true);
145        return action;
146    }
147
148    /**
149     * Returns a new action which launches https://taghistory.raifer.tech/ for the given tag
150     * @return a new action
151     * @since 16596
152     */
153    public TaginfoAction toTagHistoryAction() {
154        String url = TAG_HISTORY_URL_PROP.get();
155        TaginfoAction action = new TaginfoAction(tr("Go to OSM Tag History"), tagSupplier, relationTypeSupplier, url) {
156            @Override
157            public String getTaginfoUrlForTag(Tag tag) {
158                return String.join("/", taginfoUrl, tag.getKey(), tag.getValue());
159            }
160
161            @Override
162            public String getTaginfoUrlForRelationType(String type) {
163                return null;
164            }
165        };
166        new ImageProvider("dialogs/taghistory").getResource().attachImageIcon(action, true);
167        return action;
168    }
169}