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 * 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 * Setup system keystore to add JOSM HTTPS certificate (for remote control). 168 * @param entryAlias The entry alias to use 169 * @param trustedCert the JOSM certificate for localhost 170 * @return {@code true} if something has changed as a result of the call (certificate installation, etc.) 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 7343 176 */ 177 default boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert) 178 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { 179 // TODO setup HTTPS certificate on Unix and OS X systems 180 return false; 181 } 182 183 /** 184 * Returns the {@code X509Certificate} matching the given certificate amendment information. 185 * @param certAmend certificate amendment 186 * @return the {@code X509Certificate} matching the given certificate amendment information, or {@code null} 187 * @throws KeyStoreException in case of error 188 * @throws IOException in case of error 189 * @throws CertificateException in case of error 190 * @throws NoSuchAlgorithmException in case of error 191 * @since 13450 192 */ 193 default X509Certificate getX509Certificate(NativeCertAmend certAmend) 194 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { 195 return null; 196 } 197 198 /** 199 * Executes a native command and returns the first line of standard output. 200 * @param command array containing the command to call and its arguments. 201 * @return first stripped line of standard output 202 * @throws IOException if an I/O error occurs 203 * @since 12217 204 */ 205 default String exec(String... command) throws IOException { 206 Process p = Runtime.getRuntime().exec(command); 207 try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) { 208 return Utils.strip(input.readLine()); 209 } 210 } 211 212 /** 213 * Returns the platform-dependent default cache directory. 214 * @return the platform-dependent default cache directory 215 * @since 7829 216 */ 217 File getDefaultCacheDirectory(); 218 219 /** 220 * Returns the platform-dependent default preferences directory. 221 * @return the platform-dependent default preferences directory 222 * @since 7831 223 */ 224 File getDefaultPrefDirectory(); 225 226 /** 227 * Returns the platform-dependent default user data directory. 228 * @return the platform-dependent default user data directory 229 * @since 7834 230 */ 231 File getDefaultUserDataDirectory(); 232 233 /** 234 * Returns the list of platform-dependent default datum shifting directories for the PROJ.4 library. 235 * @return the list of platform-dependent default datum shifting directories for the PROJ.4 library 236 * @since 11642 237 */ 238 default List<File> getDefaultProj4NadshiftDirectories() { 239 return getPlatform().accept(NTV2Proj4DirGridShiftFileSource.getInstance()); 240 } 241 242 /** 243 * Determines if the JVM is OpenJDK-based. 244 * @return {@code true} if {@code java.home} contains "openjdk", {@code false} otherwise 245 * @since 12219 246 */ 247 default boolean isOpenJDK() { 248 String javaHome = Utils.getSystemProperty("java.home"); 249 return javaHome != null && javaHome.contains("openjdk"); 250 } 251 252 /** 253 * Returns extended modifier key used as the appropriate accelerator key for menu shortcuts. 254 * It is advised everywhere to use {@link Toolkit#getMenuShortcutKeyMask()} to get the cross-platform modifier, but: 255 * <ul> 256 * <li>it returns KeyEvent.CTRL_MASK instead of KeyEvent.CTRL_DOWN_MASK. We used the extended 257 * modifier for years, and Oracle recommends to use it instead, so it's best to keep it</li> 258 * <li>the method throws a HeadlessException ! So we would need to handle it for unit tests anyway</li> 259 * </ul> 260 * @return extended modifier key used as the appropriate accelerator key for menu shortcuts 261 * @since 12748 (as a replacement to {@code GuiHelper.getMenuShortcutKeyMaskEx()}) 262 */ 263 default int getMenuShortcutKeyMaskEx() { 264 // To remove when switching to Java 10+, and use Toolkit.getMenuShortcutKeyMaskEx instead 265 return KeyEvent.CTRL_DOWN_MASK; 266 } 267 268 /** 269 * Called when an outdated version of Java is detected at startup. 270 * @since 12270 271 */ 272 @FunctionalInterface 273 interface JavaExpirationCallback { 274 /** 275 * Asks user to update its version of Java. 276 * @param updVersion target update version 277 * @param url download URL 278 * @param major true for a migration towards a major version of Java (8:9), false otherwise 279 * @param eolDate the EOL/expiration date 280 */ 281 void askUpdateJava(String updVersion, String url, String eolDate, boolean major); 282 } 283 284 /** 285 * Checks if the running version of Java has expired, proposes to user to update it if needed. 286 * @param callback Java expiration callback 287 * @since 12270 (signature) 288 * @since 12219 289 */ 290 default void checkExpiredJava(JavaExpirationCallback callback) { 291 Date expiration = Utils.getJavaExpirationDate(); 292 if (expiration != null && expiration.before(new Date())) { 293 String version = Utils.getJavaLatestVersion(); 294 callback.askUpdateJava(version != null ? version : "latest", 295 Config.getPref().get("java.update.url", "https://www.java.com/download"), 296 DateUtils.getDateFormat(DateFormat.MEDIUM).format(expiration), false); 297 } 298 } 299 300 /** 301 * Called when interfacing with native OS functions. Currently only used with macOS. 302 * The callback must perform all GUI-related tasks associated to an OS request. 303 * The non-GUI, platform-specific tasks, are usually performed by the {@code PlatformHook}. 304 * @since 12695 305 */ 306 interface NativeOsCallback { 307 /** 308 * macOS: Called when JOSM is asked to open a list of files. 309 * @param files list of files to open 310 */ 311 void openFiles(List<File> files); 312 313 /** 314 * macOS: Invoked when JOSM is asked to quit. 315 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation. 316 */ 317 boolean handleQuitRequest(); 318 319 /** 320 * macOS: Called when JOSM is asked to show it's about dialog. 321 */ 322 void handleAbout(); 323 324 /** 325 * macOS: Called when JOSM is asked to show it's preferences UI. 326 */ 327 void handlePreferences(); 328 } 329 330 /** 331 * Registers the native OS callback. Currently only needed for macOS. 332 * @param callback the native OS callback 333 * @since 12695 334 */ 335 default void setNativeOsCallback(NativeOsCallback callback) { 336 // To be implemented if needed 337 } 338 339 /** 340 * Resolves a file link to its destination file. 341 * @param file file (link or regular file) 342 * @return destination file in case of a file link, file if regular 343 * @since 13691 344 */ 345 default File resolveFileLink(File file) { 346 // Override if needed 347 return file; 348 } 349 350 /** 351 * Returns a set of possible platform specific directories where resources could be stored. 352 * @return A set of possible platform specific directories where resources could be stored. 353 * @since 14144 354 */ 355 default Collection<String> getPossiblePreferenceDirs() { 356 return Collections.emptyList(); 357 } 358}