/* * 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 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 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 result = new ArrayList(); 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 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() { @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 classes = findClasses(jdkHome, false); VirtualFile sources = findSources(jdkHome); VirtualFile docs = findDocs(jdkHome, "docs/api"); final SdkModificator sdkModificator = sdk.getSdkModificator(); final Set previousRoots = new LinkedHashSet(Arrays.asList(sdkModificator.getRoots(OrderRootType.CLASSES))); sdkModificator.removeRoots(OrderRootType.CLASSES); previousRoots.removeAll(new HashSet(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 myCachedVersionStrings = new HashMap(); @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 findClasses(File file, boolean isJre) { List result = ContainerUtil.newArrayList(); List 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(); } }