70ac76f3210dd17a6734bb610ce2f2720a32a044
[idea/community.git] / plugins / groovy / groovy-psi / src / org / jetbrains / plugins / groovy / lang / psi / impl / GroovyFileImpl.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.jetbrains.plugins.groovy.lang.psi.impl;
18
19 import com.intellij.lang.ASTNode;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.psi.*;
24 import com.intellij.psi.impl.PsiFileEx;
25 import com.intellij.psi.impl.source.resolve.SymbolCollectingProcessor.ResultWithContext;
26 import com.intellij.psi.scope.BaseScopeProcessor;
27 import com.intellij.psi.scope.ElementClassHint;
28 import com.intellij.psi.scope.NameHint;
29 import com.intellij.psi.scope.PsiScopeProcessor;
30 import com.intellij.psi.stubs.StubElement;
31 import com.intellij.psi.util.*;
32 import com.intellij.util.IncorrectOperationException;
33 import com.intellij.util.Processor;
34 import com.intellij.util.containers.ContainerUtil;
35 import com.intellij.util.containers.MostlySingularMultiMap;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38 import org.jetbrains.plugins.groovy.GroovyLanguage;
39 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
40 import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
41 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
42 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
43 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrTopLevelDefinition;
44 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
45 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
46 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
47 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
48 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
49 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.GrTopStatement;
50 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
51 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition;
52 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
53 import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyImportHelper.ImportKind;
54 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrBindingVariable;
55 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightParameter;
56 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
57 import org.jetbrains.plugins.groovy.lang.psi.stubs.GrFileStub;
58 import org.jetbrains.plugins.groovy.lang.psi.stubs.GrPackageDefinitionStub;
59 import org.jetbrains.plugins.groovy.lang.resolve.ImplicitImportsKt;
60 import org.jetbrains.plugins.groovy.lang.resolve.MethodTypeInferencer;
61 import org.jetbrains.plugins.groovy.lang.resolve.PackageSkippingProcessor;
62 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
63 import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
64 import org.jetbrains.plugins.groovy.lang.resolve.processors.GroovyResolverProcessor;
65
66 import java.util.concurrent.ConcurrentMap;
67
68 /**
69  * Implements all abstractions related to Groovy file
70  *
71  * @author ilyas
72  */
73 public class GroovyFileImpl extends GroovyFileBaseImpl implements GroovyFile {
74   private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.lang.psi.impl.GroovyFileImpl");
75
76   private static final String SYNTHETIC_PARAMETER_NAME = "args";
77
78   private static final CachedValueProvider<ConcurrentMap<String, GrBindingVariable>> BINDING_PROVIDER = () -> {
79     final ConcurrentMap<String, GrBindingVariable> map = ContainerUtil.newConcurrentMap();
80     return CachedValueProvider.Result.create(map, PsiModificationTracker.MODIFICATION_COUNT);
81   };
82
83   private volatile Boolean myScript;
84   private volatile GroovyScriptClass myScriptClass;
85   private volatile GrParameter mySyntheticArgsParameter;
86   private volatile PsiElement myContext;
87   private final CachedValue<MostlySingularMultiMap<String, ResultWithContext>> myResolveCache;
88
89   public GroovyFileImpl(FileViewProvider viewProvider) {
90     super(viewProvider, GroovyLanguage.INSTANCE);
91     myResolveCache = CachedValuesManager.getManager(myManager.getProject()).createCachedValue(
92       () -> CachedValueProvider.Result.create(buildDeclarationCache(), PsiModificationTracker.MODIFICATION_COUNT, this), false);
93   }
94
95   @Override
96   @NotNull
97   public String getPackageName() {
98     GrPackageDefinition packageDef = getPackageDefinition();
99     if (packageDef != null) {
100       final String name = packageDef.getPackageName();
101       if (name != null) {
102         return name;
103       }
104     }
105     return "";
106   }
107
108   @Override
109   public GrPackageDefinition getPackageDefinition() {
110     final StubElement<?> stub = getStub();
111     if (stub != null) {
112       for (StubElement element : stub.getChildrenStubs()) {
113         if (element instanceof GrPackageDefinitionStub) return (GrPackageDefinition)element.getPsi();
114       }
115       return null;
116     }
117
118     ASTNode node = calcTreeElement().findChildByType(GroovyElementTypes.PACKAGE_DEFINITION);
119     return node != null ? (GrPackageDefinition)node.getPsi() : null;
120   }
121
122   private GrParameter getSyntheticArgsParameter() {
123     GrParameter parameter = mySyntheticArgsParameter;
124     if (parameter == null) {
125       final PsiType psiType = JavaPsiFacade.getElementFactory(getProject()).createTypeFromText("java.lang.String[]", this);
126       parameter = new GrLightParameter(SYNTHETIC_PARAMETER_NAME, psiType, this);
127       mySyntheticArgsParameter = parameter;
128     }
129     return parameter;
130   }
131
132   @Override
133   public boolean processDeclarations(@NotNull final PsiScopeProcessor processor,
134                                      @NotNull ResolveState state,
135                                      @Nullable PsiElement lastParent,
136                                      @NotNull PsiElement place) {
137
138     if (isPhysical() && !isScript() &&
139         (getUserData(PsiFileEx.BATCH_REFERENCE_PROCESSING) == Boolean.TRUE || myResolveCache.hasUpToDateValue())) {
140       return processCachedDeclarations(processor, state, myResolveCache.getValue());
141     }
142
143     return processDeclarationsNoGuess(processor, state, lastParent, place);
144   }
145
146   private static boolean processCachedDeclarations(@NotNull PsiScopeProcessor processor,
147                                                    @NotNull ResolveState state,
148                                                    MostlySingularMultiMap<String, ResultWithContext> cache) {
149     for (PsiScopeProcessor each : GroovyResolverProcessor.allProcessors(processor)) {
150       String name = ResolveUtil.getNameHint(each);
151       Processor<ResultWithContext> cacheProcessor = res -> each.execute(
152         res.getElement(), state.put(ClassHint.RESOLVE_CONTEXT, res.getFileContext())
153       );
154       boolean result = name != null ? cache.processForKey(name, cacheProcessor) : cache.processAllValues(cacheProcessor);
155       if (!result) return false;
156     }
157     return true;
158   }
159
160   @NotNull
161   private MostlySingularMultiMap<String, ResultWithContext> buildDeclarationCache() {
162     MostlySingularMultiMap<String, ResultWithContext> results = new MostlySingularMultiMap<>();
163     processDeclarationsNoGuess(new BaseScopeProcessor() {
164       @Override
165       public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
166         if (element instanceof PsiNamedElement) {
167           PsiElement context = state.get(ClassHint.RESOLVE_CONTEXT);
168           String name = getDeclarationName((PsiNamedElement)element, context);
169           if (name != null) {
170             results.add(name, new ResultWithContext((PsiNamedElement)element, context));
171           }
172         }
173         return true;
174       }
175
176       private String getDeclarationName(@NotNull PsiNamedElement element, @Nullable PsiElement context) {
177         String name = context instanceof GrImportStatement ? ((GrImportStatement)context).getImportedName() : null;
178         return name != null ? name : element.getName();
179       }
180     }, ResolveState.initial(), null, this);
181     return results;
182   }
183
184   private boolean processDeclarationsNoGuess(@NotNull PsiScopeProcessor processor,
185                                              @NotNull ResolveState state,
186                                              @Nullable PsiElement lastParent, @NotNull PsiElement place) {
187     ElementClassHint classHint = processor.getHint(ElementClassHint.KEY);
188
189     if (myContext != null) {
190       if (ResolveUtil.shouldProcessProperties(classHint)) {
191         if (!processChildrenScopes(processor, state, lastParent, place)) return false;
192       }
193       return true;
194     }
195
196     boolean processClasses = ResolveUtil.shouldProcessClasses(classHint);
197
198     GrImportStatement[] importStatements = getImportStatements();
199     if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.ALIAS, false)) return false;
200
201     GroovyScriptClass scriptClass = getScriptClass();
202     if (scriptClass != null && StringUtil.isJavaIdentifier(scriptClass.getName())) {
203
204       if (!(lastParent instanceof GrTypeDefinition)) {
205         if (!ResolveUtil.processClassDeclarations(scriptClass, processor, state, lastParent, place)) return false;
206       }
207
208       if (processClasses) {
209         if (!ResolveUtil.processElement(processor, scriptClass, state)) return false;
210       }
211     }
212
213     if (processClasses) {
214       for (GrTypeDefinition definition : getTypeDefinitions()) {
215         if (!ResolveUtil.processElement(processor, definition, state)) return false;
216       }
217     }
218
219     if (ResolveUtil.shouldProcessProperties(classHint)) {
220       if (!processChildrenScopes(processor, state, lastParent, place)) return false;
221     }
222
223     if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.ALIAS, true)) return false;
224     if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.SIMPLE, null)) return false;
225     if (!processDeclarationsInPackage(processor, state, lastParent, place)) return false;
226     if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.ON_DEMAND, null)) return false;
227     if (!ImplicitImportsKt.processImplicitImports(processor, state, lastParent, place, this)) return false;
228
229     if (ResolveUtil.shouldProcessPackages(classHint)) {
230
231       NameHint nameHint = processor.getHint(NameHint.KEY);
232       String expectedName = nameHint != null ? nameHint.getName(state) : null;
233
234       final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
235       if (expectedName != null) {
236         final PsiPackage pkg = facade.findPackage(expectedName);
237         if (pkg != null && !processor.execute(pkg, state)) {
238           return false;
239         }
240       }
241       else {
242         PsiPackage defaultPackage = facade.findPackage("");
243         if (defaultPackage != null) {
244           for (PsiPackage subPackage : defaultPackage.getSubPackages(getResolveScope())) {
245             if (!ResolveUtil.processElement(processor, subPackage, state)) return false;
246           }
247         }
248       }
249     }
250
251     if (ResolveUtil.shouldProcessProperties(classHint)) {
252       if (lastParent != null && !(lastParent instanceof GrTypeDefinition) && scriptClass != null) {
253         if (!ResolveUtil.processElement(processor, getSyntheticArgsParameter(), state)) return false;
254       }
255     }
256
257     return true;
258   }
259
260   public boolean isInScriptBody(PsiElement lastParent, PsiElement place) {
261     return isScript() &&
262         !(lastParent instanceof GrTypeDefinition) &&
263         PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class, false) == null;
264   }
265
266   protected boolean processImports(PsiScopeProcessor processor,
267                                    @NotNull ResolveState state,
268                                    @Nullable PsiElement lastParent,
269                                    @NotNull PsiElement place,
270                                    @NotNull GrImportStatement[] importStatements,
271                                    @NotNull ImportKind kind,
272                                    @Nullable Boolean processStatic) {
273     return GroovyImportHelper.processImports(state, lastParent, place, processor, importStatements, kind, processStatic);
274   }
275
276   @NotNull
277   public ConcurrentMap<String, GrBindingVariable> getBindings() {
278     return CachedValuesManager.getCachedValue(this, BINDING_PROVIDER);
279   }
280
281   @Override
282   public boolean isTopControlFlowOwner() {
283     return true;
284   }
285
286   private boolean processDeclarationsInPackage(@NotNull PsiScopeProcessor processor,
287                                                @NotNull ResolveState state,
288                                                @Nullable PsiElement lastParent,
289                                                @NotNull PsiElement place) {
290     if (ResolveUtil.shouldProcessClasses(processor.getHint(ElementClassHint.KEY))) {
291       PsiPackage aPackage = JavaPsiFacade.getInstance(getProject()).findPackage(getPackageName());
292       if (aPackage != null) {
293         return aPackage.processDeclarations(new PackageSkippingProcessor(processor), state, lastParent, place);
294       }
295     }
296     return true;
297   }
298
299   private boolean processChildrenScopes(@NotNull PsiScopeProcessor processor,
300                                         @NotNull ResolveState state,
301                                         @Nullable PsiElement lastParent,
302                                         @NotNull PsiElement place) {
303     final StubElement<?> stub = getStub();
304     if (stub != null) {
305       return true; // only local usages are traversed here. Having a stub means the clients are outside and won't see our variables
306     }
307
308     PsiElement run = lastParent == null ? getLastChild() : lastParent.getPrevSibling();
309     while (run != null) {
310       if (shouldProcess(lastParent, run) &&
311           !run.processDeclarations(processor, state, null, place)) {
312         return false;
313       }
314       run = run.getPrevSibling();
315     }
316
317     return true;
318   }
319
320   private static boolean shouldProcess(@Nullable PsiElement lastParent, @NotNull PsiElement run) {
321     return run instanceof GrAssignmentExpression // binding variables
322            || !(run instanceof GrTopLevelDefinition || run instanceof GrImportStatement || lastParent instanceof GrMember);
323   }
324
325   @Override
326   public GrImportStatement[] getImportStatements() {
327     final StubElement<?> stub = getStub();
328     if (stub != null) {
329       return stub.getChildrenByType(GroovyElementTypes.IMPORT_STATEMENT, GrImportStatement.ARRAY_FACTORY);
330     }
331
332     return calcTreeElement().getChildrenAsPsiElements(GroovyElementTypes.IMPORT_STATEMENT, GrImportStatement.ARRAY_FACTORY);
333   }
334
335   @Override
336   public GrImportStatement addImportForClass(@NotNull PsiClass aClass) {
337     try {
338       // Calculating position
339       GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject());
340
341       String qname = aClass.getQualifiedName();
342       if (qname != null) {
343         GrImportStatement importStatement = factory.createImportStatementFromText(qname, false, false, null);
344         return addImport(importStatement);
345       }
346     }
347     catch (IncorrectOperationException e) {
348       LOG.error(e);
349     }
350     return null;
351   }
352
353
354   @NotNull
355   @Override
356   public GrImportStatement addImport(@NotNull GrImportStatement statement) throws IncorrectOperationException {
357     return GroovyCodeStyleManager.getInstance(getProject()).addImport(this, statement);
358   }
359
360   @Override
361   public boolean isScript() {
362     final StubElement stub = getStub();
363     if (stub instanceof GrFileStub) {
364       return ((GrFileStub)stub).isScript();
365     }
366
367     Boolean isScript = myScript;
368     if (isScript == null) {
369       isScript = checkIsScript();
370       myScript = isScript;
371     }
372     return isScript;
373   }
374
375   private boolean checkIsScript() {
376     final GrTopStatement[] topStatements = findChildrenByClass(GrTopStatement.class);
377     boolean hasClassDefinitions = false;
378     boolean hasTopStatements = false;
379     for (GrTopStatement st : topStatements) {
380       if (st instanceof GrTypeDefinition) {
381         hasClassDefinitions = true;
382       }
383       else if (!(st instanceof GrImportStatement || st instanceof GrPackageDefinition)) {
384         hasTopStatements = true;
385         break;
386       }
387     }
388     return hasTopStatements || !hasClassDefinitions;
389   }
390
391   @Override
392   public void subtreeChanged() {
393     myScript = null;
394     super.subtreeChanged();
395   }
396
397   @Override
398   public GroovyScriptClass getScriptClass() {
399     if (!isScript()) {
400       return null;
401     }
402
403     GroovyScriptClass aClass = myScriptClass;
404     if (aClass == null) {
405       aClass = new GroovyScriptClass(this);
406       myScriptClass = aClass;
407     }
408
409     return aClass;
410   }
411
412   @Override
413   public void setPackageName(String packageName) {
414     final ASTNode fileNode = getNode();
415     final GrPackageDefinition currentPackage = getPackageDefinition();
416     if (packageName == null || packageName.isEmpty()) {
417       if (currentPackage != null) {
418         final ASTNode currNode = currentPackage.getNode();
419         fileNode.removeChild(currNode);
420       }
421       return;
422     }
423
424     final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject());
425     final GrPackageDefinition newPackage = (GrPackageDefinition)factory.createTopElementFromText("package " + packageName);
426
427     if (currentPackage != null) {
428       final GrCodeReferenceElement packageReference = currentPackage.getPackageReference();
429       if (packageReference != null) {
430         GrCodeReferenceElement ref = newPackage.getPackageReference();
431         if (ref != null) {
432           packageReference.replace(ref);
433         }
434         return;
435       }
436     }
437
438     final ASTNode newNode = newPackage.getNode();
439     if (currentPackage != null) {
440       final ASTNode currNode = currentPackage.getNode();
441       fileNode.replaceChild(currNode, newNode);
442     } else {
443       ASTNode anchor = fileNode.getFirstChildNode();
444       if (anchor != null && anchor.getElementType() == GroovyTokenTypes.mSH_COMMENT) {
445         anchor = anchor.getTreeNext();
446         fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
447       }
448       fileNode.addChild(newNode, anchor);
449       if (anchor != null && !anchor.getText().startsWith("\n\n")) {
450         fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
451       }
452     }
453   }
454
455   @Nullable
456   @Override
457   public GrPackageDefinition setPackage(@Nullable GrPackageDefinition newPackage) {
458     final GrPackageDefinition oldPackage = getPackageDefinition();
459     if (oldPackage == null) {
460       if (newPackage != null) {
461         final GrPackageDefinition result = (GrPackageDefinition)addAfter(newPackage, null);
462         getNode().addLeaf(GroovyTokenTypes.mNLS, "\n", result.getNode().getTreeNext());
463         return result;
464       }
465     }
466     else {
467       if (newPackage != null) {
468         return (GrPackageDefinition)oldPackage.replace(newPackage);
469       }
470       else {
471         oldPackage.delete();
472       }
473     }
474     return null;
475   }
476
477   @Override
478   public PsiType getInferredScriptReturnType() {
479     return CachedValuesManager.getCachedValue(this, () -> CachedValueProvider.Result
480       .create(GroovyPsiManager.inferType(this, new MethodTypeInferencer(this)),
481               PsiModificationTracker.MODIFICATION_COUNT));
482   }
483
484   @Override
485   public void clearCaches() {
486     super.clearCaches();
487     myScriptClass = null;
488     mySyntheticArgsParameter = null;
489   }
490
491   @Override
492   public PsiElement getContext() {
493     return myContext != null && myContext.isValid() ? myContext : super.getContext();
494   }
495
496   @Override
497   @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
498   protected GroovyFileImpl clone() {
499     GroovyFileImpl clone = (GroovyFileImpl)super.clone();
500     clone.myContext = myContext;
501     return clone;
502   }
503
504   public void setContext(PsiElement context) {
505     if (context != null) {
506       myContext = context;
507     }
508   }
509
510   public void setContextNullable(PsiElement context) {
511     myContext = context;
512   }
513
514   @Override
515   @NotNull
516   public PsiClass[] getClasses() {
517     final PsiClass[] declaredDefs = super.getClasses();
518     if (!isScript()) return declaredDefs;
519     final PsiClass scriptClass = getScriptClass();
520     PsiClass[] result = new PsiClass[declaredDefs.length + 1];
521     result[result.length - 1] = scriptClass;
522     System.arraycopy(declaredDefs, 0, result, 0, declaredDefs.length);
523     return result;
524   }
525
526   @Override
527   public PsiElement getOriginalElement() {
528     final PsiClass scriptClass = getScriptClass();
529     if (scriptClass != null) {
530       final PsiElement originalElement = scriptClass.getOriginalElement();
531       if (originalElement != scriptClass && originalElement != null) {
532         return originalElement.getContainingFile();
533       }
534     }
535     return this;
536   }
537
538   @NotNull
539   @Override
540   public GrVariableDeclaration[] getScriptDeclarations(boolean topLevelOnly) {
541     return PsiImplUtilKt.getScriptDeclarations(this, topLevelOnly);
542   }
543
544   @Override
545   public String toString() {
546     if (ApplicationManager.getApplication().isUnitTestMode()){
547       return super.toString();
548     }
549     return "GroovyFileImpl:" + getName();
550   }
551 }
552