[java] Javadoc generation in modular projects; part 1 (IDEA-179255)

This commit is contained in:
Roman Shevchenko
2017-10-12 19:00:22 +02:00
parent 6022f0804d
commit eeb2a59dcb
@@ -1,6 +1,7 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.javadoc;
import com.google.common.collect.Streams;
import com.intellij.analysis.AnalysisScope;
import com.intellij.execution.CantRunException;
import com.intellij.execution.ExecutionException;
@@ -22,30 +23,30 @@ import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.JavaSdkType;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.ex.JavaSdkUtil;
import com.intellij.openapi.projectRoots.ex.PathUtilEx;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.JavadocOrderRootType;
import com.intellij.openapi.roots.OrderEnumerator;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import javax.swing.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static com.intellij.analysis.AnalysisScope.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author nik
@@ -150,8 +151,7 @@ public class JavadocGeneratorRunProfile implements ModuleRunProfile {
cmdLine.setExePath(tool.getPath());
if (myConfiguration.HEAP_SIZE != null && myConfiguration.HEAP_SIZE.trim().length() != 0) {
JavaSdkVersion version = JavaSdk.getInstance().getVersion(jdk);
String param = version == null || version.isAtLeast(JavaSdkVersion.JDK_1_2) ? "-J-Xmx" : "-J-mx";
String param = JavaSdkUtil.isJdkAtLeast(jdk, JavaSdkVersion.JDK_1_2) ? "-J-Xmx" : "-J-mx";
cmdLine.getParametersList().prepend(param + myConfiguration.HEAP_SIZE + "m");
}
}
@@ -218,53 +218,70 @@ public class JavadocGeneratorRunProfile implements ModuleRunProfile {
parameters.add(myConfiguration.OUTPUT_DIRECTORY.replace('/', File.separatorChar));
}
Set<Module> modules = new LinkedHashSet<>();
try {
File argsFile = FileUtil.createTempFile("javadoc_args", null);
try (PrintWriter writer = new PrintWriter(new FileWriter(argsFile))) {
Set<String> packages = new HashSet<>();
Set<String> sources = new HashSet<>();
int t = myGenerationOptions.getScopeType();
boolean usePackageNotation = t == MODULE || t == MODULES || t == PROJECT || t == DIRECTORY;
Runnable r = () -> myGenerationOptions.accept(new MyContentIterator(myProject, modules, packages, sources, usePackageNotation));
Set<Module> modules = new LinkedHashSet<>();
Set<VirtualFile> sources = new HashSet<>();
Runnable r = () -> myGenerationOptions.accept(new MyContentIterator(myProject, modules, sources));
String title = JavadocBundle.message("javadoc.generate.sources.progress");
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(r, title, true, myProject)) {
return;
}
if (packages.size() + sources.size() == 0) {
if (sources.isEmpty()) {
throw new CantRunException(JavadocBundle.message("javadoc.generate.no.classes.in.selected.packages.error"));
}
for (String pkg : packages) {
writer.println(pkg);
}
for (String src : sources) {
writer.println(StringUtil.wrapWithDoubleQuote(src));
boolean hasJavaModules = sources.stream().anyMatch(f -> PsiJavaModule.MODULE_INFO_FILE.equals(f.getName()));
if (hasJavaModules && modules.size() > 1) {
throw new CantRunException("At the moment, IDEA cannot generate Javadoc for multiple modules" +
" with module-info.java files in them. Sorry. We're working on this.");
}
OrderEnumerator rootEnumerator = OrderEnumerator.orderEntries(myProject);
OrderEnumerator sourcePathEnumerator = ProjectRootManager.getInstance(myProject).orderEntries(modules);
if (!myConfiguration.OPTION_INCLUDE_LIBS) {
rootEnumerator = rootEnumerator.withoutSdk().withoutLibraries();
sourcePathEnumerator = sourcePathEnumerator.withoutSdk().withoutLibraries();
}
List<VirtualFile> files = rootEnumerator.getSourcePathsList().getRootDirs();
if (!myGenerationOptions.isIncludeTestSource()) {
ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex();
files = ContainerUtil.filter(files, f -> !index.isInTestSourceContent(f));
}
if (!files.isEmpty()) {
writer.println("-sourcepath");
writer.println(StringUtil.wrapWithDoubleQuote(StringUtil.join(files, File.pathSeparator)));
sourcePathEnumerator = sourcePathEnumerator.productionOnly();
}
List<VirtualFile> sourceRoots = sourcePathEnumerator.getSourcePathsList().getRootDirs();
OrderEnumerator classPathEnumerator = ProjectRootManager.getInstance(myProject).orderEntries(modules).withoutModuleSourceEntries();
if (jdk.getSdkType() instanceof JavaSdk) {
classPathEnumerator = classPathEnumerator.withoutSdk();
}
String classPath = classPathEnumerator.getPathsList().getPathsString();
if (!classPath.isEmpty()) {
writer.println("-classpath");
writer.println(StringUtil.wrapWithDoubleQuote(classPath));
if (!myGenerationOptions.isIncludeTestSource()) {
classPathEnumerator = classPathEnumerator.productionOnly();
}
List<VirtualFile> classRoots = classPathEnumerator.getPathsList().getRootDirs();
if (sourceRoots.size() + classRoots.size() > 0) {
if (hasJavaModules) {
if (!sourceRoots.isEmpty()) {
String path = sourceRoots.stream().map(MyJavaCommandLineState::localPath).collect(Collectors.joining(File.pathSeparator));
writer.println("--source-path");
writer.println(StringUtil.wrapWithDoubleQuote(path));
}
if (!classRoots.isEmpty()) {
String path = classRoots.stream().map(MyJavaCommandLineState::localPath).collect(Collectors.joining(File.pathSeparator));
writer.println("--module-path");
writer.println(StringUtil.wrapWithDoubleQuote(path));
}
}
else {
// placing source roots on a classpath is perfectly legal and allows to generate correct Javadoc
// when a module without a module-info.java file depends on another module which has one
Stream<VirtualFile> roots = Streams.concat(sourceRoots.stream(), classRoots.stream());
String path = roots.map(MyJavaCommandLineState::localPath).collect(Collectors.joining(File.pathSeparator));
writer.println("-classpath");
writer.println(StringUtil.wrapWithDoubleQuote(path));
}
}
for (VirtualFile source : sources) {
writer.println(StringUtil.wrapWithDoubleQuote(localPath(source)));
}
}
@@ -276,56 +293,36 @@ public class JavadocGeneratorRunProfile implements ModuleRunProfile {
throw new CantRunException(JavadocBundle.message("javadoc.generate.temp.file.error"), e);
}
}
private static String localPath(VirtualFile root) {
return FileUtil.toSystemDependentName(VfsUtil.getLocalFile(root).getPath());
}
}
private static class MyContentIterator extends PsiRecursiveElementWalkingVisitor {
private final PsiManager myPsiManager;
private final Set<Module> myModules;
private final Set<String> myPackages;
private final Collection<String> mySourceFiles;
private final boolean myUsePackageNotation;
private final Set<VirtualFile> mySourceFiles;
public MyContentIterator(Project project, Set<Module> modules, Set<String> packages, Collection<String> sources, boolean usePackageNotation) {
public MyContentIterator(Project project, Set<Module> modules, Set<VirtualFile> sources) {
myPsiManager = PsiManager.getInstance(project);
myModules = modules;
myPackages = packages;
mySourceFiles = sources;
myUsePackageNotation = usePackageNotation;
}
@Override
public void visitFile(PsiFile file) {
VirtualFile vFile = file.getVirtualFile();
if (vFile != null && vFile.isInLocalFileSystem()) {
Module module = ModuleUtilCore.findModuleForFile(vFile, myPsiManager.getProject());
if (module != null) {
myModules.add(module);
}
if (file instanceof PsiJavaFile && !(file instanceof ServerPageFile)) {
String packageName = ((PsiJavaFile)file).getPackageName();
if (!myUsePackageNotation || packageName.isEmpty() || containsPackagePrefix(module, packageName)) {
mySourceFiles.add(FileUtil.toSystemIndependentName(vFile.getPath()));
}
else {
myPackages.add(packageName);
if (file instanceof PsiJavaFile && !(file instanceof ServerPageFile)) {
VirtualFile vFile = file.getVirtualFile();
if (vFile != null && vFile.isInLocalFileSystem()) {
mySourceFiles.add(vFile);
Module module = ModuleUtilCore.findModuleForFile(vFile, myPsiManager.getProject());
if (module != null) {
myModules.add(module);
}
}
}
}
private static boolean containsPackagePrefix(Module module, String packageFQName) {
if (module != null) {
for (ContentEntry contentEntry : ModuleRootManager.getInstance(module).getContentEntries()) {
for (SourceFolder sourceFolder : contentEntry.getSourceFolders(JavaModuleSourceRootTypes.SOURCES)) {
String packagePrefix = sourceFolder.getPackagePrefix();
if (!packagePrefix.isEmpty() && packageFQName.startsWith(packagePrefix)) {
return true;
}
}
}
}
return false;
}
}
}