fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / PsiJavaParserFacadeImpl.java
1 // Copyright 2000-2019 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.psi.impl;
3
4 import com.intellij.ide.highlighter.JavaFileType;
5 import com.intellij.lang.java.parser.DeclarationParser;
6 import com.intellij.lang.java.parser.JavaParser;
7 import com.intellij.lang.java.parser.JavaParserUtil;
8 import com.intellij.lang.java.parser.ReferenceParser;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.openapi.roots.LanguageLevelProjectExtension;
11 import com.intellij.openapi.util.text.StringUtil;
12 import com.intellij.pom.java.LanguageLevel;
13 import com.intellij.psi.*;
14 import com.intellij.psi.impl.source.DummyHolder;
15 import com.intellij.psi.impl.source.DummyHolderFactory;
16 import com.intellij.psi.impl.source.JavaDummyElement;
17 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
18 import com.intellij.psi.impl.source.tree.FileElement;
19 import com.intellij.psi.impl.source.tree.TreeElement;
20 import com.intellij.psi.javadoc.PsiDocComment;
21 import com.intellij.psi.javadoc.PsiDocTag;
22 import com.intellij.psi.util.PsiTreeUtil;
23 import com.intellij.psi.util.PsiUtil;
24 import com.intellij.util.IncorrectOperationException;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.annotations.Nullable;
27
28 import java.util.HashMap;
29 import java.util.Map;
30
31 /**
32  * @author max
33  */
34 public class PsiJavaParserFacadeImpl implements PsiJavaParserFacade {
35   private static final String DUMMY_FILE_NAME = "_Dummy_." + JavaFileType.INSTANCE.getDefaultExtension();
36
37   private static final JavaParserUtil.ParserWrapper ANNOTATION =
38     builder -> JavaParser.INSTANCE.getDeclarationParser().parseAnnotation(builder);
39
40   private static final JavaParserUtil.ParserWrapper PARAMETER =
41     builder -> JavaParser.INSTANCE.getDeclarationParser().parseParameter(builder, true, false, false);
42
43   private static final JavaParserUtil.ParserWrapper RESOURCE = builder -> JavaParser.INSTANCE.getDeclarationParser().parseResource(builder);
44
45   private static final JavaParserUtil.ParserWrapper TYPE = builder -> {
46     int flags = ReferenceParser.EAT_LAST_DOT | ReferenceParser.ELLIPSIS | ReferenceParser.WILDCARD | ReferenceParser.DISJUNCTIONS | ReferenceParser.VAR_TYPE;
47     JavaParser.INSTANCE.getReferenceParser().parseType(builder, flags);
48   };
49
50   public static final JavaParserUtil.ParserWrapper REFERENCE =
51     builder -> JavaParser.INSTANCE.getReferenceParser().parseJavaCodeReference(builder, false, true, false, false);
52
53   private static final JavaParserUtil.ParserWrapper DIAMOND_REF =
54     builder -> JavaParser.INSTANCE.getReferenceParser().parseJavaCodeReference(builder, false, true, false, true);
55
56   private static final JavaParserUtil.ParserWrapper STATIC_IMPORT_REF =
57     builder -> JavaParser.INSTANCE.getReferenceParser().parseImportCodeReference(builder, true);
58
59   private static final JavaParserUtil.ParserWrapper TYPE_PARAMETER =
60     builder -> JavaParser.INSTANCE.getReferenceParser().parseTypeParameter(builder);
61
62   private static final JavaParserUtil.ParserWrapper DECLARATION =
63     builder -> JavaParser.INSTANCE.getDeclarationParser().parse(builder, DeclarationParser.Context.CLASS);
64
65   private static final JavaParserUtil.ParserWrapper CODE_BLOCK =
66     builder -> JavaParser.INSTANCE.getStatementParser().parseCodeBlockDeep(builder, true);
67
68   private static final JavaParserUtil.ParserWrapper STATEMENT = builder -> JavaParser.INSTANCE.getStatementParser().parseStatement(builder);
69
70   private static final JavaParserUtil.ParserWrapper EXPRESSION = builder -> JavaParser.INSTANCE.getExpressionParser().parse(builder);
71
72   private static final JavaParserUtil.ParserWrapper ENUM_CONSTANT =
73     builder -> JavaParser.INSTANCE.getDeclarationParser().parseEnumConstant(builder);
74
75   private static final JavaParserUtil.ParserWrapper MODULE = builder -> JavaParser.INSTANCE.getModuleParser().parse(builder);
76
77   private static final Map<String, PsiPrimitiveType> PRIMITIVE_TYPES;
78   static {
79     PRIMITIVE_TYPES = new HashMap<>();
80     PRIMITIVE_TYPES.put(PsiType.BYTE.getCanonicalText(), PsiType.BYTE);
81     PRIMITIVE_TYPES.put(PsiType.CHAR.getCanonicalText(), PsiType.CHAR);
82     PRIMITIVE_TYPES.put(PsiType.DOUBLE.getCanonicalText(), PsiType.DOUBLE);
83     PRIMITIVE_TYPES.put(PsiType.FLOAT.getCanonicalText(), PsiType.FLOAT);
84     PRIMITIVE_TYPES.put(PsiType.INT.getCanonicalText(), PsiType.INT);
85     PRIMITIVE_TYPES.put(PsiType.LONG.getCanonicalText(), PsiType.LONG);
86     PRIMITIVE_TYPES.put(PsiType.SHORT.getCanonicalText(), PsiType.SHORT);
87     PRIMITIVE_TYPES.put(PsiType.BOOLEAN.getCanonicalText(), PsiType.BOOLEAN);
88     PRIMITIVE_TYPES.put(PsiType.VOID.getCanonicalText(), PsiType.VOID);
89     PRIMITIVE_TYPES.put(PsiType.NULL.getCanonicalText(), PsiType.NULL);
90   }
91
92   protected final PsiManager myManager;
93
94   public PsiJavaParserFacadeImpl(@NotNull Project project) {
95     myManager = PsiManager.getInstance(project);
96   }
97
98   protected PsiJavaFile createDummyJavaFile(String text) {
99     return (PsiJavaFile)PsiFileFactory.getInstance(myManager.getProject()).createFileFromText(DUMMY_FILE_NAME, JavaFileType.INSTANCE, text);
100   }
101
102   @NotNull
103   @Override
104   public PsiAnnotation createAnnotationFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
105     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, ANNOTATION, level(context)), context);
106     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
107     if (!(element instanceof PsiAnnotation)) {
108       throw newException("Incorrect annotation '" + text + "'", holder);
109     }
110     return (PsiAnnotation)element;
111   }
112
113   @NotNull
114   @Override
115   public PsiDocTag createDocTagFromText(@NotNull String text) throws IncorrectOperationException {
116     return createDocCommentFromText(StringUtil.join("/**\n", text, "\n */")).getTags()[0];
117   }
118
119   @NotNull
120   @Override
121   public PsiDocComment createDocCommentFromText(@NotNull String text) throws IncorrectOperationException {
122     return createDocCommentFromText(text, null);
123   }
124
125   @NotNull
126   @Override
127   public PsiDocComment createDocCommentFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
128     PsiMethod method = createMethodFromText(text.trim() + "void m();", context);
129     PsiDocComment comment = method.getDocComment();
130     if (comment == null) {
131       throw new IncorrectOperationException("Incorrect comment '" + text + "'");
132     }
133     return comment;
134   }
135
136   @NotNull
137   @Override
138   public PsiClass createClassFromText(@NotNull String body, @Nullable PsiElement context) throws IncorrectOperationException {
139     PsiJavaFile aFile = createDummyJavaFile(StringUtil.join("class _Dummy_ {\n", body, "\n}"));
140     PsiClass[] classes = aFile.getClasses();
141     if (classes.length != 1) {
142       throw new IncorrectOperationException("Incorrect class '" + body + "'");
143     }
144     return classes[0];
145   }
146
147   @NotNull
148   @Override
149   public PsiField createFieldFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
150     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, DECLARATION, level(context)), context);
151     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
152     if (!(element instanceof PsiField)) {
153       throw newException("Incorrect field '" + text + "'", holder);
154     }
155     return (PsiField)element;
156   }
157
158   @NotNull
159   @Override
160   public PsiMethod createMethodFromText(@NotNull String text, @Nullable PsiElement context, LanguageLevel level) throws IncorrectOperationException {
161     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, DECLARATION, level), context);
162     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
163     if (!(element instanceof PsiMethod)) {
164       throw newException("Incorrect method '" + text + "'", holder);
165     }
166     return (PsiMethod)element;
167   }
168
169   @NotNull
170   @Override
171   public PsiMethod createMethodFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
172     LanguageLevel level = LanguageLevelProjectExtension.getInstance(myManager.getProject()).getLanguageLevel();
173     return createMethodFromText(text, context, level);
174   }
175
176   @NotNull
177   @Override
178   public PsiParameter createParameterFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
179     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, PARAMETER, level(context)), context);
180     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
181     if (!(element instanceof PsiParameter)) {
182       throw newException("Incorrect parameter '" + text + "'", holder);
183     }
184     return (PsiParameter)element;
185   }
186
187   @NotNull
188   @Override
189   public PsiResourceVariable createResourceFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
190     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, RESOURCE, level(context)), context);
191     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
192     if (!(element instanceof PsiResourceVariable)) {
193       throw newException("Incorrect resource '" + text + "'", holder);
194     }
195     return (PsiResourceVariable)element;
196   }
197
198   @NotNull
199   @Override
200   public PsiType createTypeFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
201     return createTypeInner(text, context, false);
202   }
203
204   @NotNull
205   @Override
206   public PsiTypeElement createTypeElementFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
207     LanguageLevel level = level(context);
208     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, TYPE, level), context);
209     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
210     if (!(element instanceof PsiTypeElement)) {
211       throw newException("Incorrect type '" + text + "' (" + level + ")", holder);
212     }
213     return (PsiTypeElement)element;
214   }
215
216   PsiType createTypeInner(String text, @Nullable PsiElement context, boolean markAsCopy) throws IncorrectOperationException {
217     PsiPrimitiveType primitiveType = PRIMITIVE_TYPES.get(text);
218     if (primitiveType != null) return primitiveType;
219
220     PsiTypeElement element = createTypeElementFromText(text, context);
221     if (markAsCopy) {
222       GeneratedMarkerVisitor.markGenerated(element);
223     }
224     return element.getType();
225   }
226
227   @NotNull
228   @Override
229   public PsiJavaCodeReferenceElement createReferenceFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
230     boolean isStaticImport = context instanceof PsiImportStaticStatement && !((PsiImportStaticStatement)context).isOnDemand();
231     boolean mayHaveDiamonds = context instanceof PsiNewExpression && PsiUtil.getLanguageLevel(context).isAtLeast(LanguageLevel.JDK_1_7);
232     JavaParserUtil.ParserWrapper wrapper = isStaticImport ? STATIC_IMPORT_REF : mayHaveDiamonds ? DIAMOND_REF : REFERENCE;
233     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, wrapper, level(context)), context);
234     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
235     if (!(element instanceof PsiJavaCodeReferenceElement)) {
236       throw newException("Incorrect reference '" + text + "'", holder);
237     }
238     return (PsiJavaCodeReferenceElement)element;
239   }
240
241   @NotNull
242   @Override
243   public PsiCodeBlock createCodeBlockFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
244     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, CODE_BLOCK, level(context), true), context);
245     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
246     if (!(element instanceof PsiCodeBlock)) {
247       throw newException("Incorrect code block '" + text + "'", holder);
248     }
249     return (PsiCodeBlock)element;
250   }
251
252   @NotNull
253   @Override
254   public PsiStatement createStatementFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
255     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, STATEMENT, level(context)), context);
256     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
257     if (!(element instanceof PsiStatement)) {
258       throw newException("Incorrect statement '" + text + "'", holder);
259     }
260     return (PsiStatement)element;
261   }
262
263   @NotNull
264   @Override
265   public PsiExpression createExpressionFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
266     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, EXPRESSION, level(context)), context);
267     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
268     if (!(element instanceof PsiExpression)) {
269       throw newException("Incorrect expression '" + text + "'", holder);
270     }
271     return (PsiExpression)element;
272   }
273
274   @NotNull
275   @Override
276   public PsiTypeParameter createTypeParameterFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
277     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, TYPE_PARAMETER, level(context)), context);
278     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
279     if (!(element instanceof PsiTypeParameter)) {
280       throw newException("Incorrect type parameter '" + text + "'", holder);
281     }
282     return (PsiTypeParameter)element;
283   }
284
285   @NotNull
286   @Override
287   public PsiComment createCommentFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
288     PsiJavaFile aFile = createDummyJavaFile(text);
289     for (PsiElement aChildren : aFile.getChildren()) {
290       if (aChildren instanceof PsiComment) {
291         if (!aChildren.getText().equals(text)) {
292           break;
293         }
294         PsiComment comment = (PsiComment)aChildren;
295         DummyHolderFactory.createHolder(myManager, (TreeElement)SourceTreeToPsiMap.psiElementToTree(comment), context);
296         return comment;
297       }
298     }
299
300     throw new IncorrectOperationException("Incorrect comment '" + text + "'");
301   }
302
303   @NotNull
304   @Override
305   public PsiEnumConstant createEnumConstantFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
306     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, ENUM_CONSTANT, level(context)), context);
307     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
308     if (!(element instanceof PsiEnumConstant)) {
309       throw newException("Incorrect enum constant '" + text + "'", holder);
310     }
311     return (PsiEnumConstant)element;
312   }
313
314   @Override
315   @NotNull
316   public PsiType createPrimitiveTypeFromText(@NotNull String text) throws IncorrectOperationException {
317     PsiPrimitiveType primitiveType = getPrimitiveType(text);
318     if (primitiveType == null) throw new IncorrectOperationException("Incorrect primitive type '" + text + "'");
319     return primitiveType;
320   }
321
322   @NotNull
323   @Override
324   public PsiJavaModule createModuleFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
325     DummyHolder holder = DummyHolderFactory.createHolder(myManager, new JavaDummyElement(text, MODULE, LanguageLevel.JDK_1_9), context);
326     PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
327     if (!(element instanceof PsiJavaModule)) {
328       throw newException("Incorrect module declaration '" + text + "'", holder);
329     }
330     return (PsiJavaModule)element;
331   }
332
333   @NotNull
334   @Override
335   public PsiStatement createModuleStatementFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
336     String template = "module M { " + text + "; }";
337     PsiJavaModule module = createModuleFromText(template, context);
338     PsiStatement statement = PsiTreeUtil.getChildOfType(module, PsiStatement.class);
339     if (statement == null) throw new IncorrectOperationException("Incorrect module statement '" + text + "'");
340     return statement;
341   }
342
343   @NotNull
344   @Override
345   public PsiJavaModuleReferenceElement createModuleReferenceFromText(@NotNull String text, @Nullable PsiElement context) throws IncorrectOperationException {
346     return createModuleFromText("module " + text + " {}", context).getNameIdentifier();
347   }
348
349   public static PsiPrimitiveType getPrimitiveType(String text) {
350     return PRIMITIVE_TYPES.get(text);
351   }
352
353   protected static LanguageLevel level(@Nullable PsiElement context) {
354     return context != null && context.isValid() ? PsiUtil.getLanguageLevel(context) : LanguageLevel.HIGHEST;
355   }
356
357   private static IncorrectOperationException newException(String msg, DummyHolder holder) {
358     FileElement root = holder.getTreeElement();
359     if (root instanceof JavaDummyElement) {
360       Throwable cause = ((JavaDummyElement)root).getParserError();
361       if (cause != null) {
362         return new IncorrectOperationException(msg, cause);
363       }
364     }
365     return new IncorrectOperationException(msg);
366   }
367 }