source: josm/trunk/src/org/openstreetmap/josm/tools/PlatformHook.java@ 18790

Last change on this file since 18790 was 18790, checked in by taylor.smock, 10 months ago

Fix #23103, see #17858: Notify users that a plugin requires a newer Java version

We were previously only logging a warning if a plugin required a newer Java
version. We additionally needed to update the download link generation for Azul
and sync the next minimum Java version with that used by the JOSM wiki check.

There was also a help topic that linked to a dead page (which was also
unavailable in the internet archive).

  • Property svn:eol-style set to native
File size: 16.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import java.awt.GraphicsEnvironment;
5import java.awt.Toolkit;
6import java.awt.event.KeyEvent;
7import java.io.BufferedReader;
8import java.io.File;
9import java.io.IOException;
10import java.io.InputStreamReader;
11import java.nio.charset.StandardCharsets;
12import java.security.KeyStoreException;
13import java.security.NoSuchAlgorithmException;
14import java.security.cert.CertificateException;
15import java.security.cert.X509Certificate;
16import java.text.DateFormat;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.Date;
20import java.util.List;
21
22import org.openstreetmap.josm.data.projection.datum.NTV2Proj4DirGridShiftFileSource;
23import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend;
24import org.openstreetmap.josm.spi.preferences.Config;
25import org.openstreetmap.josm.tools.date.DateUtils;
26
27/**
28 * This interface allows platform (operating system) dependent code
29 * to be bundled into self-contained classes.
30 * @since 1023
31 */
32public interface PlatformHook {
33
34 /**
35 * Visitor to construct a PlatformHook from a given {@link Platform} object.
36 */
37 PlatformVisitor<PlatformHook> CONSTRUCT_FROM_PLATFORM = new PlatformVisitor<PlatformHook>() {
38 @Override
39 public PlatformHook visitUnixoid() {
40 return new PlatformHookUnixoid();
41 }
42
43 @Override
44 public PlatformHook visitWindows() {
45 return new PlatformHookWindows();
46 }
47
48 @Override
49 public PlatformHook visitOsx() {
50 return new PlatformHookOsx();
51 }
52 };
53
54 /**
55 * Get the platform corresponding to this platform hook.
56 * @return the platform corresponding to this platform hook
57 */
58 Platform getPlatform();
59
60 /**
61 * The preStartupHook will be called extremely early. It is
62 * guaranteed to be called before the GUI setup has started.
63 *
64 * Reason: On OSX we need to inform the Swing libraries
65 * that we want to be integrated with the OS before we setup our GUI.
66 */
67 default void preStartupHook() {
68 // Do nothing
69 }
70
71 /**
72 * The afterPrefStartupHook will be called early, but after
73 * the preferences have been loaded and basic processing of
74 * command line arguments is finished.
75 * It is guaranteed to be called before the GUI setup has started.
76 */
77 default void afterPrefStartupHook() {
78 // Do nothing
79 }
80
81 /**
82 * The startupHook will be called early, but after the GUI
83 * setup has started.
84 *
85 * Reason: On OSX we need to register some callbacks with the
86 * OS, so we'll receive events from the system menu.
87 * @param javaCallback Java expiration callback, providing GUI feedback
88 * @param webStartCallback WebStart migration callback, providing GUI feedback
89 * @since 17679 (signature)
90 */
91 default void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) {
92 // Do nothing
93 }
94
95 /**
96 * The openURL hook will be used to open an URL in the
97 * default web browser.
98 * @param url The URL to open
99 * @throws IOException if any I/O error occurs
100 */
101 void openUrl(String url) throws IOException;
102
103 /**
104 * The initSystemShortcuts hook will be called by the
105 * Shortcut class after the modifier groups have been read
106 * from the config, but before any shortcuts are read from
107 * it or registered from within the application.
108 *
109 * Please note that you are not allowed to register any
110 * shortcuts from this hook, but only "systemCuts"!
111 *
112 * BTW: SystemCuts should be named "system:&lt;whatever&gt;",
113 * and it'd be best if you'd recycle the names already used
114 * by the Windows and OSX hooks. Especially the later has
115 * really many of them.
116 *
117 * You should also register any and all shortcuts that the
118 * operation system handles itself to block JOSM from trying
119 * to use them---as that would just not work. Call setAutomatic
120 * on them to prevent the keyboard preferences from allowing the
121 * user to change them.
122 */
123 void initSystemShortcuts();
124
125 /**
126 * Returns the default LAF to be used on this platform to look almost as a native application.
127 * @return The default native LAF for this platform
128 */
129 String getDefaultStyle();
130
131 /**
132 * Determines if the platform allows full-screen.
133 * @return {@code true} if full screen is allowed, {@code false} otherwise
134 */
135 default boolean canFullscreen() {
136 return !GraphicsEnvironment.isHeadless() &&
137 GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().isFullScreenSupported();
138 }
139
140 /**
141 * Renames a file.
142 * @param from Source file
143 * @param to Target file
144 * @return {@code true} if the file has been renamed, {@code false} otherwise
145 */
146 default boolean rename(File from, File to) {
147 return from.renameTo(to);
148 }
149
150 /**
151 * Returns a detailed OS description (at least family + version).
152 * @return A detailed OS description.
153 * @since 5850
154 */
155 String getOSDescription();
156
157 /**
158 * Returns OS build number.
159 * @return OS build number.
160 * @since 12217
161 */
162 default String getOSBuildNumber() {
163 return "";
164 }
165
166 /**
167 * Returns the {@code X509Certificate} matching the given certificate amendment information.
168 * @param certAmend certificate amendment
169 * @return the {@code X509Certificate} matching the given certificate amendment information, or {@code null}
170 * @throws KeyStoreException in case of error
171 * @throws IOException in case of error
172 * @throws CertificateException in case of error
173 * @throws NoSuchAlgorithmException in case of error
174 * @since 13450
175 */
176 default X509Certificate getX509Certificate(NativeCertAmend certAmend)
177 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
178 return null;
179 }
180
181 /**
182 * Executes a native command and returns the first line of standard output.
183 * @param command array containing the command to call and its arguments.
184 * @return first stripped line of standard output
185 * @throws IOException if an I/O error occurs
186 * @since 12217
187 */
188 default String exec(String... command) throws IOException {
189 Process p = Runtime.getRuntime().exec(command);
190 try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
191 return Utils.strip(input.readLine());
192 }
193 }
194
195 /**
196 * Returns the platform-dependent default cache directory.
197 * @return the platform-dependent default cache directory
198 * @since 7829
199 */
200 File getDefaultCacheDirectory();
201
202 /**
203 * Returns the platform-dependent default preferences directory.
204 * @return the platform-dependent default preferences directory
205 * @since 7831
206 */
207 File getDefaultPrefDirectory();
208
209 /**
210 * Returns the platform-dependent default user data directory.
211 * @return the platform-dependent default user data directory
212 * @since 7834
213 */
214 File getDefaultUserDataDirectory();
215
216 /**
217 * Returns the list of platform-dependent default datum shifting directories for the PROJ.4 library.
218 * @return the list of platform-dependent default datum shifting directories for the PROJ.4 library
219 * @since 11642
220 */
221 default List<File> getDefaultProj4NadshiftDirectories() {
222 return getPlatform().accept(NTV2Proj4DirGridShiftFileSource.getInstance());
223 }
224
225 /**
226 * Determines if the JVM is OpenJDK-based.
227 * @return {@code true} if {@code java.home} contains "openjdk", {@code false} otherwise
228 * @since 12219
229 */
230 default boolean isOpenJDK() {
231 String javaHome = Utils.getSystemProperty("java.home");
232 return javaHome != null && javaHome.contains("openjdk");
233 }
234
235 /**
236 * Determines if HTML rendering is supported in menu tooltips.
237 * @return {@code true} if HTML rendering is supported in menu tooltips
238 * @since 18116
239 */
240 default boolean isHtmlSupportedInMenuTooltips() {
241 return true;
242 }
243
244 /**
245 * Returns extended modifier key used as the appropriate accelerator key for menu shortcuts.
246 * It is advised everywhere to use {@link Toolkit#getMenuShortcutKeyMask()} to get the cross-platform modifier, but:
247 * <ul>
248 * <li>it returns KeyEvent.CTRL_MASK instead of KeyEvent.CTRL_DOWN_MASK. We used the extended
249 * modifier for years, and Oracle recommends to use it instead, so it's best to keep it</li>
250 * <li>the method throws a HeadlessException ! So we would need to handle it for unit tests anyway</li>
251 * </ul>
252 * @return extended modifier key used as the appropriate accelerator key for menu shortcuts
253 * @since 12748 (as a replacement to {@code GuiHelper.getMenuShortcutKeyMaskEx()})
254 */
255 default int getMenuShortcutKeyMaskEx() {
256 // To remove when switching to Java 10+, and use Toolkit.getMenuShortcutKeyMaskEx instead
257 return KeyEvent.CTRL_DOWN_MASK;
258 }
259
260 /**
261 * Called when an outdated version of Java is detected at startup.
262 * @since 12270
263 */
264 @FunctionalInterface
265 interface JavaExpirationCallback {
266 /**
267 * Asks user to update its version of Java.
268 * @param updVersion target update version
269 * @param url download URL
270 * @param major true for a migration towards a major version of Java (8:9), false otherwise
271 * @param eolDate the EOL/expiration date
272 */
273 void askUpdateJava(String updVersion, String url, String eolDate, boolean major);
274 }
275
276 /**
277 * Called when Oracle Java WebStart is detected at startup.
278 * @since 17679
279 */
280 @FunctionalInterface
281 interface WebStartMigrationCallback {
282 /**
283 * Asks user to migrate to OpenWebStart.
284 * @param url download URL
285 */
286 void askMigrateWebStart(String url);
287 }
288
289 /**
290 * Checks if the running version of Java has expired, proposes to user to update it if needed.
291 * @param callback Java expiration callback
292 * @since 12270 (signature)
293 * @since 12219
294 */
295 default void checkExpiredJava(JavaExpirationCallback callback) {
296 Date expiration = Utils.getJavaExpirationDate();
297 if (expiration != null && expiration.before(new Date())) {
298 String latestVersion = Utils.getJavaLatestVersion();
299 String currentVersion = Utils.getSystemProperty("java.version");
300 // #17831 WebStart may be launched with an expired JRE but then launching JOSM with up-to-date JRE
301 if (latestVersion == null || !latestVersion.equalsIgnoreCase(currentVersion)) {
302 callback.askUpdateJava(latestVersion != null ? latestVersion : "latest",
303 Config.getPref().get("java.update.url", getJavaUrl()),
304 DateUtils.getDateFormat(DateFormat.MEDIUM).format(expiration), false);
305 }
306 }
307 }
308
309 /**
310 * Checks if we will soon not be supporting the running version of Java
311 * @param callback Java expiration callback
312 * @since 18580
313 */
314 default void warnSoonToBeUnsupportedJava(JavaExpirationCallback callback) {
315 // Java 17 is our next minimum version, and OpenWebStart should be replacing Oracle WebStart
316 if (Utils.getJavaVersion() < 17 && !Utils.isRunningWebStart()) {
317 String latestVersion = Utils.getJavaLatestVersion();
318 String currentVersion = Utils.getSystemProperty("java.version");
319 // #17831 WebStart may be launched with an expired JRE but then launching JOSM with up-to-date JRE
320 if (latestVersion == null || !latestVersion.equalsIgnoreCase(currentVersion)) {
321 callback.askUpdateJava(latestVersion != null ? latestVersion : "latest",
322 Config.getPref().get("java.update.url", getJavaUrl()),
323 null, Utils.getJavaVersion() < 17);
324 }
325 }
326 }
327
328 /**
329 * Get the Java download URL (really shouldn't be used outside of JOSM startup checks)
330 * @return The download URL to use.
331 * @since 18580
332 */
333 default String getJavaUrl() {
334 StringBuilder defaultDownloadUrl = new StringBuilder("https://www.azul.com/downloads/?version=java-17-lts");
335 if (PlatformManager.isPlatformWindows()) {
336 defaultDownloadUrl.append("&os=windows");
337 } else if (PlatformManager.isPlatformOsx()) {
338 defaultDownloadUrl.append("&os=macos");
339 } // else probably `linux`, but they should be using a package manager.
340 // For available architectures, see
341 // https://github.com/openjdk/jdk/blob/master/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/PlatformInfo.java#L53
342 String osArch = System.getProperty("os.arch");
343 if (osArch != null) {
344 // See https://learn.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details#environment-variables
345 // for PROCESSOR_ARCHITEW6432
346 if ("x86_64".equals(osArch) || "amd64".equals(osArch)
347 || "AMD64".equalsIgnoreCase(System.getenv("PROCESSOR_ARCHITEW6432"))) {
348 defaultDownloadUrl.append("&architecture=x86-64-bit").append("&package=jdk-fx"); // jdk-fx has an installer
349 } else if ("aarch64".equals(osArch)) {
350 defaultDownloadUrl.append("&architecture=arm-64-bit").append("&package=jdk-fx"); // jdk-fx has an installer
351 } else if ("x86".equals(osArch)) {
352 // Honestly, just about everyone should be on x86_64 at this point. But just in case someone
353 // is running JOSM on a 10-year-old computer. They'd probably be better off running a RPi.
354 defaultDownloadUrl.append("&architecture=x86-32-bit").append("&package=jdk"); // jdk has an installer
355 } // else user will have to figure it out themselves.
356 }
357 defaultDownloadUrl.append("#zulu"); // Scrolls to download section
358 return defaultDownloadUrl.toString();
359 }
360
361 /**
362 * Checks if we run Oracle Web Start, proposes to user to migrate to OpenWebStart.
363 * @param callback WebStart migration callback
364 * @since 17679
365 */
366 default void checkWebStartMigration(WebStartMigrationCallback callback) {
367 if (Utils.isRunningJavaWebStart()) {
368 callback.askMigrateWebStart(Config.getPref().get("openwebstart.download.url", "https://openwebstart.com/download/"));
369 }
370 }
371
372 /**
373 * Called when interfacing with native OS functions. Currently only used with macOS.
374 * The callback must perform all GUI-related tasks associated to an OS request.
375 * The non-GUI, platform-specific tasks, are usually performed by the {@code PlatformHook}.
376 * @since 12695
377 */
378 interface NativeOsCallback {
379 /**
380 * macOS: Called when JOSM is asked to open a list of files.
381 * @param files list of files to open
382 */
383 void openFiles(List<File> files);
384
385 /**
386 * macOS: Invoked when JOSM is asked to quit.
387 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation.
388 */
389 boolean handleQuitRequest();
390
391 /**
392 * macOS: Called when JOSM is asked to show it's about dialog.
393 */
394 void handleAbout();
395
396 /**
397 * macOS: Called when JOSM is asked to show it's preferences UI.
398 */
399 void handlePreferences();
400 }
401
402 /**
403 * Registers the native OS callback. Currently only needed for macOS.
404 * @param callback the native OS callback
405 * @since 12695
406 */
407 default void setNativeOsCallback(NativeOsCallback callback) {
408 // To be implemented if needed
409 }
410
411 /**
412 * Resolves a file link to its destination file.
413 * @param file file (link or regular file)
414 * @return destination file in case of a file link, file if regular
415 * @since 13691
416 */
417 default File resolveFileLink(File file) {
418 // Override if needed
419 return file;
420 }
421
422 /**
423 * Returns a set of possible platform specific directories where resources could be stored.
424 * @return A set of possible platform specific directories where resources could be stored.
425 * @since 14144
426 */
427 default Collection<String> getPossiblePreferenceDirs() {
428 return Collections.emptyList();
429 }
430}
Note: See TracBrowser for help on using the repository browser.