4e05116d7df5a689219b8e96c6ed7398b40cbb50
[idea/community.git] / platform / util / src / com / intellij / openapi / application / PathManager.java
1 /*
2  * Copyright 2000-2015 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.openapi.application;
17
18 import com.intellij.openapi.util.NamedJDOMExternalizable;
19 import com.intellij.openapi.util.Pair;
20 import com.intellij.openapi.util.SystemInfo;
21 import com.intellij.openapi.util.SystemInfoRt;
22 import com.intellij.openapi.util.io.FileUtil;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.util.io.URLUtil;
25 import com.sun.jna.TypeMapper;
26 import com.sun.jna.platform.FileUtils;
27 import gnu.trove.THashSet;
28 import org.apache.log4j.Appender;
29 import org.apache.oro.text.regex.PatternMatcher;
30 import org.jdom.Document;
31 import org.jetbrains.annotations.Contract;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34 import org.picocontainer.PicoContainer;
35
36 import java.io.*;
37 import java.net.URL;
38 import java.util.*;
39
40 import static com.intellij.util.SystemProperties.getUserHome;
41
42 public class PathManager {
43   public static final String PROPERTIES_FILE = "idea.properties.file";
44   public static final String PROPERTY_SYSTEM_PATH = "idea.system.path";
45   public static final String PROPERTY_CONFIG_PATH = "idea.config.path";
46   public static final String PROPERTY_PLUGINS_PATH = "idea.plugins.path";
47   public static final String PROPERTY_HOME_PATH = "idea.home.path";
48   public static final String PROPERTY_LOG_PATH = "idea.log.path";
49   public static final String PROPERTY_PATHS_SELECTOR = "idea.paths.selector";
50   public static final String DEFAULT_OPTIONS_FILE_NAME = "other";
51
52   private static final String LIB_FOLDER = "lib";
53   private static final String PLUGINS_FOLDER = "plugins";
54   private static final String BIN_FOLDER = "bin";
55   private static final String LOG_DIRECTORY = "log";
56   private static final String CONFIG_FOLDER = "config";
57   private static final String OPTIONS_FOLDER = "options";
58   private static final String SYSTEM_FOLDER = "system";
59   private static final String PATHS_SELECTOR = System.getProperty(PROPERTY_PATHS_SELECTOR);
60
61   private static String ourHomePath;
62   private static String ourSystemPath;
63   private static String ourConfigPath;
64   private static String ourPluginsPath;
65   private static String ourLogPath;
66
67   // IDE installation paths
68
69   @NotNull
70   public static String getHomePath() {
71     if (ourHomePath != null) return ourHomePath;
72
73     String fromProperty = System.getProperty(PROPERTY_HOME_PATH);
74     if (fromProperty != null) {
75       ourHomePath = getAbsolutePath(fromProperty);
76       if (!new File(ourHomePath).isDirectory()) {
77         throw new RuntimeException("Invalid home path '" + ourHomePath + "'");
78       }
79     }
80     else {
81       ourHomePath = getHomePathFor(PathManager.class);
82       if (ourHomePath == null) {
83         String advice = SystemInfo.isMac ? "reinstall the software."
84                                          : "make sure bin/idea.properties is present in the installation directory.";
85         throw new RuntimeException("Could not find installation home path. Please " + advice);
86       }
87     }
88
89     if (SystemInfo.isWindows) {
90       try {
91         ourHomePath = new File(ourHomePath).getCanonicalPath();
92       }
93       catch (IOException ignored) { }
94     }
95
96     return ourHomePath;
97   }
98
99   @Nullable
100   public static String getHomePathFor(@NotNull Class aClass) {
101     String rootPath = getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
102     if (rootPath == null) return null;
103
104     File root = new File(rootPath).getAbsoluteFile();
105     do {
106       String parent = root.getParent();
107       if (parent == null) return null;
108       root = new File(parent).getAbsoluteFile();  // one step back to get folder
109     }
110     while (!isIdeaHome(root));
111     return root.getAbsolutePath();
112   }
113
114   private static boolean isIdeaHome(final File root) {
115     return new File(root, FileUtil.toSystemDependentName("bin/idea.properties")).exists() ||
116            new File(root, FileUtil.toSystemDependentName("community/bin/idea.properties")).exists();
117   }
118
119   @NotNull
120   public static String getBinPath() {
121     return getHomePath() + File.separator + BIN_FOLDER;
122   }
123
124   @NotNull
125   public static String getLibPath() {
126     return getHomePath() + File.separator + LIB_FOLDER;
127   }
128
129   @SuppressWarnings("MethodNamesDifferingOnlyByCase")
130   @NotNull
131   public static String getPreInstalledPluginsPath() {
132     return getHomePath() + File.separatorChar + PLUGINS_FOLDER;
133   }
134
135   // config paths
136
137   @Nullable
138   public static String getPathsSelector() {
139     return PATHS_SELECTOR;
140   }
141
142   @NotNull
143   public static String getConfigPath() {
144     if (ourConfigPath != null) return ourConfigPath;
145
146     if (System.getProperty(PROPERTY_CONFIG_PATH) != null) {
147       ourConfigPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_CONFIG_PATH)));
148     }
149     else if (PATHS_SELECTOR != null) {
150       ourConfigPath = getDefaultConfigPathFor(PATHS_SELECTOR);
151     }
152     else {
153       ourConfigPath = getHomePath() + File.separator + CONFIG_FOLDER;
154     }
155
156     return ourConfigPath;
157   }
158
159   @NotNull
160   public static String getDefaultConfigPathFor(@NotNull String selector) {
161     return platformPath(selector, "Library/Preferences", CONFIG_FOLDER);
162   }
163
164   @NotNull
165   public static String getDefaultPluginPathFor(@NotNull String selector) {
166     return platformPath(selector, "Library/Application Support", PLUGINS_FOLDER);
167   }
168
169   public static void ensureConfigFolderExists() {
170     checkAndCreate(getConfigPath(), true);
171   }
172
173   @NotNull
174   public static String getOptionsPath() {
175     return getConfigPath() + File.separator + OPTIONS_FOLDER;
176   }
177
178   @NotNull
179   public static File getOptionsFile(@NotNull String fileName) {
180     return new File(getOptionsPath(), fileName + ".xml");
181   }
182
183   @NotNull
184   public static File getOptionsFile(@NotNull NamedJDOMExternalizable externalizable) {
185     return getOptionsFile(externalizable.getExternalFileName());
186   }
187
188   @NotNull
189   public static String getPluginsPath() {
190     if (ourPluginsPath != null) return ourPluginsPath;
191
192     if (System.getProperty(PROPERTY_PLUGINS_PATH) != null) {
193       ourPluginsPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_PLUGINS_PATH)));
194     }
195     else if (SystemInfo.isMac && PATHS_SELECTOR != null) {
196       ourPluginsPath = getUserHome() + File.separator + "Library/Application Support" + File.separator + PATHS_SELECTOR;
197     }
198     else {
199       ourPluginsPath = getConfigPath() + File.separatorChar + PLUGINS_FOLDER;
200     }
201
202     return ourPluginsPath;
203   }
204
205   // runtime paths
206
207   @NotNull
208   public static String getSystemPath() {
209     if (ourSystemPath != null) return ourSystemPath;
210
211     if (System.getProperty(PROPERTY_SYSTEM_PATH) != null) {
212       ourSystemPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_SYSTEM_PATH)));
213     }
214     else if (PATHS_SELECTOR != null) {
215       ourSystemPath = platformPath(PATHS_SELECTOR, "Library/Caches", SYSTEM_FOLDER);
216     }
217     else {
218       ourSystemPath = getHomePath() + File.separator + SYSTEM_FOLDER;
219     }
220
221     checkAndCreate(ourSystemPath, true);
222     return ourSystemPath;
223   }
224
225   @NotNull
226   public static String getTempPath() {
227     return getSystemPath() + File.separator + "tmp";
228   }
229
230   @NotNull
231   public static File getIndexRoot() {
232     String indexRoot = System.getProperty("index_root_path", getSystemPath() + "/index");
233     checkAndCreate(indexRoot, true);
234     return new File(indexRoot);
235   }
236
237   @NotNull
238   public static String getLogPath() {
239     if (ourLogPath != null) return ourLogPath;
240
241     if (System.getProperty(PROPERTY_LOG_PATH) != null) {
242       ourLogPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_LOG_PATH)));
243     }
244     else if (SystemInfo.isMac && PATHS_SELECTOR != null) {
245       ourLogPath = getUserHome() + File.separator + "Library/Logs" + File.separator + PATHS_SELECTOR;
246     }
247     else {
248       ourLogPath = getSystemPath() + File.separatorChar + LOG_DIRECTORY;
249     }
250
251     return ourLogPath;
252   }
253
254   @NotNull
255   public static String getPluginTempPath () {
256     return getSystemPath() + File.separator + PLUGINS_FOLDER;
257   }
258
259   // misc stuff
260
261   /**
262    * Attempts to detect classpath entry which contains given resource.
263    */
264   @Nullable
265   public static String getResourceRoot(@NotNull Class context, String path) {
266     URL url = context.getResource(path);
267     if (url == null) {
268       url = ClassLoader.getSystemResource(path.substring(1));
269     }
270     return url != null ? extractRoot(url, path) : null;
271   }
272
273   /**
274    * Attempts to extract classpath entry part from passed URL.
275    */
276   @Nullable
277   private static String extractRoot(URL resourceURL, String resourcePath) {
278     if (!(StringUtil.startsWithChar(resourcePath, '/') || StringUtil.startsWithChar(resourcePath, '\\'))) {
279       //noinspection UseOfSystemOutOrSystemErr
280       System.err.println("precondition failed: " + resourcePath);
281       return null;
282     }
283
284     String resultPath = null;
285     String protocol = resourceURL.getProtocol();
286     if (URLUtil.FILE_PROTOCOL.equals(protocol)) {
287       String path = resourceURL.getFile();
288       String testPath = path.replace('\\', '/');
289       String testResourcePath = resourcePath.replace('\\', '/');
290       if (StringUtil.endsWithIgnoreCase(testPath, testResourcePath)) {
291         resultPath = path.substring(0, path.length() - resourcePath.length());
292       }
293     }
294     else if (URLUtil.JAR_PROTOCOL.equals(protocol)) {
295       Pair<String, String> paths = URLUtil.splitJarUrl(resourceURL.getFile());
296       if (paths != null) {
297         resultPath = paths.first;
298       }
299     }
300
301     if (resultPath == null) {
302       //noinspection UseOfSystemOutOrSystemErr
303       System.err.println("cannot extract: " + resourcePath + " from " + resourceURL);
304       return null;
305     }
306
307     resultPath = StringUtil.trimEnd(resultPath, File.separator);
308     resultPath = URLUtil.unescapePercentSequences(resultPath);
309
310     return resultPath;
311   }
312
313   public static String getUserPropertiesPath() {
314     if (PATHS_SELECTOR != null) {
315       return platformPath(PATHS_SELECTOR, "Library/Preferences", ".");
316     }
317     else {
318       return getUserHome();
319     }
320   }
321
322   public static void loadProperties() {
323     String[] propFiles = {
324       System.getProperty(PROPERTIES_FILE),
325       getUserPropertiesPath() + "/idea.properties",
326       getHomePath() + "/bin/idea.properties",
327       getHomePath() + "/community/bin/idea.properties"};
328
329     for (String path : propFiles) {
330       if (path != null) {
331         File propFile = new File(path);
332         if (propFile.exists()) {
333           try {
334             Reader fis = new BufferedReader(new FileReader(propFile));
335             try {
336               Map<String, String> properties = FileUtil.loadProperties(fis);
337
338               String home = properties.get("idea.home");
339               if (home != null && ourHomePath == null) {
340                 ourHomePath = getAbsolutePath(substituteVars(home));
341               }
342
343               Properties sysProperties = System.getProperties();
344               for (String key : properties.keySet()) {
345                 if (sysProperties.getProperty(key, null) == null) { // load the property from the property file only if it is not defined yet
346                   String value = substituteVars(properties.get(key));
347                   sysProperties.setProperty(key, value);
348                 }
349               }
350             }
351             finally {
352               fis.close();
353             }
354           }
355           catch (IOException e) {
356             //noinspection UseOfSystemOutOrSystemErr
357             System.err.println("Problem reading from property file: " + propFile.getPath());
358           }
359         }
360       }
361     }
362   }
363
364   @Contract("null -> null")
365   public static String substituteVars(String s) {
366     return substituteVars(s, getHomePath());
367   }
368
369   @Contract("null, _ -> null")
370   public static String substituteVars(String s, String ideaHomePath) {
371     if (s == null) return null;
372
373     if (s.startsWith("..")) {
374       s = ideaHomePath + File.separatorChar + BIN_FOLDER + File.separatorChar + s;
375     }
376
377     s = StringUtil.replace(s, "${idea.home}", ideaHomePath);
378
379     Properties props = System.getProperties();
380     for (final Object key1 : props.keySet()) {
381       String key = (String)key1;
382       String value = props.getProperty(key);
383       s = StringUtil.replace(s, "${" + key + "}", value);
384     }
385
386     return s;
387   }
388
389   @NotNull
390   public static File findFileInLibDirectory(@NotNull String relativePath) {
391     File file = new File(getLibPath() + File.separator + relativePath);
392     return file.exists() ? file : new File(getHomePath(), "community" + File.separator + "lib" + File.separator + relativePath);
393   }
394
395   @Nullable
396   public static String getJarPathForClass(@NotNull Class aClass) {
397     String resourceRoot = getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
398     return resourceRoot != null ? new File(resourceRoot).getAbsolutePath() : null;
399   }
400
401   @NotNull
402   public static Collection<String> getUtilClassPath() {
403     final Class<?>[] classes = {
404       PathManager.class,            // module 'util'
405       NotNull.class,                // module 'annotations'
406       SystemInfoRt.class,           // module 'util-rt'
407       Document.class,               // jDOM
408       Appender.class,               // log4j
409       THashSet.class,               // trove4j
410       PicoContainer.class,          // PicoContainer
411       TypeMapper.class,             // JNA
412       FileUtils.class,              // JNA (jna-utils)
413       PatternMatcher.class          // OROMatcher
414     };
415
416     final Set<String> classPath = new HashSet<String>();
417     for (Class<?> aClass : classes) {
418       final String path = getJarPathForClass(aClass);
419       if (path != null) {
420         classPath.add(path);
421       }
422     }
423
424     final String resourceRoot = getResourceRoot(PathManager.class, "/messages/CommonBundle.properties");  // platform-resources-en
425     if (resourceRoot != null) {
426       classPath.add(new File(resourceRoot).getAbsolutePath());
427     }
428
429     return Collections.unmodifiableCollection(classPath);
430   }
431
432   // helpers
433
434   private static String getAbsolutePath(String path) {
435     path = FileUtil.expandUserHome(path);
436     return FileUtil.toCanonicalPath(new File(path).getAbsolutePath());
437   }
438
439   private static String trimPathQuotes(String path){
440     if (!(path != null && !(path.length() < 3))){
441       return path;
442     }
443     if (StringUtil.startsWithChar(path, '\"') && StringUtil.endsWithChar(path, '\"')){
444       return path.substring(1, path.length() - 1);
445     }
446     return path;
447   }
448
449   // todo[r.sh] XDG directories, Windows folders
450   private static String platformPath(String selector, String macDir, String fallback) {
451     if (SystemInfo.isMac) {
452       return getUserHome() + File.separator + macDir + File.separator + selector;
453     }
454     else {
455       return getUserHome() + File.separator + "." + selector + File.separator + fallback;
456     }
457   }
458
459   private static boolean checkAndCreate(String path, boolean createIfNotExists) {
460     if (createIfNotExists) {
461       File file = new File(path);
462       if (!file.exists()) {
463         return file.mkdirs();
464       }
465     }
466     return false;
467   }
468
469   // outdated stuff
470
471   /** @deprecated use {@link #getPluginsPath()} (to remove in IDEA 14) */
472   @SuppressWarnings("UnusedDeclaration") public static final String PLUGINS_DIRECTORY = PLUGINS_FOLDER;
473
474   /** @deprecated use {@link #getPreInstalledPluginsPath()} (to remove in IDEA 14) */
475   @SuppressWarnings({"UnusedDeclaration", "MethodNamesDifferingOnlyByCase", "SpellCheckingInspection"})
476   public static String getPreinstalledPluginsPath() {
477     return getPreInstalledPluginsPath();
478   }
479
480   /** @deprecated use {@link #getConfigPath()} (to remove in IDEA 14) */
481   @SuppressWarnings("UnusedDeclaration")
482   public static String getConfigPath(boolean createIfNotExists) {
483     ensureConfigFolderExists();
484     return ourConfigPath;
485   }
486
487   /** @deprecated use {@link #ensureConfigFolderExists()} (to remove in IDEA 14) */
488   @SuppressWarnings("UnusedDeclaration")
489   public static boolean ensureConfigFolderExists(boolean createIfNotExists) {
490     return checkAndCreate(getConfigPath(), createIfNotExists);
491   }
492
493   /** @deprecated use {@link #getOptionsPath()} (to remove in IDEA 14) */
494   @SuppressWarnings("UnusedDeclaration")
495   public static String getOptionsPathWithoutDialog() {
496     return getOptionsPath();
497   }
498
499   /** @deprecated use {@link #getOptionsFile(String)} (to remove in IDEA 14) */
500   @SuppressWarnings("UnusedDeclaration")
501   public static File getDefaultOptionsFile() {
502     return new File(getOptionsPath(), DEFAULT_OPTIONS_FILE_NAME + ".xml");
503   }
504 }