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.KeyStore; 013import java.security.KeyStoreException; 014import java.security.NoSuchAlgorithmException; 015import java.security.cert.CertificateException; 016import java.security.cert.X509Certificate; 017import java.text.DateFormat; 018import java.util.Collection; 019import java.util.Collections; 020import java.util.Date; 021import java.util.List; 022 023import org.openstreetmap.josm.data.projection.datum.NTV2Proj4DirGridShiftFileSource; 024import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend; 025import org.openstreetmap.josm.spi.preferences.Config; 026import org.openstreetmap.josm.tools.date.DateUtils; 027 028/** 029 * This interface allows platform (operating system) dependent code 030 * to be bundled into self-contained classes. 031 * @since 1023 032 */ 033public interface PlatformHook { 034 035 /** 036 * Visitor to construct a PlatformHook from a given {@link Platform} object. 037 */ 038 PlatformVisitor<PlatformHook> CONSTRUCT_FROM_PLATFORM = new PlatformVisitor<PlatformHook>() { 039 @Override 040 public PlatformHook visitUnixoid() { 041 return new PlatformHookUnixoid(); 042 } 043 044 @Override 045 public PlatformHook visitWindows() { 046 return new PlatformHookWindows(); 047 } 048 049 @Override 050 public PlatformHook visitOsx() { 051 return new PlatformHookOsx(); 052 } 053 }; 054 055 /** 056 * Get the platform corresponding to this platform hook. 057 * @return the platform corresponding to this platform hook 058 */ 059 Platform getPlatform(); 060 061 /** 062 * The preStartupHook will be called extremely early. It is 063 * guaranteed to be called before the GUI setup has started. 064 * 065 * Reason: On OSX we need to inform the Swing libraries 066 * that we want to be integrated with the OS before we setup our GUI. 067 */ 068 default void preStartupHook() { 069 // Do nothing 070 } 071 072 /** 073 * The afterPrefStartupHook will be called early, but after 074 * the preferences have been loaded and basic processing of 075 * command line arguments is finished. 076 * It is guaranteed to be called before the GUI setup has started. 077 */ 078 default void afterPrefStartupHook() { 079 // Do nothing 080 } 081 082 /** 083 * The startupHook will be called early, but after the GUI 084 * setup has started. 085 * 086 * Reason: On OSX we need to register some callbacks with the 087 * OS, so we'll receive events from the system menu. 088 * @param callback Java expiration callback, providing GUI feedback 089 * @since 12270 (signature) 090 */ 091 default void startupHook(JavaExpirationCallback callback) { 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 * shortuts from this hook, but only "systemCuts"! 111 * 112 * BTW: SystemCuts should be named "system:<whatever>", 113 * and it'd be best if sou'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 * The makeTooltip hook will be called whenever a tooltip for 127 * a menu or button is created. 128 * 129 * Tooltips are usually not system dependent, unless the 130 * JVM is too dumb to provide correct names for all the keys. 131 * 132 * Some LAFs don't understand HTML, such as the OSX LAFs. 133 * 134 * @param name Tooltip text to display 135 * @param sc Shortcut associated (to display accelerator between parenthesis) 136 * @return Full tooltip text (name + accelerator) 137 * @since 1084 138 * @deprecated Use {@link Shortcut#makeTooltip} instead. 139 */ 140 @Deprecated 141 default String makeTooltip(String name, Shortcut sc) { 142 return Shortcut.makeTooltip(name, sc != null ? sc.getKeyStroke() : null); 143 } 144 145 /** 146 * Returns the default LAF to be used on this platform to look almost as a native application. 147 * @return The default native LAF for this platform 148 */ 149 String getDefaultStyle(); 150 151 /** 152 * Determines if the platform allows full-screen. 153 * @return {@code true} if full screen is allowed, {@code false} otherwise 154 */ 155 default boolean canFullscreen() { 156 return !GraphicsEnvironment.isHeadless() && 157 GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().isFullScreenSupported(); 158 } 159 160 /** 161 * Renames a file. 162 * @param from Source file 163 * @param to Target file 164 * @return {@code true} if the file has been renamed, {@code false} otherwise 165 */ 166 default boolean rename(File from, File to) { 167 return from.renameTo(to); 168 } 169 170 /** 171 * Returns a detailed OS description (at least family + version). 172 * @return A detailed OS description. 173 * @since 5850 174 */ 175 String getOSDescription(); 176 177 /** 178 * Returns OS build number. 179 * @return OS build number. 180 * @since 12217 181 */ 182 default String getOSBuildNumber() { 183 return ""; 184 } 185 186 /** 187 * Setup system keystore to add JOSM HTTPS certificate (for remote control). 188 * @param entryAlias The entry alias to use 189 * @param trustedCert the JOSM certificate for localhost 190 * @return {@code true} if something has changed as a result of the call (certificate installation, etc.) 191 * @throws KeyStoreException in case of error 192 * @throws IOException in case of error 193 * @throws CertificateException in case of error 194 * @throws NoSuchAlgorithmException in case of error 195 * @since 7343 196 */ 197 default boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert) 198 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { 199 // TODO setup HTTPS certificate on Unix and OS X systems 200 return false; 201 } 202 203 /** 204 * Returns the {@code X509Certificate} matching the given certificate amendment information. 205 * @param certAmend certificate amendment 206 * @return the {@code X509Certificate} matching the given certificate amendment information, or {@code null} 207 * @throws KeyStoreException in case of error 208 * @throws IOException in case of error 209 * @throws CertificateException in case of error 210 * @throws NoSuchAlgorithmException in case of error 211 * @since 13450 212 */ 213 default X509Certificate getX509Certificate(NativeCertAmend certAmend) 214 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { 215 return null; 216 } 217 218 /** 219 * Executes a native command and returns the first line of standard output. 220 * @param command array containing the command to call and its arguments. 221 * @return first stripped line of standard output 222 * @throws IOException if an I/O error occurs 223 * @since 12217 224 */ 225 default String exec(String... command) throws IOException { 226 Process p = Runtime.getRuntime().exec(command); 227 try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) { 228 return Utils.strip(input.readLine()); 229 } 230 } 231 232 /** 233 * Returns the platform-dependent default cache directory. 234 * @return the platform-dependent default cache directory 235 * @since 7829 236 */ 237 File getDefaultCacheDirectory(); 238 239 /** 240 * Returns the platform-dependent default preferences directory. 241 * @return the platform-dependent default preferences directory 242 * @since 7831 243 */ 244 File getDefaultPrefDirectory(); 245 246 /** 247 * Returns the platform-dependent default user data directory. 248 * @return the platform-dependent default user data directory 249 * @since 7834 250 */ 251 File getDefaultUserDataDirectory(); 252 253 /** 254 * Returns the list of platform-dependent default datum shifting directories for the PROJ.4 library. 255 * @return the list of platform-dependent default datum shifting directories for the PROJ.4 library 256 * @since 11642 257 */ 258 default List<File> getDefaultProj4NadshiftDirectories() { 259 return getPlatform().accept(NTV2Proj4DirGridShiftFileSource.getInstance()); 260 } 261 262 /** 263 * Determines if the JVM is OpenJDK-based. 264 * @return {@code true} if {@code java.home} contains "openjdk", {@code false} otherwise 265 * @since 12219 266 */ 267 default boolean isOpenJDK() { 268 String javaHome = Utils.getSystemProperty("java.home"); 269 return javaHome != null && javaHome.contains("openjdk"); 270 } 271 272 /** 273 * Returns extended modifier key used as the appropriate accelerator key for menu shortcuts. 274 * It is advised everywhere to use {@link Toolkit#getMenuShortcutKeyMask()} to get the cross-platform modifier, but: 275 * <ul> 276 * <li>it returns KeyEvent.CTRL_MASK instead of KeyEvent.CTRL_DOWN_MASK. We used the extended 277 * modifier for years, and Oracle recommends to use it instead, so it's best to keep it</li> 278 * <li>the method throws a HeadlessException ! So we would need to handle it for unit tests anyway</li> 279 * </ul> 280 * @return extended modifier key used as the appropriate accelerator key for menu shortcuts 281 * @since 12748 (as a replacement to {@code GuiHelper.getMenuShortcutKeyMaskEx()}) 282 */ 283 default int getMenuShortcutKeyMaskEx() { 284 // To remove when switching to Java 10+, and use Toolkit.getMenuShortcutKeyMaskEx instead 285 return KeyEvent.CTRL_DOWN_MASK; 286 } 287 288 /** 289 * Called when an outdated version of Java is detected at startup. 290 * @since 12270 291 */ 292 @FunctionalInterface 293 interface JavaExpirationCallback { 294 /** 295 * Asks user to update its version of Java. 296 * @param updVersion target update version 297 * @param url download URL 298 * @param major true for a migration towards a major version of Java (8:9), false otherwise 299 * @param eolDate the EOL/expiration date 300 */ 301 void askUpdateJava(String updVersion, String url, String eolDate, boolean major); 302 } 303 304 /** 305 * Checks if the running version of Java has expired, proposes to user to update it if needed. 306 * @param callback Java expiration callback 307 * @since 12270 (signature) 308 * @since 12219 309 */ 310 default void checkExpiredJava(JavaExpirationCallback callback) { 311 Date expiration = Utils.getJavaExpirationDate(); 312 if (expiration != null && expiration.before(new Date())) { 313 String version = Utils.getJavaLatestVersion(); 314 callback.askUpdateJava(version != null ? version : "latest", 315 Config.getPref().get("java.update.url", "https://www.java.com/download"), 316 DateUtils.getDateFormat(DateFormat.MEDIUM).format(expiration), false); 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}