[java] for modular JDK, uses per-module class roots instead of a single one

This commit is contained in:
Roman Shevchenko
2016-07-06 17:45:06 +02:00
parent 9831392b62
commit bbed9b3f2d
9 changed files with 122 additions and 105 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 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.
@@ -16,15 +16,14 @@
package com.intellij.openapi.roots.ui.configuration.libraryEditor;
import com.intellij.icons.AllIcons;
import com.intellij.lang.LangBundle;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.ui.SdkPathEditor;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.ui.OrderRootTypeUIFactory;
import com.intellij.openapi.vfs.impl.jrt.JrtFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.impl.jrt.JrtFileSystem;
import com.intellij.ui.components.JBList;
import com.intellij.util.PlatformIcons;
@@ -37,8 +36,7 @@ import javax.swing.*;
public class ClassesOrderRootTypeUIFactory implements OrderRootTypeUIFactory {
@Override
public SdkPathEditor createPathEditor(Sdk sdk) {
FileChooserDescriptor descriptor = new FileChooserDescriptor(true, true, true, false, true, true);
return new MySdkPathEditor(descriptor);
return new MySdkPathEditor(new FileChooserDescriptor(true, true, true, false, true, true));
}
@Override
@@ -72,11 +70,6 @@ public class ClassesOrderRootTypeUIFactory implements OrderRootTypeUIFactory {
@Override
protected ListCellRenderer createListCellRenderer(JBList list) {
return new PathCellRenderer() {
@Override
protected String getItemText(Object value) {
return isJrtRoot(value) ? LangBundle.message("jrt.node.long") : super.getItemText(value);
}
@Override
protected Icon getItemIcon(Object value) {
return isJrtRoot(value) ? PlatformIcons.JAR_ICON : super.getItemIcon(value);
@@ -86,6 +79,6 @@ public class ClassesOrderRootTypeUIFactory implements OrderRootTypeUIFactory {
}
private static boolean isJrtRoot(Object value) {
return value instanceof VirtualFile && JrtFileSystem.isRoot((VirtualFile)value);
return value instanceof VirtualFile && JrtFileSystem.isModuleRoot((VirtualFile)value);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 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.
@@ -20,11 +20,9 @@ import com.intellij.ide.projectView.impl.nodes.PackageElement;
import com.intellij.ide.projectView.impl.nodes.PackageUtil;
import com.intellij.ide.projectView.impl.nodes.ProjectViewDirectoryHelper;
import com.intellij.ide.util.treeView.TreeViewUtil;
import com.intellij.lang.LangBundle;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.impl.DirectoryIndex;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.impl.jrt.JrtFileSystem;
import com.intellij.psi.JavaDirectoryService;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiPackage;
@@ -70,10 +68,6 @@ public class JavaProjectViewDirectoryHelper extends ProjectViewDirectoryHelper {
@Nullable
@Override
public String getNodeName(final ViewSettings settings, final Object parentValue, final PsiDirectory directory) {
if (JrtFileSystem.isRoot(directory.getVirtualFile())) {
return LangBundle.message("jrt.node.short");
}
PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory);
PsiPackage parentPackage;
@@ -534,23 +534,24 @@ public class JavaSdkImpl extends JavaSdk {
}
private static List<VirtualFile> findClasses(File file, boolean isJre) {
List<File> roots = JavaSdkUtil.getJdkClassesRoots(file, isJre);
List<String> urls = ContainerUtil.newArrayListWithCapacity(roots.size() + 1);
if (JrtFileSystem.isModularJdk(file.getPath())) {
urls.add(VirtualFileManager.constructUrl(JrtFileSystem.PROTOCOL, FileUtil.toSystemIndependentName(file.getPath()) + JrtFileSystem.SEPARATOR));
}
for (File root : roots) {
urls.add(VfsUtil.getUrlForLibraryRoot(root));
}
List<VirtualFile> result = ContainerUtil.newArrayList();
VirtualFileManager fileManager = VirtualFileManager.getInstance();
List<VirtualFile> result = ContainerUtil.newArrayListWithCapacity(urls.size());
for (String url : urls) {
VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(url);
if (vFile != null) {
result.add(vFile);
String path = file.getPath();
if (JrtFileSystem.isModularJdk(path)) {
String url = VirtualFileManager.constructUrl(JrtFileSystem.PROTOCOL, FileUtil.toSystemIndependentName(path) + JrtFileSystem.SEPARATOR);
for (String module : JrtFileSystem.listModules(path)) {
ContainerUtil.addIfNotNull(result, fileManager.findFileByUrl(url + module));
}
}
for (File root : JavaSdkUtil.getJdkClassesRoots(file, isJre)) {
String url = VfsUtil.getUrlForLibraryRoot(root);
ContainerUtil.addIfNotNull(result, fileManager.findFileByUrl(url));
}
Collections.sort(result, (o1, o2) -> o1.getPath().compareTo(o2.getPath()));
return result;
}
@@ -23,10 +23,12 @@ import com.intellij.notification.Notifications;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
@@ -41,20 +43,34 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.intellij.util.containers.ContainerUtil.newTroveMap;
public class JrtFileSystem extends ArchiveFileSystem {
public static final String PROTOCOL = StandardFileSystems.JRT_PROTOCOL;
public static final String PROTOCOL_PREFIX = StandardFileSystems.JRT_PROTOCOL_PREFIX;
public static final String SEPARATOR = JarFileSystem.JAR_SEPARATOR;
private static final boolean SUPPORTED =
SystemInfo.isJavaVersionAtLeast("9") || SystemInfo.isJavaVersionAtLeast("1.8") && !SystemInfo.isJavaVersionAtLeast("1.9");
private static final URI ROOT_URI = URI.create("jrt:/");
private final Map<String, ArchiveHandler> myHandlers = newTroveMap(FileUtil.PATH_HASHING_STRATEGY);
private final AtomicBoolean mySubscribed = new AtomicBoolean(false);
@@ -63,18 +79,26 @@ public class JrtFileSystem extends ArchiveFileSystem {
}
private static void scheduleConfiguredSdkCheck() {
if (isSupported()) return;
MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect();
connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener.Adapter() {
@Override
public void appStarting(Project project) {
for (Sdk sdk : ProjectJdkTable.getInstance().getSdksOfType(JavaSdk.getInstance())) {
String homePath = sdk.getHomePath();
if (homePath != null && isModularJdk(homePath)) {
String title = LangBundle.message("jrt.not.available.title", sdk.getName());
String message = LangBundle.message("jrt.not.available.message");
Notifications.Bus.notify(new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, title, message, NotificationType.WARNING));
}
Stream.of(sdk.getRootProvider().getUrls(OrderRootType.CLASSES))
.filter(url -> url.startsWith(PROTOCOL_PREFIX))
.findFirst()
.ifPresent(url -> {
if (!isSupported()) {
String title = LangBundle.message("jrt.not.available.title", sdk.getName());
String message = LangBundle.message("jrt.not.available.message");
Notifications.Bus.notify(new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, title, message, NotificationType.WARNING));
}
else if (url.endsWith(SEPARATOR)) {
String title = LangBundle.message("jrt.outdated.title", sdk.getName());
String message = LangBundle.message("jrt.outdated.message");
Notifications.Bus.notify(new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, title, message, NotificationType.WARNING));
}
});
}
connection.disconnect();
}
@@ -204,4 +228,36 @@ public class JrtFileSystem extends ArchiveFileSystem {
public static boolean isRoot(@NotNull VirtualFile file) {
return file.getParent() == null && file.getFileSystem() instanceof JrtFileSystem;
}
public static boolean isModuleRoot(@NotNull VirtualFile file) {
VirtualFile parent = file.getParent();
return parent != null && isRoot(parent);
}
@NotNull
public static List<String> listModules(@NotNull String path) {
try {
Path root = getFileSystem(path).getPath("/modules");
return Files.list(root).map(p -> p.getFileName().toString()).collect(Collectors.toList());
}
catch (IOException e) {
Logger.getInstance(JrtFileSystem.class).debug(e);
return Collections.emptyList();
}
}
static FileSystem getFileSystem(String path) throws IOException {
FileSystem fs;
if (SystemInfo.isJavaVersionAtLeast("9")) {
fs = FileSystems.newFileSystem(ROOT_URI, Collections.singletonMap("java.home", path));
}
else {
File file = new File(path, "jrt-fs.jar");
if (!file.exists()) throw new IOException("Missing provider: " + file);
URL url = file.toURI().toURL();
ClassLoader loader = new URLClassLoader(new URL[]{url}, null);
fs = FileSystems.newFileSystem(ROOT_URI, Collections.emptyMap(), loader);
}
return fs;
}
}
@@ -15,39 +15,21 @@
*/
package com.intellij.openapi.vfs.impl.jrt;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.impl.ArchiveHandler;
import com.intellij.reference.SoftReference;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.StringInterner;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.Map;
class JrtHandler extends ArchiveHandler {
private static final URI ROOT_URI = URI.create("jrt:/");
private static class JrtEntryInfo extends EntryInfo {
private final String myModule;
public JrtEntryInfo(@NotNull String shortName, @NotNull String module, long length, long timestamp, EntryInfo parent) {
super(shortName, false, length, timestamp, parent);
myModule = module;
}
}
private SoftReference<FileSystem> myFileSystem;
private final StringInterner myInterner = new StringInterner();
public JrtHandler(@NotNull String path) {
super(path);
@@ -56,16 +38,7 @@ class JrtHandler extends ArchiveHandler {
private synchronized FileSystem getFileSystem() throws IOException {
FileSystem fs = SoftReference.dereference(myFileSystem);
if (fs == null) {
if (SystemInfo.isJavaVersionAtLeast("9")) {
fs = FileSystems.newFileSystem(ROOT_URI, Collections.singletonMap("java.home", getFile().getPath()));
}
else {
File file = new File(getFile(), "jrt-fs.jar");
if (!file.exists()) throw new IOException("Missing provider: " + file);
URL url = file.toURI().toURL();
ClassLoader loader = new URLClassLoader(new URL[]{url}, null);
fs = FileSystems.newFileSystem(ROOT_URI, Collections.emptyMap(), loader);
}
fs = JrtFileSystem.getFileSystem(getFile().getPath());
myFileSystem = new SoftReference<>(fs);
}
return fs;
@@ -95,23 +68,17 @@ class JrtHandler extends ArchiveHandler {
private void process(Path entry, BasicFileAttributes attrs) throws IOException {
int pathLength = entry.getNameCount();
if (pathLength <= 2) return;
if (pathLength > 1) {
Path relativePath = entry.subpath(1, pathLength);
String path = relativePath.toString();
if (!map.containsKey(path)) {
EntryInfo parent = map.get(pathLength > 2 ? relativePath.getParent().toString() : "");
if (parent == null) throw new IOException("Out of order: " + entry);
Path relativePath = entry.subpath(2, pathLength);
String path = relativePath.toString(), shortName = entry.getFileName().toString();
if (map.containsKey(path) || "module-info.class".equals(shortName)) return;
EntryInfo parent = map.get(pathLength > 3 ? relativePath.getParent().toString() : "");
if (parent == null) throw new IOException("Out of order: " + entry);
long length = attrs.size();
long modified = attrs.lastModifiedTime().toMillis();
if (attrs.isDirectory()) {
map.put(path, new EntryInfo(shortName, true, length, modified, parent));
}
else {
String module = myInterner.intern(entry.getName(1).toString());
map.put(path, new JrtEntryInfo(shortName, module, length, modified, parent));
String shortName = entry.getFileName().toString();
long modified = attrs.lastModifiedTime().toMillis();
map.put(path, new EntryInfo(shortName, attrs.isDirectory(), attrs.size(), modified, parent));
}
}
}
});
@@ -123,8 +90,8 @@ class JrtHandler extends ArchiveHandler {
@Override
public byte[] contentsToByteArray(@NotNull String relativePath) throws IOException {
EntryInfo entry = getEntryInfo(relativePath);
if (!(entry instanceof JrtEntryInfo)) throw new FileNotFoundException(getFile() + " : " + relativePath);
Path path = getFileSystem().getPath("/modules/" + ((JrtEntryInfo)entry).myModule + '/' + relativePath);
if (entry == null) throw new FileNotFoundException(getFile() + " : " + relativePath);
Path path = getFileSystem().getPath("/modules/" + relativePath);
return Files.readAllBytes(path);
}
}
@@ -63,14 +63,22 @@ public class JrtFileSystemTest extends BareTestFixtureTestCase {
assertThat(JrtFileSystem.isRoot(myRoot)).isTrue();
}
@Test
public void moduleListing() {
String path = myTempDir.getRoot().getPath();
assertThat(JrtFileSystem.listModules(path)).containsExactlyInAnyOrder("java.base", "test1");
}
@Test
public void basicOps() throws IOException {
assertThat(myRoot.findChild("test")).isNotNull();
assertThat(childNames(myRoot)).containsExactlyInAnyOrder("java.base", "test1");
List<String> names = Stream.of(myRoot.getChildren()).map(VirtualFile::getName).collect(Collectors.toList());
assertThat(names).containsOnly("test");
VirtualFile moduleRoot = myRoot.findChild("test1");
assertThat(moduleRoot).isNotNull();
assertThat(JrtFileSystem.isModuleRoot(moduleRoot)).isTrue();
assertThat(childNames(moduleRoot)).containsExactlyInAnyOrder("test", "module-info.class");
VirtualFile classFile = myRoot.findFileByRelativePath("test/pkg1/Class1.class");
VirtualFile classFile = moduleRoot.findFileByRelativePath("test/pkg1/Class1.class");
assertThat(classFile).isNotNull();
byte[] bytes = classFile.contentsToByteArray();
@@ -80,12 +88,7 @@ public class JrtFileSystemTest extends BareTestFixtureTestCase {
@Test
public void refresh() throws IOException {
VirtualFile dir = myRoot.findChild("test");
assertThat(dir).isNotNull();
assertThat(dir.isValid()).isTrue();
assertThat(Stream.of(dir.getChildren()).map(VirtualFile::getName).collect(Collectors.toList())).containsOnly("pkg1");
assertThat(myRoot.findFileByRelativePath("test/pkg2/Class2.class")).isNull();
assertThat(childNames(myRoot)).containsExactlyInAnyOrder("java.base", "test1");
Path modules = myTempDir.getRoot().toPath().resolve("lib/modules");
Files.move(modules, myTempDir.getRoot().toPath().resolve("lib/modules.bak"));
@@ -95,8 +98,10 @@ public class JrtFileSystemTest extends BareTestFixtureTestCase {
assertThat(local).isNotNull();
local.refresh(false, true);
assertThat(dir.isValid()).isTrue();
assertThat(Stream.of(dir.getChildren()).map(VirtualFile::getName).collect(Collectors.toList())).containsOnly("pkg1", "pkg2");
assertThat(myRoot.findFileByRelativePath("test/pkg2/Class2.class")).isNotNull();
assertThat(childNames(myRoot)).containsExactlyInAnyOrder("java.base", "test1", "test2");
}
private static List<String> childNames(VirtualFile dir) {
return Stream.of(dir.getChildren()).map(VirtualFile::getName).collect(Collectors.toList());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 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.
@@ -28,6 +28,7 @@ public class StandardFileSystems {
public static final String JAR_PROTOCOL_PREFIX = JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR;
public static final String JRT_PROTOCOL = "jrt";
public static final String JRT_PROTOCOL_PREFIX = JRT_PROTOCOL + URLUtil.SCHEME_SEPARATOR;
private static final NotNullLazyValue<VirtualFileSystem> ourLocal = new NotNullLazyValue<VirtualFileSystem>() {
@NotNull
@@ -68,4 +69,4 @@ public class StandardFileSystems {
String localPath = path.substring(0, separatorIndex);
return local().findFileByPath(localPath);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 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.
@@ -22,7 +22,6 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.VfsImplUtil;
import com.intellij.util.Function;
import com.intellij.util.SystemProperties;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -110,7 +109,7 @@ public class JarFileSystemImpl extends JarFileSystem {
@NotNull
@Override
protected JarHandler getHandler(@NotNull VirtualFile entryFile) {
return VfsImplUtil.getHandler(this, entryFile, localPath -> new JarHandler(localPath));
return VfsImplUtil.getHandler(this, entryFile, JarHandler::new);
}
@Override
@@ -132,4 +131,4 @@ public class JarFileSystemImpl extends JarFileSystem {
public void refresh(boolean asynchronous) {
VfsImplUtil.refresh(this, asynchronous);
}
}
}
@@ -53,9 +53,10 @@ quickfix.change.template.data.language.text=Change {0} template data language to
incorrect.name=Incorrect name
jrt.node.short=[JRT]
jrt.node.long=[Java Run-time Modules]
jrt.not.available.message=You need to run IDEA on Java 8+ to be able to use modular JDK. Sorry.
jrt.not.available.title=Unsupported JDK ''{0}'' detected
jrt.outdated.message=JDK definition need to be updated. Please delete and re-create the JDK.
jrt.outdated.title=Outdated JDK ''{0}''
compound.run.configuration.cycle={0} ''{1}'' causes dependency cycle and cannot be added