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 callback Java expiration callback, providing GUI feedback 088 * @since 12270 (signature) 089 */ 090 default void startupHook(JavaExpirationCallback callback) { 091 // Do nothing 092 } 093 094 /** 095 * The openURL hook will be used to open an URL in the 096 * default web browser. 097 * @param url The URL to open 098 * @throws IOException if any I/O error occurs 099 */ 100 void openUrl(String url) throws IOException; 101 102 /** 103 * The initSystemShortcuts hook will be called by the 104 * Shortcut class after the modifier groups have been read 105 * from the config, but before any shortcuts are read from 106 * it or registered from within the application. 107 * 108 * Please note that you are not allowed to register any 109 * shortuts from this hook, but only "systemCuts"! 110 * 111 * BTW: SystemCuts should be named "system:<whatever>", 112 * and it'd be best if sou'd recycle the names already used 113 * by the Windows and OSX hooks. Especially the later has 114 * really many of them. 115 * 116 * You should also register any and all shortcuts that the 117 * operation system handles itself to block JOSM from trying 118 * to use them---as that would just not work. Call setAutomatic 119 * on them to prevent the keyboard preferences from allowing the 120 * user to change them. 121 */ 122 void initSystemShortcuts(); 123 124 /** 125 * Returns the default LAF to be used on this platform to look almost as a native application. 126 * @return The default native LAF for this platform 127 */ 128 String getDefaultStyle(); 129 130 /** 131 * Determines if the platform allows full-screen. 132 * @return {@code true} if full screen is allowed, {@code false} otherwise 133 */ 134 default boolean canFullscreen() { 135 return !GraphicsEnvironment.isHeadless() && 136 GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().isFullScreenSupported(); 137 } 138 139 /** 140 * Renames a file. 141 * @param from Source file 142 * @param to Target file 143 * @return {@code true} if the file has been renamed, {@code false} otherwise 144 */ 145 default boolean rename(File from, File to) { 146 return from.renameTo(to); 147 } 148 149 /** 150 * Returns a detailed OS description (at least family + version). 151 * @return A detailed OS description. 152 * @since 5850 153 */ 154 String getOSDescription(); 155 156 /** 157 * Returns OS build number. 158 * @return OS build number. 159 * @since 12217 160 */ 161 default String getOSBuildNumber() { 162 return ""; 163 } 164 165 /** 166 * Returns the {@code X509Certificate} matching the given certificate amendment information. 167 * @param certAmend certificate amendment 168 * @return the {@code X509Certificate} matching the given certificate amendment information, or {@code null} 169 * @throws KeyStoreException in case of error 170 * @throws IOException in case of error 171 * @throws CertificateException in case of error 172 * @throws NoSuchAlgorithmException in case of error 173 * @since 13450 174 */ 175 default X509Certificate getX509Certificate(NativeCertAmend certAmend) 176 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { 177 return null; 178 } 179 180 /** 181 * Executes a native command and returns the first line of standard output. 182 * @param command array containing the command to call and its arguments. 183 * @return first stripped line of standard output 184 * @throws IOException if an I/O error occurs 185 * @since 12217 186 */ 187 default String exec(String... command) throws IOException { 188 Process p = Runtime.getRuntime().exec(command); 189 try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) { 190 return Utils.strip(input.readLine()); 191 } 192 } 193 194 /** 195 * Returns the platform-dependent default cache directory. 196 * @return the platform-dependent default cache directory 197 * @since 7829 198 */ 199 File getDefaultCacheDirectory(); 200 201 /** 202 * Returns the platform-dependent default preferences directory. 203 * @return the platform-dependent default preferences directory 204 * @since 7831 205 */ 206 File getDefaultPrefDirectory(); 207 208 /** 209 * Returns the platform-dependent default user data directory. 210 * @return the platform-dependent default user data directory 211 * @since 7834 212 */ 213 File getDefaultUserDataDirectory(); 214 215 /** 216 * Returns the list of platform-dependent default datum shifting directories for the PROJ.4 library. 217 * @return the list of platform-dependent default datum shifting directories for the PROJ.4 library 218 * @since 11642 219 */ 220 default List<File> getDefaultProj4NadshiftDirectories() { 221 return getPlatform().accept(NTV2Proj4DirGridShiftFileSource.getInstance()); 222 } 223 224 /** 225 * Determines if the JVM is OpenJDK-based. 226 * @return {@code true} if {@code java.home} contains "openjdk", {@code false} otherwise 227 * @since 12219 228 */ 229 default boolean isOpenJDK() { 230 String javaHome = Utils.getSystemProperty("java.home"); 231 return javaHome != null && javaHome.contains("openjdk"); 232 } 233 234 /** 235 * Returns extended modifier key used as the appropriate accelerator key for menu shortcuts. 236 * It is advised everywhere to use {@link Toolkit#getMenuShortcutKeyMask()} to get the cross-platform modifier, but: 237 * <ul> 238 * <li>it returns KeyEvent.CTRL_MASK instead of KeyEvent.CTRL_DOWN_MASK. We used the extended 239 * modifier for years, and Oracle recommends to use it instead, so it's best to keep it</li> 240 * <li>the method throws a HeadlessException ! So we would need to handle it for unit tests anyway</li> 241 * </ul> 242 * @return extended modifier key used as the appropriate accelerator key for menu shortcuts 243 * @since 12748 (as a replacement to {@code GuiHelper.getMenuShortcutKeyMaskEx()}) 244 */ 245 default int getMenuShortcutKeyMaskEx() { 246 // To remove when switching to Java 10+, and use Toolkit.getMenuShortcutKeyMaskEx instead 247 return KeyEvent.CTRL_DOWN_MASK; 248 } 249 250 /** 251 * Called when an outdated version of Java is detected at startup. 252 * @since 12270 253 */ 254 @FunctionalInterface 255 interface JavaExpirationCallback { 256 /** 257 * Asks user to update its version of Java. 258 * @param updVersion target update version 259 * @param url download URL 260 * @param major true for a migration towards a major version of Java (8:9), false otherwise 261 * @param eolDate the EOL/expiration date 262 */ 263 void askUpdateJava(String updVersion, String url, String eolDate, boolean major); 264 } 265 266 /** 267 * Checks if the running version of Java has expired, proposes to user to update it if needed. 268 * @param callback Java expiration callback 269 * @since 12270 (signature) 270 * @since 12219 271 */ 272 default void checkExpiredJava(JavaExpirationCallback callback) { 273 Date expiration = Utils.getJavaExpirationDate(); 274 if (expiration != null && expiration.before(new Date())) { 275 String latestVersion = Utils.getJavaLatestVersion(); 276 String currentVersion = Utils.getSystemProperty("java.version"); 277 // #17831 WebStart may be launched with an expired JRE but then launching JOSM with up-to-date JRE 278 if (latestVersion == null || !latestVersion.equalsIgnoreCase(currentVersion)) { 279 callback.askUpdateJava(latestVersion != null ? latestVersion : "latest", 280 Config.getPref().get("java.update.url", "https://www.java.com/download"), 281 DateUtils.getDateFormat(DateFormat.MEDIUM).format(expiration), false); 282 } 283 } 284 } 285 286 /** 287 * Called when interfacing with native OS functions. Currently only used with macOS. 288 * The callback must perform all GUI-related tasks associated to an OS request. 289 * The non-GUI, platform-specific tasks, are usually performed by the {@code PlatformHook}. 290 * @since 12695 291 */ 292 interface NativeOsCallback { 293 /** 294 * macOS: Called when JOSM is asked to open a list of files. 295 * @param files list of files to open 296 */ 297 void openFiles(List<File> files); 298 299 /** 300 * macOS: Invoked when JOSM is asked to quit. 301 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation. 302 */ 303 boolean handleQuitRequest(); 304 305 /** 306 * macOS: Called when JOSM is asked to show it's about dialog. 307 */ 308 void handleAbout(); 309 310 /** 311 * macOS: Called when JOSM is asked to show it's preferences UI. 312 */ 313 void handlePreferences(); 314 } 315 316 /** 317 * Registers the native OS callback. Currently only needed for macOS. 318 * @param callback the native OS callback 319 * @since 12695 320 */ 321 default void setNativeOsCallback(NativeOsCallback callback) { 322 // To be implemented if needed 323 } 324 325 /** 326 * Resolves a file link to its destination file. 327 * @param file file (link or regular file) 328 * @return destination file in case of a file link, file if regular 329 * @since 13691 330 */ 331 default File resolveFileLink(File file) { 332 // Override if needed 333 return file; 334 } 335 336 /** 337 * Returns a set of possible platform specific directories where resources could be stored. 338 * @return A set of possible platform specific directories where resources could be stored. 339 * @since 14144 340 */ 341 default Collection<String> getPossiblePreferenceDirs() { 342 return Collections.emptyList(); 343 } 344}