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.projectRoots.impl;
18 import com.intellij.execution.configurations.GeneralCommandLine;
19 import com.intellij.execution.util.ExecUtil;
20 import com.intellij.icons.AllIcons;
21 import com.intellij.lang.LangBundle;
22 import com.intellij.openapi.actionSystem.DataKey;
23 import com.intellij.openapi.application.PathManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
26 import com.intellij.openapi.project.ProjectBundle;
27 import com.intellij.openapi.projectRoots.*;
28 import com.intellij.openapi.roots.AnnotationOrderRootType;
29 import com.intellij.openapi.roots.JavadocOrderRootType;
30 import com.intellij.openapi.roots.OrderRootType;
31 import com.intellij.openapi.util.SystemInfo;
32 import com.intellij.openapi.util.io.FileUtil;
33 import com.intellij.openapi.util.text.StringUtil;
34 import com.intellij.openapi.vfs.*;
35 import com.intellij.openapi.vfs.impl.jrt.JrtFileSystem;
36 import com.intellij.util.Function;
37 import com.intellij.util.containers.ContainerUtil;
38 import com.intellij.util.containers.HashMap;
39 import org.jdom.Element;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42 import org.jetbrains.jps.model.java.impl.JavaSdkUtil;
46 import java.io.FileFilter;
48 import java.util.regex.Matcher;
49 import java.util.regex.Pattern;
52 * @author Eugene Zhuravlev
55 public class JavaSdkImpl extends JavaSdk {
56 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.impl.JavaSdkImpl");
58 public static final DataKey<Boolean> KEY = DataKey.create("JavaSdk");
60 private static final String VM_EXE_NAME = "java"; // do not use JavaW.exe for Windows because of issues with encoding
61 private static final Pattern VERSION_STRING_PATTERN = Pattern.compile("^(.*)java version \"([1234567890_.]*)\"(.*)$");
62 private static final String JAVA_VERSION_PREFIX = "java version ";
63 private static final String OPENJDK_VERSION_PREFIX = "openjdk version ";
65 public JavaSdkImpl() {
70 public String getPresentableName() {
71 return ProjectBundle.message("sdk.java.name");
75 public Icon getIcon() {
76 return AllIcons.Nodes.PpJdk;
81 public String getHelpTopic() {
82 return "reference.project.structure.sdk.java";
86 public Icon getIconForAddAction() {
87 return AllIcons.General.AddJdk;
92 public String getDefaultDocumentationUrl(@NotNull final Sdk sdk) {
93 final JavaSdkVersion version = getVersion(sdk);
94 if (version == JavaSdkVersion.JDK_1_5) {
95 return "http://docs.oracle.com/javase/1.5.0/docs/api/";
97 if (version == JavaSdkVersion.JDK_1_6) {
98 return "http://docs.oracle.com/javase/6/docs/api/";
100 if (version == JavaSdkVersion.JDK_1_7) {
101 return "http://docs.oracle.com/javase/7/docs/api/";
103 if (version == JavaSdkVersion.JDK_1_8) {
104 return "http://docs.oracle.com/javase/8/docs/api";
110 public AdditionalDataConfigurable createAdditionalDataConfigurable(SdkModel sdkModel, SdkModificator sdkModificator) {
115 public void saveAdditionalData(@NotNull SdkAdditionalData additionalData, @NotNull Element additional) {
119 @SuppressWarnings({"HardCodedStringLiteral"})
120 public String getBinPath(@NotNull Sdk sdk) {
121 return getConvertedHomePath(sdk) + "bin";
125 public String getToolsPath(@NotNull Sdk sdk) {
126 final String versionString = sdk.getVersionString();
127 final boolean isJdk1_x = versionString != null && (versionString.contains("1.0") || versionString.contains("1.1"));
128 return getConvertedHomePath(sdk) + "lib" + File.separator + (isJdk1_x? "classes.zip" : "tools.jar");
132 public String getVMExecutablePath(@NotNull Sdk sdk) {
133 return getBinPath(sdk) + File.separator + VM_EXE_NAME;
136 private static String getConvertedHomePath(Sdk sdk) {
137 String homePath = sdk.getHomePath();
138 assert homePath != null : sdk;
139 String path = FileUtil.toSystemDependentName(homePath);
140 if (!path.endsWith(File.separator)) {
141 path += File.separator;
147 @SuppressWarnings({"HardCodedStringLiteral"})
148 public String suggestHomePath() {
149 if (SystemInfo.isMac) {
150 if (new File("/usr/libexec/java_home").canExecute()) {
151 String path = ExecUtil.execAndReadLine(new GeneralCommandLine("/usr/libexec/java_home"));
152 if (path != null && new File(path).exists()) {
156 return "/System/Library/Frameworks/JavaVM.framework/Versions";
159 if (SystemInfo.isLinux) {
160 final String[] homes = {"/usr/java", "/opt/java", "/usr/lib/jvm"};
161 for (String home : homes) {
162 if (new File(home).isDirectory()) {
168 if (SystemInfo.isSolaris) {
172 if (SystemInfo.isWindows) {
173 String property = System.getProperty("java.home");
174 if (property == null) return null;
175 File javaHome = new File(property).getParentFile();//actually java.home points to to jre home
176 if (javaHome != null && JdkUtil.checkForJdk(javaHome)) {
177 return javaHome.getAbsolutePath();
186 public Collection<String> suggestHomePaths() {
187 if (!SystemInfo.isWindows)
188 return Collections.singletonList(suggestHomePath());
190 String property = System.getProperty("java.home");
191 if (property == null)
192 return Collections.emptyList();
194 File javaHome = new File(property).getParentFile();//actually java.home points to to jre home
195 if (javaHome == null || !javaHome.isDirectory() || javaHome.getParentFile() == null) {
196 return Collections.emptyList();
198 ArrayList<String> result = new ArrayList<String>();
199 File javasFolder = javaHome.getParentFile();
200 scanFolder(javasFolder, result);
201 File parentFile = javasFolder.getParentFile();
202 File root = parentFile != null ? parentFile.getParentFile() : null;
203 String name = parentFile != null ? parentFile.getName() : "";
204 if (name.contains("Program Files") && root != null) {
205 String x86Suffix = " (x86)";
206 boolean x86 = name.endsWith(x86Suffix) && name.length() > x86Suffix.length();
207 File anotherJavasFolder;
209 anotherJavasFolder = new File(root, name.substring(0, name.length() - x86Suffix.length()));
212 anotherJavasFolder = new File(root, name + x86Suffix);
214 if (anotherJavasFolder.isDirectory()) {
215 scanFolder(new File(anotherJavasFolder, javasFolder.getName()), result);
221 private static void scanFolder(File javasFolder, ArrayList<String> result) {
222 File[] candidates = javasFolder.listFiles(new FileFilter() {
224 public boolean accept(File pathname) {
225 return JdkUtil.checkForJdk(pathname);
228 if (candidates != null) {
229 result.addAll(ContainerUtil.map2List(candidates, new Function<File, String>() {
231 public String fun(File file) {
232 return file.getAbsolutePath();
239 public FileChooserDescriptor getHomeChooserDescriptor() {
240 final FileChooserDescriptor baseDescriptor = super.getHomeChooserDescriptor();
241 final FileChooserDescriptor descriptor = new FileChooserDescriptor(baseDescriptor) {
243 public void validateSelectedFiles(VirtualFile[] files) throws Exception {
244 if (files.length > 0 && JrtFileSystem.isModularJdk(files[0].getPath()) && !JrtFileSystem.isSupported()) {
245 throw new Exception(LangBundle.message("jrt.not.available.message"));
247 baseDescriptor.validateSelectedFiles(files);
250 descriptor.putUserData(KEY, Boolean.TRUE);
255 public String adjustSelectedSdkHome(String homePath) {
256 if (SystemInfo.isMac) {
257 File home = new File(homePath, "/Home");
258 if (home.exists()) return home.getPath();
260 home = new File(homePath, "Contents/Home");
261 if (home.exists()) return home.getPath();
268 public boolean isValidSdkHome(String path) {
269 if (!checkForJdk(new File(path))) {
272 if (JrtFileSystem.isModularJdk(path) && !JrtFileSystem.isSupported()) {
279 public String suggestSdkName(String currentSdkName, String sdkHome) {
280 final String suggestedName;
281 if (currentSdkName != null && !currentSdkName.isEmpty()) {
282 final Matcher matcher = VERSION_STRING_PATTERN.matcher(currentSdkName);
283 final boolean replaceNameWithVersion = matcher.matches();
284 if (replaceNameWithVersion){
285 // user did not change name -> set it automatically
286 final String versionString = getVersionString(sdkHome);
287 suggestedName = versionString == null ? currentSdkName : matcher.replaceFirst("$1" + versionString + "$3");
290 suggestedName = currentSdkName;
294 String versionString = getVersionString(sdkHome);
295 suggestedName = versionString == null ? ProjectBundle.message("sdk.java.unknown.name") : getVersionNumber(versionString);
297 return suggestedName;
301 private static String getVersionNumber(@NotNull String versionString) {
302 if (versionString.startsWith(JAVA_VERSION_PREFIX) || versionString.startsWith(OPENJDK_VERSION_PREFIX)) {
303 boolean openJdk = versionString.startsWith(OPENJDK_VERSION_PREFIX);
304 versionString = versionString.substring(openJdk ? OPENJDK_VERSION_PREFIX.length() : JAVA_VERSION_PREFIX.length());
305 if (versionString.startsWith("\"") && versionString.endsWith("\"")) {
306 versionString = versionString.substring(1, versionString.length() - 1);
308 int dotIdx = versionString.indexOf('.');
311 int major = Integer.parseInt(versionString.substring(0, dotIdx));
312 int minorDot = versionString.indexOf('.', dotIdx + 1);
314 int minor = Integer.parseInt(versionString.substring(dotIdx + 1, minorDot));
315 versionString = major + "." + minor;
318 catch (NumberFormatException e) {
319 // Do nothing. Use original version string if failed to parse according to major.minor pattern.
323 return versionString;
327 @SuppressWarnings({"HardCodedStringLiteral"})
328 public void setupSdkPaths(@NotNull Sdk sdk) {
329 String homePath = sdk.getHomePath();
330 assert homePath != null : sdk;
332 File jdkHome = new File(homePath);
333 List<VirtualFile> classes = findClasses(jdkHome, false);
334 VirtualFile sources = findSources(jdkHome);
335 VirtualFile docs = findDocs(jdkHome, "docs/api");
336 SdkModificator sdkModificator = sdk.getSdkModificator();
338 Set<VirtualFile> previousRoots = new LinkedHashSet<VirtualFile>(Arrays.asList(sdkModificator.getRoots(OrderRootType.CLASSES)));
339 sdkModificator.removeRoots(OrderRootType.CLASSES);
340 previousRoots.removeAll(new HashSet<VirtualFile>(classes));
341 for (VirtualFile aClass : classes) {
342 sdkModificator.addRoot(aClass, OrderRootType.CLASSES);
344 for (VirtualFile root : previousRoots) {
345 sdkModificator.addRoot(root, OrderRootType.CLASSES);
348 if (sources != null) {
349 sdkModificator.addRoot(sources, OrderRootType.SOURCES);
351 VirtualFile javaFxSources = findSources(jdkHome, "javafx-src");
352 if (javaFxSources != null) {
353 sdkModificator.addRoot(javaFxSources, OrderRootType.SOURCES);
357 sdkModificator.addRoot(docs, JavadocOrderRootType.getInstance());
359 else if (SystemInfo.isMac) {
360 VirtualFile commonDocs = findDocs(jdkHome, "docs");
361 if (commonDocs == null) {
362 commonDocs = findInJar(new File(jdkHome, "docs.jar"), "doc/api");
363 if (commonDocs == null) {
364 commonDocs = findInJar(new File(jdkHome, "docs.jar"), "docs/api");
367 if (commonDocs != null) {
368 sdkModificator.addRoot(commonDocs, JavadocOrderRootType.getInstance());
371 VirtualFile appleDocs = findDocs(jdkHome, "appledocs");
372 if (appleDocs == null) {
373 appleDocs = findInJar(new File(jdkHome, "appledocs.jar"), "appledoc/api");
375 if (appleDocs != null) {
376 sdkModificator.addRoot(appleDocs, JavadocOrderRootType.getInstance());
379 if (commonDocs == null && appleDocs == null && sources == null) {
380 String url = getDefaultDocumentationUrl(sdk);
382 sdkModificator.addRoot(VirtualFileManager.getInstance().findFileByUrl(url), JavadocOrderRootType.getInstance());
386 else if (getVersion(sdk) == JavaSdkVersion.JDK_1_7) {
387 VirtualFile url = VirtualFileManager.getInstance().findFileByUrl("http://docs.oracle.com/javafx/2/api/");
388 sdkModificator.addRoot(url, JavadocOrderRootType.getInstance());
391 attachJdkAnnotations(sdkModificator);
393 sdkModificator.commitChanges();
396 public static void attachJdkAnnotations(@NotNull SdkModificator modificator) {
397 LocalFileSystem lfs = LocalFileSystem.getInstance();
398 // community idea under idea
399 VirtualFile root = lfs.findFileByPath(FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/java/jdkAnnotations");
401 if (root == null) { // idea under idea
402 root = lfs.findFileByPath(FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/community/java/jdkAnnotations");
404 if (root == null) { // build
405 root = VirtualFileManager.getInstance().findFileByUrl("jar://"+ FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar!/");
408 LOG.error("jdk annotations not found in: "+ FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar!/");
412 OrderRootType annoType = AnnotationOrderRootType.getInstance();
413 modificator.removeRoot(root, annoType);
414 modificator.addRoot(root, annoType);
417 private final Map<String, String> myCachedVersionStrings = new HashMap<String, String>();
420 public final String getVersionString(String sdkHome) {
421 String versionString = myCachedVersionStrings.get(sdkHome);
422 if (versionString == null) {
423 versionString = getJdkVersion(sdkHome);
424 if (!StringUtil.isEmpty(versionString)) {
425 myCachedVersionStrings.put(sdkHome, versionString);
428 return versionString;
432 public int compareTo(@NotNull String versionString, @NotNull String versionNumber) {
433 return getVersionNumber(versionString).compareTo(versionNumber);
437 public JavaSdkVersion getVersion(@NotNull Sdk sdk) {
438 String version = sdk.getVersionString();
439 if (version == null) return null;
440 return JdkVersionUtil.getVersion(version);
445 public JavaSdkVersion getVersion(@NotNull String versionString) {
446 return JdkVersionUtil.getVersion(versionString);
450 public boolean isOfVersionOrHigher(@NotNull Sdk sdk, @NotNull JavaSdkVersion version) {
451 JavaSdkVersion sdkVersion = getVersion(sdk);
452 return sdkVersion != null && sdkVersion.isAtLeast(version);
456 public Sdk createJdk(@NotNull String jdkName, @NotNull String home, boolean isJre) {
457 ProjectJdkImpl jdk = new ProjectJdkImpl(jdkName, this);
458 SdkModificator sdkModificator = jdk.getSdkModificator();
460 String path = home.replace(File.separatorChar, '/');
461 sdkModificator.setHomePath(path);
462 sdkModificator.setVersionString(jdkName); // must be set after home path, otherwise setting home path clears the version string
464 File jdkHomeFile = new File(home);
465 addClasses(jdkHomeFile, sdkModificator, isJre);
466 addSources(jdkHomeFile, sdkModificator);
467 addDocs(jdkHomeFile, sdkModificator);
468 sdkModificator.commitChanges();
473 private static void addClasses(File file, SdkModificator sdkModificator, boolean isJre) {
474 for (VirtualFile virtualFile : findClasses(file, isJre)) {
475 sdkModificator.addRoot(virtualFile, OrderRootType.CLASSES);
479 private static List<VirtualFile> findClasses(File file, boolean isJre) {
480 List<File> roots = JavaSdkUtil.getJdkClassesRoots(file, isJre);
481 List<String> urls = ContainerUtil.newArrayListWithCapacity(roots.size() + 1);
482 if (JrtFileSystem.isModularJdk(file.getPath())) {
483 urls.add(VirtualFileManager.constructUrl(JrtFileSystem.PROTOCOL, FileUtil.toSystemIndependentName(file.getPath()) + JrtFileSystem.SEPARATOR));
485 for (File root : roots) {
486 urls.add(VfsUtil.getUrlForLibraryRoot(root));
489 List<VirtualFile> result = ContainerUtil.newArrayListWithCapacity(urls.size());
490 for (String url : urls) {
491 VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(url);
499 private static void addSources(File file, SdkModificator sdkModificator) {
500 VirtualFile vFile = findSources(file);
502 sdkModificator.addRoot(vFile, OrderRootType.SOURCES);
507 @SuppressWarnings({"HardCodedStringLiteral"})
508 public static VirtualFile findSources(File file) {
509 return findSources(file, "src");
513 @SuppressWarnings({"HardCodedStringLiteral"})
514 public static VirtualFile findSources(File file, final String srcName) {
515 File srcDir = new File(file, "src");
516 File jarFile = new File(file, srcName + ".jar");
517 if (!jarFile.exists()) {
518 jarFile = new File(file, srcName + ".zip");
521 if (jarFile.exists()) {
522 VirtualFile vFile = findInJar(jarFile, "src");
523 if (vFile != null) return vFile;
525 vFile = findInJar(jarFile, "");
529 if (!srcDir.exists() || !srcDir.isDirectory()) return null;
530 String path = srcDir.getAbsolutePath().replace(File.separatorChar, '/');
531 return LocalFileSystem.getInstance().findFileByPath(path);
535 @SuppressWarnings({"HardCodedStringLiteral"})
536 private static void addDocs(File file, SdkModificator rootContainer) {
537 VirtualFile vFile = findDocs(file, "docs/api");
539 rootContainer.addRoot(vFile, JavadocOrderRootType.getInstance());
544 private static VirtualFile findInJar(File jarFile, String relativePath) {
545 if (!jarFile.exists()) return null;
546 String url = JarFileSystem.PROTOCOL_PREFIX +
547 jarFile.getAbsolutePath().replace(File.separatorChar, '/') + JarFileSystem.JAR_SEPARATOR + relativePath;
548 return VirtualFileManager.getInstance().findFileByUrl(url);
552 public static VirtualFile findDocs(File file, final String relativePath) {
553 file = new File(file.getAbsolutePath() + File.separator + relativePath.replace('/', File.separatorChar));
554 if (!file.exists() || !file.isDirectory()) return null;
555 String path = file.getAbsolutePath().replace(File.separatorChar, '/');
556 return LocalFileSystem.getInstance().findFileByPath(path);
560 public boolean isRootTypeApplicable(OrderRootType type) {
561 return type == OrderRootType.CLASSES ||
562 type == OrderRootType.SOURCES ||
563 type == JavadocOrderRootType.getInstance() ||
564 type == AnnotationOrderRootType.getInstance();