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

Last change on this file was 19541, checked in by stoecker, 3 months ago

remove outdated expiry data check, see #24635

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