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