blob: f3d6575e9fd9e58f57e54822c865e6cf2cc0f680 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.projectRoots.impl;
import com.intellij.execution.util.ExecUtil;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.projectRoots.*;
import com.intellij.openapi.roots.AnnotationOrderRootType;
import com.intellij.openapi.roots.JavadocOrderRootType;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.impl.JavaSdkUtil;
import javax.swing.*;
import java.io.File;
import java.io.FileFilter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Eugene Zhuravlev
* @since Sep 17, 2004
*/
public class JavaSdkImpl extends JavaSdk {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.impl.JavaSdkImpl");
// do not use javaw.exe for Windows because of issues with encoding
@NonNls private static final String VM_EXE_NAME = "java";
@NonNls private final Pattern myVersionStringPattern = Pattern.compile("^(.*)java version \"([1234567890_.]*)\"(.*)$");
@NonNls private static final String JAVA_VERSION_PREFIX = "java version ";
@NonNls private static final String OPENJDK_VERSION_PREFIX = "openjdk version ";
public static final DataKey<Boolean> KEY = DataKey.create("JavaSdk");
public JavaSdkImpl() {
super("JavaSDK");
}
@Override
public String getPresentableName() {
return ProjectBundle.message("sdk.java.name");
}
@Override
public Icon getIcon() {
return AllIcons.Nodes.PpJdk;
}
@NotNull
@Override
public String getHelpTopic() {
return "reference.project.structure.sdk.java";
}
@Override
public Icon getIconForAddAction() {
return AllIcons.General.AddJdk;
}
@NonNls
@Override
@Nullable
public String getDefaultDocumentationUrl(@NotNull final Sdk sdk) {
final JavaSdkVersion version = getVersion(sdk);
if (version == JavaSdkVersion.JDK_1_5) {
return "http://docs.oracle.com/javase/1.5.0/docs/api/";
}
if (version == JavaSdkVersion.JDK_1_6) {
return "http://docs.oracle.com/javase/6/docs/api/";
}
if (version == JavaSdkVersion.JDK_1_7) {
return "http://docs.oracle.com/javase/7/docs/api/";
}
if (version == JavaSdkVersion.JDK_1_8) {
return "http://docs.oracle.com/javase/8/docs/api";
}
return null;
}
@Override
public AdditionalDataConfigurable createAdditionalDataConfigurable(SdkModel sdkModel, SdkModificator sdkModificator) {
return null;
}
@Override
public void saveAdditionalData(@NotNull SdkAdditionalData additionalData, @NotNull Element additional) {
}
@Override
@SuppressWarnings({"HardCodedStringLiteral"})
public String getBinPath(@NotNull Sdk sdk) {
return getConvertedHomePath(sdk) + "bin";
}
@Override
@NonNls
public String getToolsPath(@NotNull Sdk sdk) {
final String versionString = sdk.getVersionString();
final boolean isJdk1_x = versionString != null && (versionString.contains("1.0") || versionString.contains("1.1"));
return getConvertedHomePath(sdk) + "lib" + File.separator + (isJdk1_x? "classes.zip" : "tools.jar");
}
@Override
public String getVMExecutablePath(@NotNull Sdk sdk) {
/*
if ("64".equals(System.getProperty("sun.arch.data.model"))) {
return getBinPath(sdk) + File.separator + System.getProperty("os.arch") + File.separator + VM_EXE_NAME;
}
*/
return getBinPath(sdk) + File.separator + VM_EXE_NAME;
}
private static String getConvertedHomePath(Sdk sdk) {
String homePath = sdk.getHomePath();
assert homePath != null : sdk;
String path = FileUtil.toSystemDependentName(homePath);
if (!path.endsWith(File.separator)) {
path += File.separator;
}
return path;
}
@Override
@SuppressWarnings({"HardCodedStringLiteral"})
public String suggestHomePath() {
if (SystemInfo.isMac) {
if (new File("/usr/libexec/java_home").exists()) {
final String path = ExecUtil.execAndReadLine("/usr/libexec/java_home");
if (path != null && new File(path).exists()) {
return path;
}
}
return "/System/Library/Frameworks/JavaVM.framework/Versions";
}
if (SystemInfo.isLinux) {
final String[] homes = {"/usr/java", "/opt/java", "/usr/lib/jvm"};
for (String home : homes) {
if (new File(home).isDirectory()) {
return home;
}
}
}
if (SystemInfo.isSolaris) {
return "/usr/jdk";
}
if (SystemInfo.isWindows) {
String property = System.getProperty("java.home");
if (property == null) return null;
File javaHome = new File(property).getParentFile();//actually java.home points to to jre home
if (javaHome != null && JdkUtil.checkForJdk(javaHome)) {
return javaHome.getAbsolutePath();
}
}
return null;
}
@NotNull
@Override
public Collection<String> suggestHomePaths() {
if (!SystemInfo.isWindows)
return Collections.singletonList(suggestHomePath());
String property = System.getProperty("java.home");
if (property == null)
return Collections.emptyList();
File javaHome = new File(property).getParentFile();//actually java.home points to to jre home
if (javaHome == null || !javaHome.isDirectory() || javaHome.getParentFile() == null) {
return Collections.emptyList();
}
ArrayList<String> result = new ArrayList<String>();
File javasFolder = javaHome.getParentFile();
scanFolder(javasFolder, result);
File parentFile = javasFolder.getParentFile();
File root = parentFile != null ? parentFile.getParentFile() : null;
String name = parentFile != null ? parentFile.getName() : "";
if (name.contains("Program Files") && root != null) {
String x86Suffix = " (x86)";
boolean x86 = name.endsWith(x86Suffix) && name.length() > x86Suffix.length();
File anotherJavasFolder;
if (x86) {
anotherJavasFolder = new File(root, name.substring(0, name.length() - x86Suffix.length()));
}
else {
anotherJavasFolder = new File(root, name + x86Suffix);
}
if (anotherJavasFolder.isDirectory()) {
scanFolder(new File(anotherJavasFolder, javasFolder.getName()), result);
}
}
return result;
}
private static void scanFolder(File javasFolder, ArrayList<String> result) {
File[] candidates = javasFolder.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return JdkUtil.checkForJdk(pathname);
}
});
if (candidates != null) {
result.addAll(ContainerUtil.map2List(candidates, new Function<File, String>() {
@Override
public String fun(File file) {
return file.getAbsolutePath();
}
}));
}
}
@Override
public FileChooserDescriptor getHomeChooserDescriptor() {
FileChooserDescriptor descriptor = super.getHomeChooserDescriptor();
descriptor.putUserData(KEY, Boolean.TRUE);
return descriptor;
}
@NonNls public static final String MAC_HOME_PATH = "/Home";
@Override
public String adjustSelectedSdkHome(String homePath) {
if (SystemInfo.isMac) {
File home = new File(homePath, MAC_HOME_PATH);
if (home.exists()) return home.getPath();
home = new File(new File(homePath, "Contents"), "Home");
if (home.exists()) return home.getPath();
}
return homePath;
}
@Override
public boolean isValidSdkHome(String path) {
return checkForJdk(new File(path));
}
@Override
public String suggestSdkName(String currentSdkName, String sdkHome) {
final String suggestedName;
if (currentSdkName != null && !currentSdkName.isEmpty()) {
final Matcher matcher = myVersionStringPattern.matcher(currentSdkName);
final boolean replaceNameWithVersion = matcher.matches();
if (replaceNameWithVersion){
// user did not change name -> set it automatically
final String versionString = getVersionString(sdkHome);
suggestedName = versionString == null ? currentSdkName : matcher.replaceFirst("$1" + versionString + "$3");
}
else {
suggestedName = currentSdkName;
}
}
else {
String versionString = getVersionString(sdkHome);
suggestedName = versionString == null ? ProjectBundle.message("sdk.java.unknown.name") : getVersionNumber(versionString);
}
return suggestedName;
}
@NotNull
private static String getVersionNumber(@NotNull String versionString) {
if (versionString.startsWith(JAVA_VERSION_PREFIX) || versionString.startsWith(OPENJDK_VERSION_PREFIX)) {
boolean openJdk = versionString.startsWith(OPENJDK_VERSION_PREFIX);
versionString = versionString.substring(openJdk ? OPENJDK_VERSION_PREFIX.length() : JAVA_VERSION_PREFIX.length());
if (versionString.startsWith("\"") && versionString.endsWith("\"")) {
versionString = versionString.substring(1, versionString.length() - 1);
}
int dotIdx = versionString.indexOf('.');
if (dotIdx > 0) {
try {
int major = Integer.parseInt(versionString.substring(0, dotIdx));
int minorDot = versionString.indexOf('.', dotIdx + 1);
if (minorDot > 0) {
int minor = Integer.parseInt(versionString.substring(dotIdx + 1, minorDot));
versionString = major + "." + minor;
}
}
catch (NumberFormatException e) {
// Do nothing. Use original version string if failed to parse according to major.minor pattern.
}
}
}
return versionString;
}
@Override
@SuppressWarnings({"HardCodedStringLiteral"})
public void setupSdkPaths(@NotNull Sdk sdk) {
final File jdkHome = new File(sdk.getHomePath());
List<VirtualFile> classes = findClasses(jdkHome, false);
VirtualFile sources = findSources(jdkHome);
VirtualFile docs = findDocs(jdkHome, "docs/api");
final SdkModificator sdkModificator = sdk.getSdkModificator();
final Set<VirtualFile> previousRoots = new LinkedHashSet<VirtualFile>(Arrays.asList(sdkModificator.getRoots(OrderRootType.CLASSES)));
sdkModificator.removeRoots(OrderRootType.CLASSES);
previousRoots.removeAll(new HashSet<VirtualFile>(classes));
for (VirtualFile aClass : classes) {
sdkModificator.addRoot(aClass, OrderRootType.CLASSES);
}
for (VirtualFile root : previousRoots) {
sdkModificator.addRoot(root, OrderRootType.CLASSES);
}
if(sources != null){
sdkModificator.addRoot(sources, OrderRootType.SOURCES);
}
final VirtualFile javaFxSources = findSources(jdkHome, "javafx-src");
if (javaFxSources != null) {
sdkModificator.addRoot(javaFxSources, OrderRootType.SOURCES);
}
if(docs != null){
sdkModificator.addRoot(docs, JavadocOrderRootType.getInstance());
}
else if (SystemInfo.isMac) {
VirtualFile commonDocs = findDocs(jdkHome, "docs");
if (commonDocs == null) {
commonDocs = findInJar(new File(jdkHome, "docs.jar"), "doc/api");
if (commonDocs == null) {
commonDocs = findInJar(new File(jdkHome, "docs.jar"), "docs/api");
}
}
if (commonDocs != null) {
sdkModificator.addRoot(commonDocs, JavadocOrderRootType.getInstance());
}
VirtualFile appleDocs = findDocs(jdkHome, "appledocs");
if (appleDocs == null) {
appleDocs = findInJar(new File(jdkHome, "appledocs.jar"), "appledoc/api");
}
if (appleDocs != null) {
sdkModificator.addRoot(appleDocs, JavadocOrderRootType.getInstance());
}
if (commonDocs == null && appleDocs == null && sources == null) {
String url = getDefaultDocumentationUrl(sdk);
if (url != null) {
sdkModificator.addRoot(VirtualFileManager.getInstance().findFileByUrl(url), JavadocOrderRootType.getInstance());
}
}
} else {
if (getVersion(sdk) == JavaSdkVersion.JDK_1_7) {
sdkModificator.addRoot(VirtualFileManager.getInstance().findFileByUrl("http://docs.oracle.com/javafx/2/api/"), JavadocOrderRootType.getInstance());
}
}
attachJdkAnnotations(sdkModificator);
sdkModificator.commitChanges();
}
public static void attachJdkAnnotations(@NotNull SdkModificator modificator) {
LocalFileSystem lfs = LocalFileSystem.getInstance();
// community idea under idea
VirtualFile root = lfs.findFileByPath(FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/java/jdkAnnotations");
if (root == null) { // idea under idea
root = lfs.findFileByPath(FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/community/java/jdkAnnotations");
}
if (root == null) { // build
root = VirtualFileManager.getInstance().findFileByUrl("jar://"+ FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar!/");
}
if (root == null) {
LOG.error("jdk annotations not found in: "+ FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar!/");
return;
}
OrderRootType annoType = AnnotationOrderRootType.getInstance();
modificator.removeRoot(root, annoType);
modificator.addRoot(root, annoType);
}
private final Map<String, String> myCachedVersionStrings = new HashMap<String, String>();
@Override
public final String getVersionString(final String sdkHome) {
if (myCachedVersionStrings.containsKey(sdkHome)) {
return myCachedVersionStrings.get(sdkHome);
}
String versionString = getJdkVersion(sdkHome);
if (versionString != null && versionString.isEmpty()) {
versionString = null;
}
if (versionString != null){
myCachedVersionStrings.put(sdkHome, versionString);
}
return versionString;
}
@Override
public int compareTo(@NotNull String versionString, @NotNull String versionNumber) {
return getVersionNumber(versionString).compareTo(versionNumber);
}
@Override
public JavaSdkVersion getVersion(@NotNull Sdk sdk) {
String version = sdk.getVersionString();
if (version == null) return null;
return JdkVersionUtil.getVersion(version);
}
@Override
@Nullable
public JavaSdkVersion getVersion(@NotNull String versionString) {
return JdkVersionUtil.getVersion(versionString);
}
@Override
public boolean isOfVersionOrHigher(@NotNull Sdk sdk, @NotNull JavaSdkVersion version) {
JavaSdkVersion sdkVersion = getVersion(sdk);
return sdkVersion != null && sdkVersion.isAtLeast(version);
}
@Override
public Sdk createJdk(@NotNull String jdkName, @NotNull String home, boolean isJre) {
ProjectJdkImpl jdk = new ProjectJdkImpl(jdkName, this);
SdkModificator sdkModificator = jdk.getSdkModificator();
String path = home.replace(File.separatorChar, '/');
sdkModificator.setHomePath(path);
sdkModificator.setVersionString(jdkName); // must be set after home path, otherwise setting home path clears the version string
File jdkHomeFile = new File(home);
addClasses(jdkHomeFile, sdkModificator, isJre);
addSources(jdkHomeFile, sdkModificator);
addDocs(jdkHomeFile, sdkModificator);
sdkModificator.commitChanges();
return jdk;
}
private static void addClasses(File file, SdkModificator sdkModificator, boolean isJre) {
for (VirtualFile virtualFile : findClasses(file, isJre)) {
sdkModificator.addRoot(virtualFile, OrderRootType.CLASSES);
}
}
private static List<VirtualFile> findClasses(File file, boolean isJre) {
List<VirtualFile> result = ContainerUtil.newArrayList();
List<File> rootFiles = JavaSdkUtil.getJdkClassesRoots(file, isJre);
for (File child : rootFiles) {
String url = VfsUtil.getUrlForLibraryRoot(child);
VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(url);
if (vFile != null) {
result.add(vFile);
}
}
return result;
}
private static void addSources(File file, SdkModificator sdkModificator) {
VirtualFile vFile = findSources(file);
if (vFile != null) {
sdkModificator.addRoot(vFile, OrderRootType.SOURCES);
}
}
@Nullable
@SuppressWarnings({"HardCodedStringLiteral"})
public static VirtualFile findSources(File file) {
return findSources(file, "src");
}
@Nullable
@SuppressWarnings({"HardCodedStringLiteral"})
public static VirtualFile findSources(File file, final String srcName) {
File srcDir = new File(file, "src");
File jarFile = new File(file, srcName + ".jar");
if (!jarFile.exists()) {
jarFile = new File(file, srcName + ".zip");
}
if (jarFile.exists()) {
VirtualFile vFile = findInJar(jarFile, "src");
if (vFile != null) return vFile;
// try 1.4 format
vFile = findInJar(jarFile, "");
return vFile;
}
else {
if (!srcDir.exists() || !srcDir.isDirectory()) return null;
String path = srcDir.getAbsolutePath().replace(File.separatorChar, '/');
return LocalFileSystem.getInstance().findFileByPath(path);
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
private static void addDocs(File file, SdkModificator rootContainer) {
VirtualFile vFile = findDocs(file, "docs/api");
if (vFile != null) {
rootContainer.addRoot(vFile, JavadocOrderRootType.getInstance());
}
}
@Nullable
private static VirtualFile findInJar(File jarFile, String relativePath) {
if (!jarFile.exists()) return null;
String url = JarFileSystem.PROTOCOL_PREFIX +
jarFile.getAbsolutePath().replace(File.separatorChar, '/') + JarFileSystem.JAR_SEPARATOR + relativePath;
return VirtualFileManager.getInstance().findFileByUrl(url);
}
@Nullable
public static VirtualFile findDocs(File file, final String relativePath) {
file = new File(file.getAbsolutePath() + File.separator + relativePath.replace('/', File.separatorChar));
if (!file.exists() || !file.isDirectory()) return null;
String path = file.getAbsolutePath().replace(File.separatorChar, '/');
return LocalFileSystem.getInstance().findFileByPath(path);
}
@Override
public boolean isRootTypeApplicable(OrderRootType type) {
return type == OrderRootType.CLASSES ||
type == OrderRootType.SOURCES ||
type == JavadocOrderRootType.getInstance() ||
type == AnnotationOrderRootType.getInstance();
}
}