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.Date; 019import java.util.List; 020 021import org.openstreetmap.josm.data.projection.datum.NTV2Proj4DirGridShiftFileSource; 022import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend; 023import org.openstreetmap.josm.spi.preferences.Config; 024import org.openstreetmap.josm.tools.date.DateUtils; 025 026/** 027 * This interface allows platform (operating system) dependent code 028 * to be bundled into self-contained classes. 029 * @since 1023 030 */ 031public interface PlatformHook { 032 033 /** 034 * Visitor to construct a PlatformHook from a given {@link Platform} object. 035 */ 036 PlatformVisitor<PlatformHook> CONSTRUCT_FROM_PLATFORM = new PlatformVisitor<PlatformHook>() { 037 @Override 038 public PlatformHook visitUnixoid() { 039 return new PlatformHookUnixoid(); 040 } 041 042 @Override 043 public PlatformHook visitWindows() { 044 return new PlatformHookWindows(); 045 } 046 047 @Override 048 public PlatformHook visitOsx() { 049 return new PlatformHookOsx(); 050 } 051 }; 052 053 /** 054 * Get the platform corresponding to this platform hook. 055 * @return the platform corresponding to this platform hook 056 */ 057 Platform getPlatform(); 058 059 /** 060 * The preStartupHook will be called extremly early. It is 061 * guaranteed to be called before the GUI setup has started. 062 * 063 * Reason: On OSX we need to inform the Swing libraries 064 * that we want to be integrated with the OS before we setup our GUI. 065 */ 066 default void preStartupHook() { 067 // Do nothing 068 } 069 070 /** 071 * The afterPrefStartupHook will be called early, but after 072 * the preferences have been loaded and basic processing of 073 * command line arguments is finished. 074 * It is guaranteed to be called before the GUI setup has started. 075 */ 076 default void afterPrefStartupHook() { 077 // Do nothing 078 } 079 080 /** 081 * The startupHook will be called early, but after the GUI 082 * setup has started. 083 * 084 * Reason: On OSX we need to register some callbacks with the 085 * OS, so we'll receive events from the system menu. 086 * @param callback Java expiration callback, providing GUI feedback 087 * @since 12270 (signature) 088 */ 089 default void startupHook(JavaExpirationCallback callback) { 090 // Do nothing 091 } 092 093 /** 094 * The openURL hook will be used to open an URL in the 095 * default web browser. 096 * @param url The URL to open 097 * @throws IOException if any I/O error occurs 098 */ 099 void openUrl(String url) throws IOException; 100 101 /** 102 * The initSystemShortcuts hook will be called by the 103 * Shortcut class after the modifier groups have been read 104 * from the config, but before any shortcuts are read from 105 * it or registered from within the application. 106 * 107 * Please note that you are not allowed to register any 108 * shortuts from this hook, but only "systemCuts"! 109 * 110 * BTW: SystemCuts should be named "system:<whatever>", 111 * and it'd be best if sou'd recycle the names already used 112 * by the Windows and OSX hooks. Especially the later has 113 * really many of them. 114 * 115 * You should also register any and all shortcuts that the 116 * operation system handles itself to block JOSM from trying 117 * to use them---as that would just not work. Call setAutomatic 118 * on them to prevent the keyboard preferences from allowing the 119 * user to change them. 120 */ 121 void initSystemShortcuts(); 122 123 /** 124 * The makeTooltip hook will be called whenever a tooltip for 125 * a menu or button is created. 126 * 127 * Tooltips are usually not system dependent, unless the 128 * JVM is too dumb to provide correct names for all the keys. 129 * 130 * Some LAFs don't understand HTML, such as the OSX LAFs. 131 * 132 * @param name Tooltip text to display 133 * @param sc Shortcut associated (to display accelerator between parenthesis) 134 * @return Full tooltip text (name + accelerator) 135 */ 136 default String makeTooltip(String name, Shortcut sc) { 137 StringBuilder result = new StringBuilder(); 138 result.append("<html>").append(name); 139 if (sc != null && !sc.getKeyText().isEmpty()) { 140 result.append(" <font size='-2'>(") 141 .append(sc.getKeyText()) 142 .append(")</font>"); 143 } 144 return result.append(" </html>").toString(); 145 } 146 147 /** 148 * Returns the default LAF to be used on this platform to look almost as a native application. 149 * @return The default native LAF for this platform 150 */ 151 String getDefaultStyle(); 152 153 /** 154 * Determines if the platform allows full-screen. 155 * @return {@code true} if full screen is allowed, {@code false} otherwise 156 */ 157 default boolean canFullscreen() { 158 return !GraphicsEnvironment.isHeadless() && 159 GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().isFullScreenSupported(); 160 } 161 162 /** 163 * Renames a file. 164 * @param from Source file 165 * @param to Target file 166 * @return {@code true} if the file has been renamed, {@code false} otherwise 167 */ 168 default boolean rename(File from, File to) { 169 return from.renameTo(to); 170 } 171 172 /** 173 * Returns a detailed OS description (at least family + version). 174 * @return A detailed OS description. 175 * @since 5850 176 */ 177 String getOSDescription(); 178 179 /** 180 * Returns OS build number. 181 * @return OS build number. 182 * @since 12217 183 */ 184 default String getOSBuildNumber() { 185 return ""; 186 } 187 188 /** 189 * Setup system keystore to add JOSM HTTPS certificate (for remote control). 190 * @param entryAlias The entry alias to use 191 * @param trustedCert the JOSM certificate for localhost 192 * @return {@code true} if something has changed as a result of the call (certificate installation, etc.) 193 * @throws KeyStoreException in case of error 194 * @throws IOException in case of error 195 * @throws CertificateException in case of error 196 * @throws NoSuchAlgorithmException in case of error 197 * @since 7343 198 */ 199 default boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert) 200 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { 201 // TODO setup HTTPS certificate on Unix and OS X systems 202 return false; 203 } 204 205 /** 206 * Returns the {@code X509Certificate} matching the given certificate amendment information. 207 * @param certAmend certificate amendment 208 * @return the {@code X509Certificate} matching the given certificate amendment information, or {@code null} 209 * @throws KeyStoreException in case of error 210 * @throws IOException in case of error 211 * @throws CertificateException in case of error 212 * @throws NoSuchAlgorithmException in case of error 213 * @since 13450 214 */ 215 default X509Certificate getX509Certificate(NativeCertAmend certAmend) 216 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { 217 return null; 218 } 219 220 /** 221 * Executes a native command and returns the first line of standard output. 222 * @param command array containing the command to call and its arguments. 223 * @return first stripped line of standard output 224 * @throws IOException if an I/O error occurs 225 * @since 12217 226 */ 227 default String exec(String... command) throws IOException { 228 Process p = Runtime.getRuntime().exec(command); 229 try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) { 230 return Utils.strip(input.readLine()); 231 } 232 } 233 234 /** 235 * Returns the platform-dependent default cache directory. 236 * @return the platform-dependent default cache directory 237 * @since 7829 238 */ 239 File getDefaultCacheDirectory(); 240 241 /** 242 * Returns the platform-dependent default preferences directory. 243 * @return the platform-dependent default preferences directory 244 * @since 7831 245 */ 246 File getDefaultPrefDirectory(); 247 248 /** 249 * Returns the platform-dependent default user data directory. 250 * @return the platform-dependent default user data directory 251 * @since 7834 252 */ 253 File getDefaultUserDataDirectory(); 254 255 /** 256 * Returns the list of platform-dependent default datum shifting directories for the PROJ.4 library. 257 * @return the list of platform-dependent default datum shifting directories for the PROJ.4 library 258 * @since 11642 259 */ 260 default List<File> getDefaultProj4NadshiftDirectories() { 261 return getPlatform().accept(NTV2Proj4DirGridShiftFileSource.getInstance()); 262 } 263 264 /** 265 * Determines if the JVM is OpenJDK-based. 266 * @return {@code true} if {@code java.home} contains "openjdk", {@code false} otherwise 267 * @since 12219 268 */ 269 default boolean isOpenJDK() { 270 String javaHome = Utils.getSystemProperty("java.home"); 271 return javaHome != null && javaHome.contains("openjdk"); 272 } 273 274 /** 275 * Returns extended modifier key used as the appropriate accelerator key for menu shortcuts. 276 * It is advised everywhere to use {@link Toolkit#getMenuShortcutKeyMask()} to get the cross-platform modifier, but: 277 * <ul> 278 * <li>it returns KeyEvent.CTRL_MASK instead of KeyEvent.CTRL_DOWN_MASK. We used the extended 279 * modifier for years, and Oracle recommends to use it instead, so it's best to keep it</li> 280 * <li>the method throws a HeadlessException ! So we would need to handle it for unit tests anyway</li> 281 * </ul> 282 * @return extended modifier key used as the appropriate accelerator key for menu shortcuts 283 * @since 12748 (as a replacement to {@code GuiHelper.getMenuShortcutKeyMaskEx()}) 284 */ 285 default int getMenuShortcutKeyMaskEx() { 286 // To remove when switching to Java 10+, and use Toolkit.getMenuShortcutKeyMaskEx instead 287 return KeyEvent.CTRL_DOWN_MASK; 288 } 289 290 /** 291 * Called when an outdated version of Java is detected at startup. 292 * @since 12270 293 */ 294 @FunctionalInterface 295 interface JavaExpirationCallback { 296 /** 297 * Asks user to update its version of Java. 298 * @param updVersion target update version 299 * @param url download URL 300 * @param major true for a migration towards a major version of Java (8:9), false otherwise 301 * @param eolDate the EOL/expiration date 302 */ 303 void askUpdateJava(String updVersion, String url, String eolDate, boolean major); 304 } 305 306 /** 307 * Checks if the running version of Java has expired, proposes to user to update it if needed. 308 * @param callback Java expiration callback 309 * @since 12270 (signature) 310 * @since 12219 311 */ 312 default void checkExpiredJava(JavaExpirationCallback callback) { 313 Date expiration = Utils.getJavaExpirationDate(); 314 if (expiration != null && expiration.before(new Date())) { 315 String version = Utils.getJavaLatestVersion(); 316 callback.askUpdateJava(version != null ? version : "latest", 317 Config.getPref().get("java.update.url", "https://www.java.com/download"), 318 DateUtils.getDateFormat(DateFormat.MEDIUM).format(expiration), false); 319 } 320 } 321 322 /** 323 * Called when interfacing with native OS functions. Currently only used with macOS. 324 * The callback must perform all GUI-related tasks associated to an OS request. 325 * The non-GUI, platform-specific tasks, are usually performed by the {@code PlatformHook}. 326 * @since 12695 327 */ 328 interface NativeOsCallback { 329 /** 330 * macOS: Called when JOSM is asked to open a list of files. 331 * @param files list of files to open 332 */ 333 void openFiles(List<File> files); 334 335 /** 336 * macOS: Invoked when JOSM is asked to quit. 337 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation. 338 */ 339 boolean handleQuitRequest(); 340 341 /** 342 * macOS: Called when JOSM is asked to show it's about dialog. 343 */ 344 void handleAbout(); 345 346 /** 347 * macOS: Called when JOSM is asked to show it's preferences UI. 348 */ 349 void handlePreferences(); 350 } 351 352 /** 353 * Registers the native OS callback. Currently only needed for macOS. 354 * @param callback the native OS callback 355 * @since 12695 356 */ 357 default void setNativeOsCallback(NativeOsCallback callback) { 358 // To be implemented if needed 359 } 360 361 /** 362 * Resolves a file link to its destination file. 363 * @param file file (link or regular file) 364 * @return destination file in case of a file link, file if regular 365 * @since 13691 366 */ 367 default File resolveFileLink(File file) { 368 // Override if needed 369 return file; 370 } 371}