terminal: use WinPtyCommand.getCommand to avoid using Win API (EA-235644 (plugin...
[idea/community.git] / java / idea-ui / src / com / intellij / ide / util / importProject / JavaModuleInsight.java
1 // Copyright 2000-2020 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.
2 package com.intellij.ide.util.importProject;
3
4 import com.intellij.ide.JavaUiBundle;
5 import com.intellij.ide.highlighter.JavaFileType;
6 import com.intellij.ide.util.projectWizard.importSources.DetectedProjectRoot;
7 import com.intellij.ide.util.projectWizard.importSources.DetectedSourceRoot;
8 import com.intellij.ide.util.projectWizard.importSources.JavaModuleSourceRoot;
9 import com.intellij.ide.util.projectWizard.importSources.JavaSourceRootDetectionUtil;
10 import com.intellij.lang.java.JavaParserDefinition;
11 import com.intellij.lexer.Lexer;
12 import com.intellij.openapi.application.ReadAction;
13 import com.intellij.openapi.diagnostic.Logger;
14 import com.intellij.openapi.module.StdModuleTypes;
15 import com.intellij.openapi.progress.ProcessCanceledException;
16 import com.intellij.openapi.progress.ProgressIndicator;
17 import com.intellij.openapi.project.ProjectManager;
18 import com.intellij.openapi.util.NlsSafe;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.pom.java.LanguageLevel;
22 import com.intellij.psi.*;
23 import com.intellij.util.Consumer;
24 import com.intellij.util.IncorrectOperationException;
25 import com.intellij.util.containers.ContainerUtil;
26 import one.util.streamex.StreamEx;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29
30 import java.io.File;
31 import java.io.IOException;
32 import java.util.*;
33 import java.util.zip.ZipEntry;
34 import java.util.zip.ZipFile;
35
36 public final class JavaModuleInsight extends ModuleInsight {
37   private static final Logger LOG = Logger.getInstance(JavaModuleInsight.class);
38   private final Lexer myLexer;
39
40   public JavaModuleInsight(@Nullable final ProgressIndicator progress,
41                            Set<String> existingModuleNames,
42                            Set<String> existingProjectLibraryNames) {
43     super(progress, existingModuleNames, existingProjectLibraryNames);
44     myLexer = JavaParserDefinition.createLexer(LanguageLevel.JDK_1_5);
45   }
46
47   @Override
48   public void scanModules() {
49     scanModuleInfoFiles();
50
51     super.scanModules();
52   }
53
54   private void scanModuleInfoFiles() {
55     final List<DetectedSourceRoot> allRoots = super.getSourceRootsToScan();
56     final List<JavaModuleSourceRoot> moduleInfoRoots = StreamEx
57       .of(allRoots)
58       .select(JavaModuleSourceRoot.class)
59       .filter(JavaModuleSourceRoot::isWithModuleInfoFile)
60       .filter(root -> !isIgnoredName(root.getDirectory()))
61       .toList();
62     if (moduleInfoRoots.isEmpty()) {
63       return;
64     }
65     myProgress.setIndeterminate(true);
66     myProgress.pushState();
67     try {
68       Map<String, ModuleInfo> moduleInfos = new HashMap<>();
69       for (JavaModuleSourceRoot moduleInfoRoot : moduleInfoRoots) {
70         final File sourceRoot = moduleInfoRoot.getDirectory();
71         myProgress.setText(JavaUiBundle.message("module.insight.scan.progress.text.scanning", sourceRoot.getPath()));
72         final ModuleInfo moduleInfo = scanModuleInfoFile(sourceRoot);
73         if (moduleInfo != null) {
74           moduleInfo.descriptor = createModuleDescriptor(moduleInfo.directory, Collections.singletonList(moduleInfoRoot));
75           moduleInfos.put(moduleInfo.name, moduleInfo);
76           addExportedPackages(sourceRoot, moduleInfo.exportsPackages);
77         }
78       }
79       myProgress.setText(JavaUiBundle.message("module.insight.scan.progress.text.building.modules.layout"));
80       for (ModuleInfo moduleInfo : moduleInfos.values()) {
81         for (String requiresModule : moduleInfo.requiresModules) {
82           ModuleInfo requiredModuleInfo = moduleInfos.get(requiresModule);
83           if (requiredModuleInfo != null) {
84             moduleInfo.descriptor.addDependencyOn(requiredModuleInfo.descriptor);
85           }
86         }
87       }
88
89       addModules(StreamEx.of(moduleInfos.values()).map(info -> info.descriptor).toList());
90     }
91     catch (ProcessCanceledException ignored) { }
92     finally {
93       myProgress.popState();
94     }
95   }
96
97   @NotNull
98   @Override
99   protected List<DetectedSourceRoot> getSourceRootsToScan() {
100     final List<DetectedSourceRoot> allRoots = super.getSourceRootsToScan();
101     return ContainerUtil.filter(allRoots, r -> !(r instanceof JavaModuleSourceRoot) || !((JavaModuleSourceRoot)r).isWithModuleInfoFile());
102   }
103
104   private ModuleInfo scanModuleInfoFile(@NotNull File directory) {
105     File file = new File(directory, PsiJavaModule.MODULE_INFO_FILE);
106     @NlsSafe final String name = file.getName();
107     myProgress.setText2(name);
108     try {
109       String text = FileUtil.loadFile(file);
110
111       PsiFileFactory factory = PsiFileFactory.getInstance(ProjectManager.getInstance().getDefaultProject());
112       ModuleInfo moduleInfo = ReadAction.compute(() -> {
113         PsiFile psiFile = factory.createFileFromText(PsiJavaModule.MODULE_INFO_FILE, JavaFileType.INSTANCE, text);
114         PsiJavaModule javaModule = psiFile instanceof PsiJavaFile ? ((PsiJavaFile)psiFile).getModuleDeclaration() : null;
115         if (javaModule == null) {
116           throw new IncorrectOperationException("Incorrect module declaration '" + file.getPath() + "'");
117         }
118         ModuleInfo info = new ModuleInfo(javaModule.getName());
119         javaModule.accept(new ModuleInfoVisitor(info));
120         return info;
121       });
122
123       File moduleDirectory = directory;
124       while (!isEntryPointRoot(moduleDirectory) && !moduleInfo.name.equals(moduleDirectory.getName())) {
125         File parent = moduleDirectory.getParentFile();
126         if (parent == null) break;
127         moduleDirectory = parent;
128       }
129       moduleInfo.directory = moduleDirectory;
130
131       return moduleInfo;
132     }
133     catch (IOException | IncorrectOperationException e) {
134       LOG.info(e);
135       return null;
136     }
137   }
138
139   @Override
140   protected boolean isSourceFile(final File file) {
141     return StringUtil.endsWithIgnoreCase(file.getName(), ".java");
142   }
143
144   @Override
145   protected boolean isLibraryFile(final String fileName) {
146     return StringUtil.endsWithIgnoreCase(fileName, ".jar") || StringUtil.endsWithIgnoreCase(fileName, ".zip");
147   }
148
149   @Override
150   protected void scanSourceFileForImportedPackages(final CharSequence chars, final Consumer<String> result) {
151     myLexer.start(chars);
152
153     JavaSourceRootDetectionUtil.skipWhiteSpaceAndComments(myLexer);
154     if (myLexer.getTokenType() == JavaTokenType.PACKAGE_KEYWORD) {
155       advanceLexer(myLexer);
156       if (readPackageName(chars, myLexer) == null) {
157         return;
158       }
159     }
160
161     while (true) {
162       if (myLexer.getTokenType() == JavaTokenType.SEMICOLON) {
163         advanceLexer(myLexer);
164       }
165       if (myLexer.getTokenType() != JavaTokenType.IMPORT_KEYWORD) {
166         return;
167       }
168       advanceLexer(myLexer);
169
170       boolean isStaticImport = false;
171       if (myLexer.getTokenType() == JavaTokenType.STATIC_KEYWORD) {
172         isStaticImport = true;
173         advanceLexer(myLexer);
174       }
175
176       final String packageName = readPackageName(chars, myLexer);
177       if (packageName == null) {
178         return;
179       }
180
181       if (packageName.endsWith(".*")) {
182         result.consume(packageName.substring(0, packageName.length() - ".*".length()));
183       }
184       else {
185         int lastDot = packageName.lastIndexOf('.');
186         if (lastDot > 0) {
187           String _packageName = packageName.substring(0, lastDot);
188           if (isStaticImport) {
189             lastDot = _packageName.lastIndexOf('.');
190             if (lastDot > 0) {
191               result.consume(_packageName.substring(0, lastDot));
192             }
193           }
194           else {
195             result.consume(_packageName);
196           }
197         }
198       }
199     }
200   }
201
202   @Nullable
203   private static String readPackageName(final CharSequence text, final Lexer lexer) {
204     final StringBuilder buffer = new StringBuilder();
205     while (true) {
206       if (lexer.getTokenType() != JavaTokenType.IDENTIFIER && lexer.getTokenType() != JavaTokenType.ASTERISK) {
207         break;
208       }
209       buffer.append(text, lexer.getTokenStart(), lexer.getTokenEnd());
210
211       advanceLexer(lexer);
212       if (lexer.getTokenType() != JavaTokenType.DOT) {
213         break;
214       }
215       buffer.append('.');
216
217       advanceLexer(lexer);
218     }
219
220     String packageName = buffer.toString();
221     if (packageName.length() == 0 || StringUtil.endsWithChar(packageName, '.') || StringUtil.startsWithChar(packageName, '*')) {
222       return null;
223     }
224     return packageName;
225   }
226
227   private static void advanceLexer(final Lexer lexer) {
228     lexer.advance();
229     JavaSourceRootDetectionUtil.skipWhiteSpaceAndComments(lexer);
230   }
231
232   @Override
233   protected void scanLibraryForDeclaredPackages(File file, Consumer<String> result) throws IOException {
234     try (ZipFile zip = new ZipFile(file)) {
235       final Enumeration<? extends ZipEntry> entries = zip.entries();
236       while (entries.hasMoreElements()) {
237         final String entryName = entries.nextElement().getName();
238         if (StringUtil.endsWithIgnoreCase(entryName, ".class")) {
239           final int index = entryName.lastIndexOf('/');
240           if (index > 0) {
241             final String packageName = entryName.substring(0, index).replace('/', '.');
242             result.consume(packageName);
243           }
244         }
245       }
246     }
247   }
248
249   @Override
250   protected ModuleDescriptor createModuleDescriptor(final File moduleContentRoot, final Collection<DetectedSourceRoot> sourceRoots) {
251     return new ModuleDescriptor(moduleContentRoot, StdModuleTypes.JAVA, sourceRoots);
252   }
253
254   @Override
255   public boolean isApplicableRoot(final DetectedProjectRoot root) {
256     return root instanceof JavaModuleSourceRoot;
257   }
258
259   private static final class ModuleInfo {
260     final String name;
261     final Set<String> requiresModules = new HashSet<>();
262     final Set<String> exportsPackages = new HashSet<>();
263
264     File directory;
265     ModuleDescriptor descriptor;
266
267     private ModuleInfo(@NotNull String name) {
268       this.name = name;
269     }
270   }
271
272   private static class ModuleInfoVisitor extends JavaRecursiveElementVisitor {
273     private final ModuleInfo myInfo;
274
275     ModuleInfoVisitor(ModuleInfo info) {
276       myInfo = info;
277     }
278
279     @Override
280     public void visitRequiresStatement(PsiRequiresStatement statement) {
281       super.visitRequiresStatement(statement);
282       String referenceText = statement.getModuleName();
283       if (referenceText != null) {
284         myInfo.requiresModules.add(referenceText);
285       }
286     }
287
288     @Override
289     public void visitPackageAccessibilityStatement(PsiPackageAccessibilityStatement statement) {
290       super.visitPackageAccessibilityStatement(statement);
291       if (statement.getRole() == PsiPackageAccessibilityStatement.Role.EXPORTS) {
292         PsiJavaCodeReferenceElement reference = statement.getPackageReference();
293         if (reference != null) {
294           String qualifiedName = reference.getQualifiedName();
295           if (qualifiedName != null) {
296             myInfo.exportsPackages.add(qualifiedName);
297           }
298         }
299       }
300     }
301   }
302 }