source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/properties/HelpAction.java@ 14509

Last change on this file since 14509 was 14208, checked in by simon04, 6 years ago

fix #16702 - Speed up OSM wiki help using MediaWiki API

  • Property svn:eol-style set to native
File size: 7.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.properties;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.event.ActionEvent;
7import java.awt.event.KeyEvent;
8import java.io.IOException;
9import java.io.InputStream;
10import java.net.URL;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.List;
14import java.util.Map;
15import java.util.Objects;
16import java.util.function.IntFunction;
17import java.util.stream.Collectors;
18
19import javax.swing.AbstractAction;
20import javax.swing.JTable;
21import javax.swing.KeyStroke;
22import javax.xml.parsers.ParserConfigurationException;
23import javax.xml.xpath.XPath;
24import javax.xml.xpath.XPathConstants;
25import javax.xml.xpath.XPathExpressionException;
26import javax.xml.xpath.XPathFactory;
27
28import org.openstreetmap.josm.data.osm.IRelation;
29import org.openstreetmap.josm.gui.MainApplication;
30import org.openstreetmap.josm.spi.preferences.Config;
31import org.openstreetmap.josm.tools.HttpClient;
32import org.openstreetmap.josm.tools.ImageProvider;
33import org.openstreetmap.josm.tools.LanguageInfo;
34import org.openstreetmap.josm.tools.Logging;
35import org.openstreetmap.josm.tools.OpenBrowser;
36import org.openstreetmap.josm.tools.Utils;
37import org.openstreetmap.josm.tools.XmlUtils;
38import org.w3c.dom.Document;
39import org.w3c.dom.Node;
40import org.xml.sax.SAXException;
41
42/**
43 * Launch browser with wiki help for selected object.
44 * @since 13521
45 */
46public class HelpAction extends AbstractAction {
47 private final JTable tagTable;
48 private final IntFunction<String> tagKeySupplier;
49 private final IntFunction<Map<String, Integer>> tagValuesSupplier;
50
51 private final JTable membershipTable;
52 private final IntFunction<IRelation<?>> memberValueSupplier;
53
54 /**
55 * Constructs a new {@code HelpAction}.
56 * @param tagTable The tag table. Cannot be null
57 * @param tagKeySupplier Finds the key from given row of tag table. Cannot be null
58 * @param tagValuesSupplier Finds the values from given row of tag table (map of values and number of occurrences). Cannot be null
59 * @param membershipTable The membership table. Can be null
60 * @param memberValueSupplier Finds the parent relation from given row of membership table. Can be null
61 * @since 13959 (signature)
62 */
63 public HelpAction(JTable tagTable, IntFunction<String> tagKeySupplier, IntFunction<Map<String, Integer>> tagValuesSupplier,
64 JTable membershipTable, IntFunction<IRelation<?>> memberValueSupplier) {
65 this.tagTable = Objects.requireNonNull(tagTable);
66 this.tagKeySupplier = Objects.requireNonNull(tagKeySupplier);
67 this.tagValuesSupplier = Objects.requireNonNull(tagValuesSupplier);
68 this.membershipTable = membershipTable;
69 this.memberValueSupplier = memberValueSupplier;
70 putValue(NAME, tr("Go to OSM wiki for tag help"));
71 putValue(SHORT_DESCRIPTION, tr("Launch browser with wiki help for selected object"));
72 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true);
73 putValue(ACCELERATOR_KEY, getKeyStroke());
74 }
75
76 /**
77 * Returns the keystroke launching this action (F1).
78 * @return the keystroke launching this action
79 */
80 public KeyStroke getKeyStroke() {
81 return KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0);
82 }
83
84 @Override
85 public void actionPerformed(ActionEvent e) {
86 if (tagTable.getSelectedRowCount() == 1) {
87 int row = tagTable.getSelectedRow();
88 String key = Utils.encodeUrl(tagKeySupplier.apply(row));
89 Map<String, Integer> m = tagValuesSupplier.apply(row);
90 if (!m.isEmpty()) {
91 String val = Utils.encodeUrl(m.entrySet().iterator().next().getKey());
92 MainApplication.worker.execute(() -> displayTagHelp(key, val));
93 }
94 } else if (membershipTable != null && membershipTable.getSelectedRowCount() == 1) {
95 int row = membershipTable.getSelectedRow();
96 final IRelation<?> relation = memberValueSupplier.apply(row);
97 MainApplication.worker.execute(() -> displayRelationHelp(relation));
98 } else {
99 // give the generic help page, if more than one element is selected
100 MainApplication.worker.execute(HelpAction::displayGenericHelp);
101 }
102 }
103
104 /**
105 * Displays the most specific wiki page for the given key/value.
106 * @param key Key
107 * @param val Value
108 * @since 14208
109 */
110 public static void displayTagHelp(String key, String val) {
111 final String lang = LanguageInfo.getWikiLanguagePrefix();
112 final List<String> pages = Arrays.asList(
113 String.format("%sTag:%s=%s", lang, key, val),
114 String.format("Tag:%s=%s", key, val),
115 String.format("%sKey:%s", lang, key),
116 String.format("Key:%s", key),
117 String.format("%sMap_Features", lang),
118 "Map_Features"
119 );
120 displayHelp(pages);
121 }
122
123 /**
124 * Displays the most specific wiki page for the given relation.
125 * @param rel Relation
126 * @since 14208
127 */
128 public static void displayRelationHelp(IRelation<?> rel) {
129 final String lang = LanguageInfo.getWikiLanguagePrefix();
130 final List<String> pages = new ArrayList<>();
131 String type = rel.get("type");
132 if (type != null) {
133 type = Utils.encodeUrl(type);
134 }
135
136 if (type != null && !type.isEmpty()) {
137 pages.add(String.format("%sRelation:%s", lang, type));
138 pages.add(String.format("Relation:%s", type));
139 }
140
141 pages.add(String.format("%sRelations", lang));
142 pages.add("Relations");
143 displayHelp(pages);
144 }
145
146 /**
147 * Displays the localized Map Features.
148 * @since 14208
149 */
150 public static void displayGenericHelp() {
151 final String lang = LanguageInfo.getWikiLanguagePrefix();
152 final List<String> pages = Arrays.asList(
153 String.format("%sMap_Features", lang),
154 "Map_Features"
155 );
156 displayHelp(pages);
157 }
158
159 /**
160 * Display help by opening the first existing wiki page in the given list.
161 * @param pages list of wiki page names to test
162 * @since 14208
163 */
164 public static void displayHelp(final List<String> pages) {
165 try {
166 // find a page that actually exists in the wiki
167 // API documentation: https://wiki.openstreetmap.org/w/api.php?action=help&modules=query
168 final URL url = new URL(Config.getUrls().getOSMWiki() + "/w/api.php?action=query&format=xml&titles=" + pages.stream()
169 .map(Utils::encodeUrl)
170 .collect(Collectors.joining("|"))
171 );
172 final HttpClient.Response conn = HttpClient.create(url).connect();
173 final Document document;
174 try (InputStream content = conn.getContent()) {
175 document = XmlUtils.parseSafeDOM(content);
176 }
177 conn.disconnect();
178 final XPath xPath = XPathFactory.newInstance().newXPath();
179 for (String page : pages) {
180 String normalized = xPath.evaluate("/api/query/normalized/n[@from='" + page + "']/@to", document);
181 if (normalized == null || normalized.isEmpty()) {
182 normalized = page;
183 }
184 final Node node = (Node) xPath.evaluate("/api/query/pages/page[@title='" + normalized + "']", document, XPathConstants.NODE);
185 if (node != null
186 && node.getAttributes().getNamedItem("missing") == null
187 && node.getAttributes().getNamedItem("invalid") == null) {
188 OpenBrowser.displayUrl(Config.getUrls().getOSMWiki() + "/wiki/" + page);
189 break;
190 }
191 }
192 } catch (IOException | ParserConfigurationException | XPathExpressionException | SAXException e1) {
193 Logging.error(e1);
194 }
195 }
196}
Note: See TracBrowser for help on using the repository browser.