001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.awt.GraphicsEnvironment; 005import java.awt.Toolkit; 006import java.awt.event.KeyEvent; 007import java.io.BufferedReader; 008import java.io.File; 009import java.io.IOException; 010import java.io.InputStreamReader; 011import java.nio.charset.StandardCharsets; 012import java.security.KeyStoreException; 013import java.security.NoSuchAlgorithmException; 014import java.security.cert.CertificateException; 015import java.security.cert.X509Certificate; 016import java.text.DateFormat; 017import java.util.Collection; 018import java.util.Collections; 019import java.util.Date; 020import java.util.List; 021 022import org.openstreetmap.josm.data.projection.datum.NTV2Proj4DirGridShiftFileSource; 023import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend; 024import org.openstreetmap.josm.spi.preferences.Config; 025import org.openstreetmap.josm.tools.date.DateUtils; 026 027/** 028 * This interface allows platform (operating system) dependent code 029 * to be bundled into self-contained classes. 030 * @since 1023 031 */ 032public interface PlatformHook { 033 034 /** 035 * Visitor to construct a PlatformHook from a given {@link Platform} object. 036 */ 037 PlatformVisitor<PlatformHook> CONSTRUCT_FROM_PLATFORM = new PlatformVisitor<PlatformHook>() { 038 @Override 039 public PlatformHook visitUnixoid() { 040 return new PlatformHookUnixoid(); 041 } 042 043 @Override 044 public PlatformHook visitWindows() { 045 return new PlatformHookWindows(); 046 } 047 048 @Override 049 public PlatformHook visitOsx() { 050 return new PlatformHookOsx(); 051 } 052 }; 053 054 /** 055 * Get the platform corresponding to this platform hook. 056 * @return the platform corresponding to this platform hook 057 */ 058 Platform getPlatform(); 059 060 /** 061 * The preStartupHook will be called extremely early. It is 062 * guaranteed to be called before the GUI setup has started. 063 * 064 * Reason: On OSX we need to inform the Swing libraries 065 * that we want to be integrated with the OS before we setup our GUI. 066 */ 067 default void preStartupHook() { 068 // Do nothing 069 } 070 071 /** 072 * The afterPrefStartupHook will be called early, but after 073 * the preferences have been loaded and basic processing of 074 * command line arguments is finished. 075 * It is guaranteed to be called before the GUI setup has started. 076 */ 077 default void afterPrefStartupHook() { 078 // Do nothing 079 } 080 081 /** 082 * The startupHook will be called early, but after the GUI 083 * setup has started. 084 * 085 * Reason: On OSX we need to register some callbacks with the 086 * OS, so we'll receive events from the system menu. 087 * @param javaCallback Java expiration callback, providing GUI feedback 088 * @param webStartCallback WebStart migration callback, providing GUI feedback 089 * @since 17679 (signature) 090 */ 091 default void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) { 092 // Do nothing 093 } 094 095 /** 096 * The openURL hook will be used to open an URL in the 097 * default web browser. 098 * @param url The URL to open 099 * @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:<whatever>", 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", "https://www.java.com/download"), 304 DateUtils.getDateFormat(DateFormat.MEDIUM).format(expiration), false); 305 } 306 } 307 } 308 309 /** 310 * Checks if we run Oracle Web Start, proposes to user to migrate to OpenWebStart. 311 * @param callback WebStart migration callback 312 * @since 17679 313 */ 314 default void checkWebStartMigration(WebStartMigrationCallback callback) { 315 if (Utils.isRunningJavaWebStart()) { 316 callback.askMigrateWebStart(Config.getPref().get("openwebstart.download.url", "https://openwebstart.com/download/")); 317 } 318 } 319 320 /** 321 * Called when interfacing with native OS functions. Currently only used with macOS. 322 * The callback must perform all GUI-related tasks associated to an OS request. 323 * The non-GUI, platform-specific tasks, are usually performed by the {@code PlatformHook}. 324 * @since 12695 325 */ 326 interface NativeOsCallback { 327 /** 328 * macOS: Called when JOSM is asked to open a list of files. 329 * @param files list of files to open 330 */ 331 void openFiles(List<File> files); 332 333 /** 334 * macOS: Invoked when JOSM is asked to quit. 335 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation. 336 */ 337 boolean handleQuitRequest(); 338 339 /** 340 * macOS: Called when JOSM is asked to show it's about dialog. 341 */ 342 void handleAbout(); 343 344 /** 345 * macOS: Called when JOSM is asked to show it's preferences UI. 346 */ 347 void handlePreferences(); 348 } 349 350 /** 351 * Registers the native OS callback. Currently only needed for macOS. 352 * @param callback the native OS callback 353 * @since 12695 354 */ 355 default void setNativeOsCallback(NativeOsCallback callback) { 356 // To be implemented if needed 357 } 358 359 /** 360 * Resolves a file link to its destination file. 361 * @param file file (link or regular file) 362 * @return destination file in case of a file link, file if regular 363 * @since 13691 364 */ 365 default File resolveFileLink(File file) { 366 // Override if needed 367 return file; 368 } 369 370 /** 371 * Returns a set of possible platform specific directories where resources could be stored. 372 * @return A set of possible platform specific directories where resources could be stored. 373 * @since 14144 374 */ 375 default Collection<String> getPossiblePreferenceDirs() { 376 return Collections.emptyList(); 377 } 378}