2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.application;
18 import com.intellij.openapi.util.*;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.util.io.URLUtil;
22 import com.sun.jna.TypeMapper;
23 import com.sun.jna.platform.FileUtils;
24 import gnu.trove.THashSet;
25 import org.apache.log4j.Appender;
26 import org.apache.oro.text.regex.PatternMatcher;
27 import org.jdom.Document;
28 import org.jetbrains.annotations.Contract;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31 import org.picocontainer.PicoContainer;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
39 import static com.intellij.util.SystemProperties.getUserHome;
41 public class PathManager {
42 public static final String PROPERTIES_FILE = "idea.properties.file";
43 public static final String PROPERTY_HOME_PATH = "idea.home.path";
44 public static final String PROPERTY_CONFIG_PATH = "idea.config.path";
45 public static final String PROPERTY_SYSTEM_PATH = "idea.system.path";
46 public static final String PROPERTY_PLUGINS_PATH = "idea.plugins.path";
47 public static final String PROPERTY_LOG_PATH = "idea.log.path";
48 public static final String PROPERTY_PATHS_SELECTOR = "idea.paths.selector";
49 public static final String DEFAULT_OPTIONS_FILE_NAME = "other";
51 private static final String PROPERTY_HOME = "idea.home"; // reduced variant of PROPERTY_HOME_PATH, now deprecated
53 private static final String LIB_FOLDER = "lib";
54 private static final String PLUGINS_FOLDER = "plugins";
55 private static final String BIN_FOLDER = "bin";
56 private static final String LOG_DIRECTORY = "log";
57 private static final String CONFIG_FOLDER = "config";
58 private static final String OPTIONS_FOLDER = "options";
59 private static final String SYSTEM_FOLDER = "system";
60 private static final String PATHS_SELECTOR = System.getProperty(PROPERTY_PATHS_SELECTOR);
62 private static final Pattern PROPERTY_REF = Pattern.compile("\\$\\{(.+?)}");
64 private static String ourHomePath;
65 private static String ourConfigPath;
66 private static String ourSystemPath;
67 private static String ourPluginsPath;
68 private static String ourLogPath;
70 // IDE installation paths
73 public static String getHomePath() {
74 if (ourHomePath != null) return ourHomePath;
76 String fromProperty = System.getProperty(PROPERTY_HOME_PATH, System.getProperty(PROPERTY_HOME));
77 if (fromProperty != null) {
78 ourHomePath = getAbsolutePath(fromProperty);
79 if (!new File(ourHomePath).isDirectory()) {
80 throw new RuntimeException("Invalid home path '" + ourHomePath + "'");
84 ourHomePath = getHomePathFor(PathManager.class);
85 if (ourHomePath == null) {
86 String advice = SystemInfo.isMac ? "reinstall the software."
87 : "make sure bin/idea.properties is present in the installation directory.";
88 throw new RuntimeException("Could not find installation home path. Please " + advice);
92 if (SystemInfo.isWindows) {
94 ourHomePath = new File(ourHomePath).getCanonicalPath();
96 catch (IOException ignored) { }
103 public static String getHomePathFor(@NotNull Class aClass) {
104 String rootPath = getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
105 if (rootPath == null) return null;
107 File root = new File(rootPath).getAbsoluteFile();
109 String parent = root.getParent();
110 if (parent == null) return null;
111 root = new File(parent).getAbsoluteFile(); // one step back to get folder
113 while (!isIdeaHome(root));
114 return root.getAbsolutePath();
117 private static boolean isIdeaHome(final File root) {
118 return new File(root, FileUtil.toSystemDependentName("bin/idea.properties")).exists() ||
119 new File(root, FileUtil.toSystemDependentName("community/bin/idea.properties")).exists();
123 public static String getBinPath() {
124 return getHomePath() + File.separator + BIN_FOLDER;
128 public static String getLibPath() {
129 return getHomePath() + File.separator + LIB_FOLDER;
132 @SuppressWarnings("MethodNamesDifferingOnlyByCase")
134 public static String getPreInstalledPluginsPath() {
135 return getHomePath() + File.separatorChar + PLUGINS_FOLDER;
141 public static String getPathsSelector() {
142 return PATHS_SELECTOR;
146 public static String getConfigPath() {
147 if (ourConfigPath != null) return ourConfigPath;
149 if (System.getProperty(PROPERTY_CONFIG_PATH) != null) {
150 ourConfigPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_CONFIG_PATH)));
152 else if (PATHS_SELECTOR != null) {
153 ourConfigPath = getDefaultConfigPathFor(PATHS_SELECTOR);
156 ourConfigPath = getHomePath() + File.separator + CONFIG_FOLDER;
159 return ourConfigPath;
163 public static String getDefaultConfigPathFor(@NotNull String selector) {
164 return platformPath(selector, "Library/Preferences", CONFIG_FOLDER);
167 public static void ensureConfigFolderExists() {
168 checkAndCreate(getConfigPath(), true);
172 public static String getOptionsPath() {
173 return getConfigPath() + File.separator + OPTIONS_FOLDER;
177 public static File getOptionsFile(@NotNull String fileName) {
178 return new File(getOptionsPath(), fileName + ".xml");
182 public static File getOptionsFile(@NotNull NamedJDOMExternalizable externalizable) {
183 return getOptionsFile(externalizable.getExternalFileName());
187 public static String getPluginsPath() {
188 if (ourPluginsPath != null) return ourPluginsPath;
190 if (System.getProperty(PROPERTY_PLUGINS_PATH) != null) {
191 ourPluginsPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_PLUGINS_PATH)));
193 else if (SystemInfo.isMac && PATHS_SELECTOR != null) {
194 ourPluginsPath = getUserHome() + File.separator + "Library/Application Support" + File.separator + PATHS_SELECTOR;
197 ourPluginsPath = getConfigPath() + File.separatorChar + PLUGINS_FOLDER;
200 return ourPluginsPath;
204 public static String getDefaultPluginPathFor(@NotNull String selector) {
205 return platformPath(selector, "Library/Application Support", PLUGINS_FOLDER);
211 public static String getSystemPath() {
212 if (ourSystemPath != null) return ourSystemPath;
214 if (System.getProperty(PROPERTY_SYSTEM_PATH) != null) {
215 ourSystemPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_SYSTEM_PATH)));
217 else if (PATHS_SELECTOR != null) {
218 ourSystemPath = platformPath(PATHS_SELECTOR, "Library/Caches", SYSTEM_FOLDER);
221 ourSystemPath = getHomePath() + File.separator + SYSTEM_FOLDER;
224 checkAndCreate(ourSystemPath, true);
225 return ourSystemPath;
229 public static String getTempPath() {
230 return getSystemPath() + File.separator + "tmp";
234 public static File getIndexRoot() {
235 String indexRoot = System.getProperty("index_root_path", getSystemPath() + "/index");
236 checkAndCreate(indexRoot, true);
237 return new File(indexRoot);
241 public static String getLogPath() {
242 if (ourLogPath != null) return ourLogPath;
244 if (System.getProperty(PROPERTY_LOG_PATH) != null) {
245 ourLogPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_LOG_PATH)));
247 else if (SystemInfo.isMac && PATHS_SELECTOR != null) {
248 ourLogPath = getUserHome() + File.separator + "Library/Logs" + File.separator + PATHS_SELECTOR;
251 ourLogPath = getSystemPath() + File.separatorChar + LOG_DIRECTORY;
258 public static String getPluginTempPath () {
259 return getSystemPath() + File.separator + PLUGINS_FOLDER;
265 * Attempts to detect classpath entry which contains given resource.
268 public static String getResourceRoot(@NotNull Class context, String path) {
269 URL url = context.getResource(path);
271 url = ClassLoader.getSystemResource(path.substring(1));
273 return url != null ? extractRoot(url, path) : null;
277 * Attempts to extract classpath entry part from passed URL.
280 private static String extractRoot(URL resourceURL, String resourcePath) {
281 if (!(StringUtil.startsWithChar(resourcePath, '/') || StringUtil.startsWithChar(resourcePath, '\\'))) {
282 log("precondition failed: " + resourcePath);
286 String resultPath = null;
287 String protocol = resourceURL.getProtocol();
288 if (URLUtil.FILE_PROTOCOL.equals(protocol)) {
289 String path = resourceURL.getFile();
290 String testPath = path.replace('\\', '/');
291 String testResourcePath = resourcePath.replace('\\', '/');
292 if (StringUtil.endsWithIgnoreCase(testPath, testResourcePath)) {
293 resultPath = path.substring(0, path.length() - resourcePath.length());
296 else if (URLUtil.JAR_PROTOCOL.equals(protocol)) {
297 Pair<String, String> paths = URLUtil.splitJarUrl(resourceURL.getFile());
299 resultPath = paths.first;
303 if (resultPath == null) {
304 log("cannot extract: " + resourcePath + " from " + resourceURL);
308 resultPath = StringUtil.trimEnd(resultPath, File.separator);
309 resultPath = URLUtil.unescapePercentSequences(resultPath);
314 public static void loadProperties() {
315 String[] propFiles = {
316 System.getProperty(PROPERTIES_FILE),
317 getUserPropertiesPath(),
318 getHomePath() + "/bin/idea.properties",
319 getHomePath() + "/community/bin/idea.properties"};
321 for (String path : propFiles) {
323 File propFile = new File(path);
324 if (propFile.exists()) {
326 Reader fis = new BufferedReader(new FileReader(propFile));
328 Map<String, String> properties = FileUtil.loadProperties(fis);
330 Properties sysProperties = System.getProperties();
331 for (String key : properties.keySet()) {
332 if (PROPERTY_HOME_PATH.equals(key) || PROPERTY_HOME.equals(key)) {
333 log(propFile.getPath() + ": '" + PROPERTY_HOME_PATH + "' and '" + PROPERTY_HOME + "' properties cannot be redefined");
335 else if (sysProperties.getProperty(key, null) != null) {
336 log(propFile.getPath() + ": '" + key + "' already defined");
339 String value = substituteVars(properties.get(key));
340 sysProperties.setProperty(key, value);
348 catch (IOException e) {
349 log("Problem reading from property file: " + propFile.getPath());
356 private static String getUserPropertiesPath() {
357 if (PATHS_SELECTOR != null) {
358 // do not use getConfigPath() here - as it may be not yet defined
359 return platformPath(PATHS_SELECTOR, "Library/Preferences", /*"APPDATA", "XDG_CONFIG_HOME", ".config",*/ "") + "/idea.properties";
362 return getUserHome() + "/idea.properties";
366 @Contract("null -> null")
367 public static String substituteVars(String s) {
368 return substituteVars(s, getHomePath());
371 @Contract("null, _ -> null")
372 public static String substituteVars(String s, String ideaHomePath) {
373 if (s == null) return null;
375 if (s.startsWith("..")) {
376 s = ideaHomePath + File.separatorChar + BIN_FOLDER + File.separatorChar + s;
379 Matcher m = PROPERTY_REF.matcher(s);
381 String key = m.group(1);
382 String value = System.getProperty(key);
385 if (PROPERTY_HOME_PATH.equals(key) || PROPERTY_HOME.equals(key)) {
386 value = ideaHomePath;
388 else if (PROPERTY_CONFIG_PATH.equals(key)) {
389 value = getConfigPath();
391 else if (PROPERTY_SYSTEM_PATH.equals(key)) {
392 value = getSystemPath();
397 log("Unknown property: " + key);
401 s = StringUtil.replace(s, m.group(), value);
402 m = PROPERTY_REF.matcher(s);
409 public static File findFileInLibDirectory(@NotNull String relativePath) {
410 File file = new File(getLibPath() + File.separator + relativePath);
411 return file.exists() ? file : new File(getHomePath(), "community" + File.separator + "lib" + File.separator + relativePath);
415 public static String getJarPathForClass(@NotNull Class aClass) {
416 String resourceRoot = getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
417 return resourceRoot != null ? new File(resourceRoot).getAbsolutePath() : null;
421 public static Collection<String> getUtilClassPath() {
422 final Class<?>[] classes = {
423 PathManager.class, // module 'util'
424 NotNull.class, // module 'annotations'
425 SystemInfoRt.class, // module 'util-rt'
426 Document.class, // jDOM
427 Appender.class, // log4j
428 THashSet.class, // trove4j
429 PicoContainer.class, // PicoContainer
430 TypeMapper.class, // JNA
431 FileUtils.class, // JNA (jna-utils)
432 PatternMatcher.class // OROMatcher
435 final Set<String> classPath = new HashSet<String>();
436 for (Class<?> aClass : classes) {
437 final String path = getJarPathForClass(aClass);
443 final String resourceRoot = getResourceRoot(PathManager.class, "/messages/CommonBundle.properties"); // platform-resources-en
444 if (resourceRoot != null) {
445 classPath.add(new File(resourceRoot).getAbsolutePath());
448 return Collections.unmodifiableCollection(classPath);
453 @SuppressWarnings("UseOfSystemOutOrSystemErr")
454 private static void log(String x) {
455 System.err.println(x);
458 private static String getAbsolutePath(String path) {
459 path = FileUtil.expandUserHome(path);
460 return FileUtil.toCanonicalPath(new File(path).getAbsolutePath());
463 private static String trimPathQuotes(String path){
464 if (!(path != null && !(path.length() < 3))){
467 if (StringUtil.startsWithChar(path, '\"') && StringUtil.endsWithChar(path, '\"')){
468 return path.substring(1, path.length() - 1);
473 // todo[r.sh] XDG directories, Windows folders
474 // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
475 // http://www.microsoft.com/security/portal/mmpc/shared/variables.aspx
476 private static String platformPath(@NotNull String selector, @Nullable String macPart, @NotNull String fallback) {
477 return platformPath(selector, macPart, null, null, null, fallback);
480 private static String platformPath(@NotNull String selector,
481 @Nullable String macPart,
482 @Nullable String winVar,
483 @Nullable String xdgVar,
484 @Nullable String xdgDir,
485 @NotNull String fallback) {
486 if (macPart != null && SystemInfo.isMac) {
487 return getUserHome() + File.separator + macPart + File.separator + selector;
490 if (winVar != null && SystemInfo.isWindows) {
491 String dir = System.getenv(winVar);
493 return dir + File.separator + selector;
497 if (xdgVar != null && xdgDir != null && SystemInfo.hasXdgOpen()) {
498 String dir = System.getenv(xdgVar);
499 if (dir == null) dir = getUserHome() + File.separator + xdgDir;
500 return dir + File.separator + selector;
503 return getUserHome() + File.separator + "." + selector + (!fallback.isEmpty() ? File.separator + fallback : "");
506 private static boolean checkAndCreate(String path, boolean createIfNotExists) {
507 if (createIfNotExists) {
508 File file = new File(path);
509 if (!file.exists()) {
510 return file.mkdirs();
518 /** @deprecated use {@link #getPluginsPath()} (to remove in IDEA 14) */
519 @SuppressWarnings("UnusedDeclaration") public static final String PLUGINS_DIRECTORY = PLUGINS_FOLDER;
521 /** @deprecated use {@link #getPreInstalledPluginsPath()} (to remove in IDEA 14) */
522 @SuppressWarnings({"UnusedDeclaration", "MethodNamesDifferingOnlyByCase", "SpellCheckingInspection"})
523 public static String getPreinstalledPluginsPath() {
524 return getPreInstalledPluginsPath();
527 /** @deprecated use {@link #getConfigPath()} (to remove in IDEA 14) */
528 @SuppressWarnings("UnusedDeclaration")
529 public static String getConfigPath(boolean createIfNotExists) {
530 ensureConfigFolderExists();
531 return ourConfigPath;
534 /** @deprecated use {@link #ensureConfigFolderExists()} (to remove in IDEA 14) */
535 @SuppressWarnings("UnusedDeclaration")
536 public static boolean ensureConfigFolderExists(boolean createIfNotExists) {
537 return checkAndCreate(getConfigPath(), createIfNotExists);
540 /** @deprecated use {@link #getOptionsPath()} (to remove in IDEA 14) */
541 @SuppressWarnings("UnusedDeclaration")
542 public static String getOptionsPathWithoutDialog() {
543 return getOptionsPath();
546 /** @deprecated use {@link #getOptionsFile(String)} (to remove in IDEA 14) */
547 @SuppressWarnings("UnusedDeclaration")
548 public static File getDefaultOptionsFile() {
549 return new File(getOptionsPath(), DEFAULT_OPTIONS_FILE_NAME + ".xml");