IDEADEV-41386: When configuring Java SDK, resolve symbolic links and remove duplicate...
[fedora-idea.git] / java / java-impl / src / com / intellij / openapi / projectRoots / impl / JavaSdkImpl.java
blobf44bd5635196648a505369889c9987fa2edd638f
1 /*
2 * Copyright 2000-2009 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.projectRoots.impl;
18 import com.intellij.openapi.application.PathManager;
19 import com.intellij.openapi.project.ProjectBundle;
20 import com.intellij.openapi.projectRoots.*;
21 import com.intellij.openapi.roots.JavadocOrderRootType;
22 import com.intellij.openapi.roots.OrderRootType;
23 import com.intellij.openapi.util.IconLoader;
24 import com.intellij.openapi.util.SystemInfo;
25 import com.intellij.openapi.vfs.*;
26 import com.intellij.util.containers.HashMap;
27 import org.jdom.Element;
28 import org.jetbrains.annotations.NonNls;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
32 import javax.swing.*;
33 import java.io.File;
34 import java.io.FileFilter;
35 import java.io.IOException;
36 import java.util.*;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
40 /**
41 * @author Eugene Zhuravlev
42 * Date: Sep 17, 2004
44 public class JavaSdkImpl extends JavaSdk {
45 // do not use javaw.exe for Windows because of issues with encoding
46 @NonNls private static final String VM_EXE_NAME = "java";
47 @NonNls private final Pattern myVersionStringPattern = Pattern.compile("^(.*)java version \"([1234567890_.]*)\"(.*)$");
48 public static final Icon ICON = IconLoader.getIcon("/nodes/ppJdkClosed.png");
49 private static final Icon JDK_ICON_EXPANDED = IconLoader.getIcon("/nodes/ppJdkOpen.png");
50 private static final Icon ADD_ICON = IconLoader.getIcon("/general/addJdk.png");
51 @NonNls private static final String JAVA_VERSION_PREFIX = "java version ";
53 public JavaSdkImpl() {
54 super("JavaSDK");
57 public String getPresentableName() {
58 return ProjectBundle.message("sdk.java.name");
61 public Icon getIcon() {
62 return ICON;
65 public Icon getIconForExpandedTreeNode() {
66 return JDK_ICON_EXPANDED;
69 public Icon getIconForAddAction() {
70 return ADD_ICON;
73 public AdditionalDataConfigurable createAdditionalDataConfigurable(SdkModel sdkModel, SdkModificator sdkModificator) {
74 return null;
77 public void saveAdditionalData(SdkAdditionalData additionalData, Element additional) {
80 public SdkAdditionalData loadAdditionalData(Element additional) {
81 return null;
84 @SuppressWarnings({"HardCodedStringLiteral"})
85 public String getBinPath(Sdk sdk) {
86 return getConvertedHomePath(sdk) + "bin";
89 @NonNls
90 public String getToolsPath(Sdk sdk) {
91 final String versionString = sdk.getVersionString();
92 final boolean isJdk1_x = versionString != null && (versionString.contains("1.0") || versionString.contains("1.1"));
93 return getConvertedHomePath(sdk) + "lib" + File.separator + (isJdk1_x? "classes.zip" : "tools.jar");
96 public String getVMExecutablePath(Sdk sdk) {
98 if ("64".equals(System.getProperty("sun.arch.data.model"))) {
99 return getBinPath(sdk) + File.separator + System.getProperty("os.arch") + File.separator + VM_EXE_NAME;
102 return getBinPath(sdk) + File.separator + VM_EXE_NAME;
105 private static String getConvertedHomePath(Sdk sdk) {
106 String path = sdk.getHomePath().replace('/', File.separatorChar);
107 if (!path.endsWith(File.separator)) {
108 path += File.separator;
110 return path;
113 @SuppressWarnings({"HardCodedStringLiteral"})
114 public String suggestHomePath() {
115 if (SystemInfo.isMac) {
116 return "/System/Library/Frameworks/JavaVM.framework/Versions/";
118 return null;
121 public boolean isValidSdkHome(String path) {
122 return checkForJdk(new File(path));
125 public String suggestSdkName(String currentSdkName, String sdkHome) {
126 final String suggestedName;
127 if (currentSdkName != null && currentSdkName.length() > 0) {
128 final Matcher matcher = myVersionStringPattern.matcher(currentSdkName);
129 final boolean replaceNameWithVersion = matcher.matches();
130 if (replaceNameWithVersion){
131 // user did not change name -> set it automatically
132 final String versionString = getVersionString(sdkHome);
133 suggestedName = versionString == null ? currentSdkName : matcher.replaceFirst("$1" + versionString + "$3");
135 else {
136 suggestedName = currentSdkName;
139 else {
140 String versionString = getVersionString(sdkHome);
141 if (versionString != null) {
142 suggestedName = getVersionNumber(versionString);
143 } else {
144 suggestedName = ProjectBundle.message("sdk.java.unknown.name");
147 return suggestedName;
150 private static String getVersionNumber(String versionString) {
151 if (versionString.startsWith(JAVA_VERSION_PREFIX)) {
152 versionString = versionString.substring(JAVA_VERSION_PREFIX.length());
153 if (versionString.startsWith("\"") && versionString.endsWith("\"")) {
154 versionString = versionString.substring(1, versionString.length() - 1);
156 int dotIdx = versionString.indexOf('.');
157 if (dotIdx > 0) {
158 try {
159 int major = Integer.parseInt(versionString.substring(0, dotIdx));
160 int minorDot = versionString.indexOf('.', dotIdx + 1);
161 if (minorDot > 0) {
162 int minor = Integer.parseInt(versionString.substring(dotIdx + 1, minorDot));
163 versionString = String.valueOf(major) + "." + String.valueOf(minor);
166 catch (NumberFormatException e) {
167 // Do nothing. Use original version string if failed to parse according to major.minor pattern.
171 return versionString;
174 @SuppressWarnings({"HardCodedStringLiteral"})
175 public void setupSdkPaths(Sdk sdk) {
176 final File jdkHome = new File(sdk.getHomePath());
177 VirtualFile[] classes = findClasses(jdkHome, false);
178 VirtualFile sources = findSources(jdkHome);
179 VirtualFile docs = findDocs(jdkHome, "docs/api");
181 final SdkModificator sdkModificator = sdk.getSdkModificator();
182 final Set<VirtualFile> previousRoots = new LinkedHashSet<VirtualFile>(Arrays.asList(sdkModificator.getRoots(OrderRootType.CLASSES)));
183 sdkModificator.removeRoots(OrderRootType.CLASSES);
184 previousRoots.removeAll(new HashSet<VirtualFile>(Arrays.asList(classes)));
185 for (VirtualFile aClass : classes) {
186 sdkModificator.addRoot(aClass, OrderRootType.CLASSES);
188 for (VirtualFile root : previousRoots) {
189 sdkModificator.addRoot(root, OrderRootType.CLASSES);
191 if(sources != null){
192 sdkModificator.addRoot(sources, OrderRootType.SOURCES);
194 if(docs != null){
195 sdkModificator.addRoot(docs, JavadocOrderRootType.getInstance());
197 else if (SystemInfo.isMac) {
198 VirtualFile commonDocs = findDocs(jdkHome, "docs");
199 if (commonDocs == null) {
200 commonDocs = findInJar(new File(jdkHome, "docs.jar"), "doc/api");
202 if (commonDocs != null) {
203 sdkModificator.addRoot(commonDocs, JavadocOrderRootType.getInstance());
206 VirtualFile appleDocs = findDocs(jdkHome, "appledocs");
207 if (appleDocs == null) {
208 appleDocs = findInJar(new File(jdkHome, "appledocs.jar"), "appledoc/api");
210 if (appleDocs != null) {
211 sdkModificator.addRoot(appleDocs, JavadocOrderRootType.getInstance());
214 sdkModificator.commitChanges();
217 private final Map<String, String> myCachedVersionStrings = new HashMap<String, String>();
219 public final String getVersionString(final String sdkHome) {
220 String versionString;
222 if(myCachedVersionStrings.containsKey(sdkHome)) {
223 return myCachedVersionStrings.get(sdkHome);
224 } else {
225 versionString = getJdkVersion(sdkHome);
227 if (versionString != null && versionString.length() == 0) {
228 versionString = null;
231 if (versionString != null){
232 myCachedVersionStrings.put(sdkHome, versionString);
235 return versionString;
238 @NotNull
239 public String getComponentName() {
240 return getName();
243 public void initComponent() { }
245 public void disposeComponent() {
248 public int compareTo(@NotNull String versionString, @NotNull String versionNumber) {
249 return getVersionNumber(versionString).compareTo(versionNumber);
252 public Sdk createJdk(final String jdkName, final String home, final boolean isJre) {
253 ProjectJdkImpl jdk = new ProjectJdkImpl(jdkName, this);
254 SdkModificator sdkModificator = jdk.getSdkModificator();
256 String path = home.replace(File.separatorChar, '/');
257 sdkModificator.setHomePath(path);
258 jdk.setVersionString(jdkName); // must be set after home path, otherwise setting home path clears the version string
260 File jdkHomeFile = new File(home);
261 addClasses(jdkHomeFile, sdkModificator, isJre);
262 addSources(jdkHomeFile, sdkModificator);
263 addDocs(jdkHomeFile, sdkModificator);
264 sdkModificator.commitChanges();
265 return jdk;
268 public static Sdk getMockJdk(@NonNls String versionName) {
269 File mockJdkCEPath = new File(PathManager.getHomePath(), "java/mockJDK");
270 if (mockJdkCEPath.exists()) {
271 return createMockJdk(mockJdkCEPath.getPath(), versionName, getInstance());
273 final String forcedPath = System.getProperty("idea.testingFramework.mockJDK");
274 String jdkHome = forcedPath != null ? forcedPath : PathManager.getHomePath() + File.separator + "mockJDK";
275 return createMockJdk(jdkHome, versionName, getInstance());
278 public static Sdk getMockJdk15(@NonNls String versionName) {
279 File mockJdkCEPath = new File(PathManager.getHomePath(), "java/mockJDK");
280 if (mockJdkCEPath.exists()) {
281 return createMockJdk(mockJdkCEPath.getPath(), versionName, getInstance());
283 String jdkHome = PathManager.getHomePath() + File.separator + "mockJDK-1.5";
284 return createMockJdk(jdkHome, versionName, getInstance());
287 public static Sdk getMockJdk17(@NonNls String versionName) {
288 String jdkHome = PathManager.getHomePath() + File.separator + "mockJDK-1.7";
289 return createMockJdk(jdkHome, versionName, getInstance());
292 private static Sdk createMockJdk(String jdkHome, final String versionName, JavaSdk javaSdk) {
293 File jdkHomeFile = new File(jdkHome);
294 if (!jdkHomeFile.exists()) return null;
296 final Sdk jdk = new ProjectJdkImpl(versionName, javaSdk);
297 final SdkModificator sdkModificator = jdk.getSdkModificator();
299 String path = jdkHome.replace(File.separatorChar, '/');
300 sdkModificator.setHomePath(path);
301 sdkModificator.setVersionString(versionName); // must be set after home path, otherwise setting home path clears the version string
303 addSources(jdkHomeFile, sdkModificator);
304 addClasses(jdkHomeFile, sdkModificator, false);
305 addClasses(jdkHomeFile, sdkModificator, true);
306 sdkModificator.commitChanges();
308 return jdk;
311 private static void addClasses(File file, SdkModificator sdkModificator, final boolean isJre) {
312 VirtualFile[] classes = findClasses(file, isJre);
313 for (VirtualFile virtualFile : classes) {
314 sdkModificator.addRoot(virtualFile, OrderRootType.CLASSES);
318 private static VirtualFile[] findClasses(File file, boolean isJre) {
319 FileFilter jarFileFilter = new FileFilter(){
320 @SuppressWarnings({"HardCodedStringLiteral"})
321 public boolean accept(File f){
322 if (f.isDirectory()) return false;
323 if (f.getName().endsWith(".jar")) return true;
324 return false;
328 File[] jarDirs;
329 if(SystemInfo.isMac && /*!ApplicationManager.getApplication().isUnitTestMode()) &&*/ !file.getName().startsWith("mockJDK")){
330 File libFile = new File(file, "lib");
331 @NonNls File classesFile = new File(file, "../Classes");
332 @NonNls File libExtFile = new File(libFile, "ext");
333 @NonNls File libEndorsedFile = new File(libFile, "endorsed");
334 jarDirs = new File[]{libEndorsedFile, libFile, classesFile, libExtFile};
336 else{
337 @NonNls final String jre = "jre";
338 File jreLibFile = isJre ? new File(file, "lib") : new File(new File(file, jre), "lib");
339 @NonNls File jreLibExtFile = new File(jreLibFile, "ext");
340 @NonNls File jreLibEndorsedFile = new File(jreLibFile, "endorsed");
341 jarDirs = new File[]{jreLibEndorsedFile, jreLibFile, jreLibExtFile};
344 Set<File> childrenSet = new LinkedHashSet<File>();
345 for (File jarDir : jarDirs) {
346 if (jarDir != null && jarDir.isDirectory()) {
347 File[] jarFiles = jarDir.listFiles(jarFileFilter);
348 for (File jarFile : jarFiles) {
349 try {
350 // File.getCanonicalFile() allows us to filter out duplicate (symbolically linked) jar files,
351 // commonly found in osx JDK distributions
352 childrenSet.add(jarFile.getCanonicalFile());
354 catch (IOException e) {
355 // Symbolic links may fail to resolve. Just skip those jars as we won't be able to find virtual file in this case anyway.
361 ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
362 for (File child : childrenSet) {
363 String url = JarFileSystem.PROTOCOL_PREFIX + child.getAbsolutePath().replace(File.separatorChar, '/') + JarFileSystem.JAR_SEPARATOR;
364 VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(url);
365 if (vFile != null) {
366 result.add(vFile);
370 @NonNls File classesZipFile = new File(new File(file, "lib"), "classes.zip");
371 if(!classesZipFile.isDirectory() && classesZipFile.exists()){
372 String url =
373 JarFileSystem.PROTOCOL_PREFIX + classesZipFile.getAbsolutePath().replace(File.separatorChar, '/') + JarFileSystem.JAR_SEPARATOR;
374 VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(url);
375 if (vFile != null){
376 result.add(vFile);
380 return VfsUtil.toVirtualFileArray(result);
383 private static void addSources(File file, SdkModificator sdkModificator) {
384 VirtualFile vFile = findSources(file);
385 if (vFile != null) {
386 sdkModificator.addRoot(vFile, OrderRootType.SOURCES);
390 @Nullable
391 @SuppressWarnings({"HardCodedStringLiteral"})
392 public static VirtualFile findSources(File file) {
393 File srcfile = new File(file, "src");
394 File jarfile = new File(file, "src.jar");
395 if (!jarfile.exists()) {
396 jarfile = new File(file, "src.zip");
399 if (jarfile.exists()) {
400 VirtualFile vFile = findInJar(jarfile, "src");
401 if (vFile != null) return vFile;
402 // try 1.4 format
403 vFile = findInJar(jarfile, "");
404 return vFile;
406 else {
407 if (!srcfile.exists() || !srcfile.isDirectory()) return null;
408 String path = srcfile.getAbsolutePath().replace(File.separatorChar, '/');
409 return LocalFileSystem.getInstance().findFileByPath(path);
413 @SuppressWarnings({"HardCodedStringLiteral"})
414 private static void addDocs(File file, SdkModificator rootContainer) {
415 VirtualFile vFile = findDocs(file, "docs/api");
416 if (vFile != null) {
417 rootContainer.addRoot(vFile, JavadocOrderRootType.getInstance());
421 @Nullable
422 private static VirtualFile findInJar(File jarFile, String relativePath) {
423 if (!jarFile.exists()) return null;
424 String url = JarFileSystem.PROTOCOL_PREFIX +
425 jarFile.getAbsolutePath().replace(File.separatorChar, '/') + JarFileSystem.JAR_SEPARATOR + relativePath;
426 return VirtualFileManager.getInstance().findFileByUrl(url);
429 @Nullable
430 public static VirtualFile findDocs(File file, final String relativePath) {
431 file = new File(file.getAbsolutePath() + File.separator + relativePath.replace('/', File.separatorChar));
432 if (!file.exists() || !file.isDirectory()) return null;
433 String path = file.getAbsolutePath().replace(File.separatorChar, '/');
434 return LocalFileSystem.getInstance().findFileByPath(path);