Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / platform-impl / src / com / intellij / idea / StartupUtil.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.idea;
17
18 import com.intellij.openapi.application.ApplicationInfo;
19 import com.intellij.openapi.application.ApplicationNamesInfo;
20 import com.intellij.openapi.application.ConfigImportHelper;
21 import com.intellij.openapi.application.PathManager;
22 import com.intellij.openapi.application.impl.ApplicationInfoImpl;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.util.SystemInfo;
25 import com.intellij.openapi.util.SystemInfoRt;
26 import com.intellij.openapi.util.io.FileUtil;
27 import com.intellij.openapi.util.io.FileUtilRt;
28 import com.intellij.openapi.util.io.win32.IdeaWin32;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.ui.AppUIUtil;
31 import com.intellij.util.Consumer;
32 import com.intellij.util.EnvironmentUtil;
33 import com.intellij.util.lang.UrlClassLoader;
34 import com.sun.jna.Native;
35 import org.jetbrains.annotations.NonNls;
36
37 import javax.swing.*;
38 import java.io.File;
39 import java.lang.management.ManagementFactory;
40 import java.text.SimpleDateFormat;
41 import java.util.*;
42
43 /**
44  * @author yole
45  */
46 public class StartupUtil {
47   @NonNls public static final String NO_SPLASH = "nosplash";
48
49   private static SocketLock ourLock;
50   private static String myDefaultLAF;
51   private static String myWizardLAF;
52   private static String myWizardMacKeymap;
53   private static Set<String> myFeaturedPluginsToInstall = new HashSet<String>();
54
55   private StartupUtil() { }
56
57   public static void setDefaultLAF(String laf) {
58     myDefaultLAF = laf;
59   }
60
61   public static String getDefaultLAF() {
62     return myDefaultLAF;
63   }
64
65   public static void setWizardLAF(String myWizardLAF) {
66     StartupUtil.myWizardLAF = myWizardLAF;
67   }
68
69   public static String getWizardLAF() {
70     return myWizardLAF;
71   }
72
73   public static void setMyWizardMacKeymap(String myWizardMacKeymap) {
74     StartupUtil.myWizardMacKeymap = myWizardMacKeymap;
75   }
76
77   public static String getMyWizardMacKeymap() {
78     return myWizardMacKeymap;
79   }
80
81   public static Set<String> getMyFeaturedPluginsToInstall() {
82     return Collections.unmodifiableSet(myFeaturedPluginsToInstall);
83   }
84
85   public static void setFeaturedPluginsToInstall(Set<String> pluginsToInstall) {
86     myFeaturedPluginsToInstall.clear();
87     myFeaturedPluginsToInstall.addAll(pluginsToInstall);
88   }
89
90   public static boolean shouldShowSplash(final String[] args) {
91     return !Arrays.asList(args).contains(NO_SPLASH);
92   }
93
94   /** @deprecated use {@link Main#isHeadless()} (to remove in IDEA 14) */
95   @SuppressWarnings("unused")
96   public static boolean isHeadless() {
97     return Main.isHeadless();
98   }
99
100   public synchronized static void addExternalInstanceListener(Consumer<List<String>> consumer) {
101     ourLock.setActivateListener(consumer);
102   }
103
104   interface AppStarter {
105     void start(boolean newConfigFolder);
106   }
107
108   public synchronized static int getAcquiredPort() {
109     return ourLock.getAcquiredPort();
110   }
111
112   static void prepareAndStart(String[] args, AppStarter appStarter) {
113     boolean newConfigFolder = false;
114
115     if (!Main.isHeadless()) {
116       AppUIUtil.updateFrameClass();
117       newConfigFolder = !new File(PathManager.getConfigPath()).exists();
118     }
119
120     boolean canStart = checkJdkVersion() && checkSystemFolders() && lockSystemFolders(args);  // note: uses config folder!
121     if (!canStart) {
122       System.exit(Main.STARTUP_IMPOSSIBLE);
123     }
124
125     if (newConfigFolder) {
126       ConfigImportHelper.importConfigsTo(PathManager.getConfigPath());
127     }
128
129     Logger.setFactory(LoggerFactory.class);
130     Logger log = Logger.getInstance(Main.class);
131     startLogging(log);
132     loadSystemLibraries(log);
133     fixProcessEnvironment(log);
134
135     if (!Main.isHeadless()) {
136       AppUIUtil.updateWindowIcon(JOptionPane.getRootFrame());
137       AppUIUtil.registerBundledFonts();
138     }
139
140     appStarter.start(newConfigFolder);
141   }
142
143   /**
144    * Checks if the program can run under the JDK it was started with.
145    */
146   private static boolean checkJdkVersion() {
147     if (!"true".equals(System.getProperty("idea.no.jre.check"))) {
148       try {
149         // try to find a class from tools.jar
150         Class.forName("com.sun.jdi.Field");
151       }
152       catch (ClassNotFoundException e) {
153         String message = "'tools.jar' seems to be not in " + ApplicationNamesInfo.getInstance().getProductName() + " classpath.\n" +
154                          "Please ensure JAVA_HOME points to JDK rather than JRE.";
155         Main.showMessage("JDK Required", message, true);
156         return false;
157       }
158
159       if (StringUtil.containsIgnoreCase(System.getProperty("java.vm.name", ""), "OpenJDK") && !SystemInfo.isJavaVersionAtLeast("1.7")) {
160         String message = "OpenJDK 6 is not supported. Please use Oracle Java or newer OpenJDK.";
161         Main.showMessage("Unsupported JVM", message, true);
162         return false;
163       }
164     }
165
166     return true;
167   }
168
169   private synchronized static boolean checkSystemFolders() {
170     String configPath = PathManager.getConfigPath();
171     PathManager.ensureConfigFolderExists();
172     if (!new File(configPath).isDirectory()) {
173       String message = "Config path '" + configPath + "' is invalid.\n" +
174                        "If you have modified the '" + PathManager.PROPERTY_CONFIG_PATH + "' property please make sure it is correct,\n" +
175                        "otherwise please re-install the IDE.";
176       Main.showMessage("Invalid Config Path", message, true);
177       return false;
178     }
179
180     String systemPath = PathManager.getSystemPath();
181     if (!new File(systemPath).isDirectory()) {
182       String message = "System path '" + systemPath + "' is invalid.\n" +
183                        "If you have modified the '" + PathManager.PROPERTY_SYSTEM_PATH + "' property please make sure it is correct,\n" +
184                        "otherwise please re-install the IDE.";
185       Main.showMessage("Invalid System Path", message, true);
186       return false;
187     }
188
189     File ideTempDir = new File(PathManager.getTempPath());
190     String tempInaccessible = null;
191
192     if (!ideTempDir.isDirectory() && !ideTempDir.mkdirs()) {
193       tempInaccessible = "unable to create the directory";
194     }
195     else {
196       try {
197         File ideTempFile = new File(ideTempDir, "idea_tmp_check.sh");
198         FileUtil.writeToFile(ideTempFile, "#!/bin/sh\nexit 0");
199
200         if (SystemInfo.isWindows || SystemInfo.isMac) {
201           tempInaccessible = null;
202         }
203         else if (!ideTempFile.setExecutable(true, true)) {
204           tempInaccessible = "cannot set executable permission";
205         }
206         else if (new ProcessBuilder(ideTempFile.getAbsolutePath()).start().waitFor() != 0) {
207           tempInaccessible = "cannot execute test script";
208         }
209
210         if (!FileUtilRt.delete(ideTempFile)) {
211           ideTempFile.deleteOnExit();
212         }
213       }
214       catch (Exception e) {
215         tempInaccessible = e.getClass().getSimpleName() + ": " + e.getMessage();
216       }
217     }
218
219     if (tempInaccessible != null) {
220       String message = "Temp directory '" + ideTempDir + "' is inaccessible.\n" +
221                        "If you have modified the '" + PathManager.PROPERTY_SYSTEM_PATH + "' property please make sure it is correct,\n" +
222                        "otherwise please re-install the IDE.\n\nDetails: " + tempInaccessible;
223       Main.showMessage("Invalid System Path", message, true);
224       return false;
225     }
226
227     return true;
228   }
229
230   private synchronized static boolean lockSystemFolders(String[] args) {
231     if (ourLock == null) {
232       ourLock = new SocketLock();
233     }
234
235     SocketLock.ActivateStatus activateStatus = ourLock.lock(PathManager.getConfigPath(), true, args);
236     if (activateStatus == SocketLock.ActivateStatus.NO_INSTANCE) {
237       activateStatus = ourLock.lock(PathManager.getSystemPath(), false);
238     }
239
240     if (activateStatus != SocketLock.ActivateStatus.NO_INSTANCE) {
241       if (Main.isHeadless() || activateStatus == SocketLock.ActivateStatus.CANNOT_ACTIVATE) {
242         String message = "Only one instance of " + ApplicationNamesInfo.getInstance().getFullProductName() + " can be run at a time.";
243         Main.showMessage("Too Many Instances", message, true);
244       }
245       return false;
246     }
247
248     return true;
249   }
250
251   private static void fixProcessEnvironment(Logger log) {
252     if (!Main.isCommandLine()) {
253       System.setProperty("__idea.mac.env.lock", "unlocked");
254     }
255     boolean envReady = EnvironmentUtil.isEnvironmentReady();  // trigger environment loading
256     if (!envReady) {
257       log.info("initializing environment");
258     }
259   }
260
261   private static final String JAVA_IO_TEMP_DIR = "java.io.tmpdir";
262
263   private static void loadSystemLibraries(final Logger log) {
264     // load JNA and Snappy in own temp directory - to avoid collisions and work around no-exec /tmp
265     File ideTempDir = new File(PathManager.getTempPath());
266     if (!(ideTempDir.mkdirs() || ideTempDir.exists())) {
267       throw new RuntimeException("Unable to create temp directory '" + ideTempDir + "'");
268     }
269
270     String javaTempDir = System.getProperty(JAVA_IO_TEMP_DIR);
271     try {
272       System.setProperty(JAVA_IO_TEMP_DIR, ideTempDir.getPath());
273       if (System.getProperty("jna.nosys") == null && System.getProperty("jna.nounpack") == null) {
274         // force using bundled JNA dispatcher (if not explicitly stated)
275         System.setProperty("jna.nosys", "true");
276         System.setProperty("jna.nounpack", "false");
277       }
278       try {
279         final long t = System.currentTimeMillis();
280         log.info("JNA library loaded (" + (Native.POINTER_SIZE * 8) + "-bit) in " + (System.currentTimeMillis() - t) + " ms");
281       }
282       catch (Throwable t) {
283         logError(log, "Unable to load JNA library", t);
284       }
285     }
286     finally {
287       System.setProperty(JAVA_IO_TEMP_DIR, javaTempDir);
288     }
289
290     if (SystemInfo.isWin2kOrNewer) {
291       IdeaWin32.isAvailable();  // logging is done there
292     }
293
294     if (SystemInfo.isWin2kOrNewer && !Main.isHeadless()) {
295       try {
296         UrlClassLoader.loadPlatformLibrary("focusKiller");
297         log.info("Using \"FocusKiller\" library to prevent focus stealing.");
298       }
299       catch (Throwable t) {
300         log.info("\"FocusKiller\" library not found or there were problems loading it.", t);
301       }
302     }
303   }
304
305   private static void logError(Logger log, String message, Throwable t) {
306     message = message + " (OS: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + ")";
307     log.error(message, t);
308   }
309
310   private static void startLogging(final Logger log) {
311     Runtime.getRuntime().addShutdownHook(new Thread("Shutdown hook - logging") {
312       @Override
313       public void run() {
314         log.info("------------------------------------------------------ IDE SHUTDOWN ------------------------------------------------------");
315       }
316     });
317     log.info("------------------------------------------------------ IDE STARTED ------------------------------------------------------");
318
319     ApplicationInfo appInfo = ApplicationInfoImpl.getShadowInstance();
320     ApplicationNamesInfo namesInfo = ApplicationNamesInfo.getInstance();
321     String buildDate = new SimpleDateFormat("dd MMM yyyy HH:ss", Locale.US).format(appInfo.getBuildDate().getTime());
322     log.info("IDE: " + namesInfo.getFullProductName() + " (build #" + appInfo.getBuild().asStringWithAllDetails() + ", " + buildDate + ")");
323     log.info("OS: " + SystemInfoRt.OS_NAME + " (" + SystemInfoRt.OS_VERSION + ", " + SystemInfo.OS_ARCH + ")");
324     log.info("JRE: " + System.getProperty("java.runtime.version", "-") + " (" + System.getProperty("java.vendor", "-") + ")");
325     log.info("JVM: " + System.getProperty("java.vm.version", "-") + " (" + System.getProperty("java.vm.name", "-") + ")");
326
327     List<String> arguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
328     if (arguments != null) {
329       log.info("JVM Args: " + StringUtil.join(arguments, " "));
330     }
331   }
332 }