[groovy] do not increment Java Structure modification count in method
[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, PsiModifiableCodeBlock {
74
75   private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.lang.psi.impl.GroovyFileImpl");
76
77   private static final String SYNTHETIC_PARAMETER_NAME = "args";
78
79   private static final CachedValueProvider<ConcurrentMap<String, GrBindingVariable>> BINDING_PROVIDER = () -> {
80     final ConcurrentMap<String, GrBindingVariable> map = ContainerUtil.newConcurrentMap();
81     return CachedValueProvider.Result.create(map, PsiModificationTracker.MODIFICATION_COUNT);
82   };
83
84   private volatile Boolean myScript;
85   private volatile GroovyScriptClass myScriptClass;
86   private volatile GrParameter mySyntheticArgsParameter;
87   private volatile PsiElement myContext;
88   private final CachedValue<MostlySingularMultiMap<String, ResultWithContext>> myResolveCache;
89
90   public GroovyFileImpl(FileViewProvider viewProvider) {
91     super(viewProvider, GroovyLanguage.INSTANCE);
92     myResolveCache = CachedValuesManager.getManager(myManager.getProject()).createCachedValue(
93       () -> CachedValueProvider.Result.create(buildDeclarationCache(), PsiModificationTracker.MODIFICATION_COUNT, this), false);
94   }
95
96   @Override
97   @NotNull
98   public String getPackageName() {
99     GrPackageDefinition packageDef = getPackageDefinition();
100     if (packageDef != null) {
101       final String name = packageDef.getPackageName();
102       if (name != null) {
103         return name;
104       }
105     }
106     return "";
107   }
108
109   @Override
110   public GrPackageDefinition getPackageDefinition() {
111     final StubElement<?> stub = getStub();
112     if (stub != null) {
113       for (StubElement element : stub.getChildrenStubs()) {
114         if (element instanceof GrPackageDefinitionStub) return (GrPackageDefinition)element.getPsi();
115       }
116       return null;
117     }
118
119     ASTNode node = calcTreeElement().findChildByType(GroovyElementTypes.PACKAGE_DEFINITION);
120     return node != null ? (GrPackageDefinition)node.getPsi() : null;
121   }
122
123   private GrParameter getSyntheticArgsParameter() {
124     GrParameter parameter = mySyntheticArgsParameter;
125     if (parameter == null) {
126       final PsiType psiType = JavaPsiFacade.getElementFactory(getProject()).createTypeFromText("java.lang.String[]", this);
127       parameter = new GrLightParameter(SYNTHETIC_PARAMETER_NAME, psiType, this);
128       mySyntheticArgsParameter = parameter;
129     }
130     return parameter;
131   }
132
133   @Override
134   public boolean processDeclarations(@NotNull final PsiScopeProcessor processor,
135                                      @NotNull ResolveState state,
136                                      @Nullable PsiElement lastParent,
137                                      @NotNull PsiElement place) {
138
139     if (isPhysical() && !isScript() &&
140         (getUserData(PsiFileEx.BATCH_REFERENCE_PROCESSING) == Boolean.TRUE || myResolveCache.hasUpToDateValue())) {
141       return processCachedDeclarations(processor, state, myResolveCache.getValue());
142     }
143
144     return processDeclarationsNoGuess(processor, state, lastParent, place);
145   }
146
147   private static boolean processCachedDeclarations(@NotNull PsiScopeProcessor processor,
148                                                    @NotNull ResolveState state,
149                                                    MostlySingularMultiMap<String, ResultWithContext> cache) {
150     for (PsiScopeProcessor each : GroovyResolverProcessor.allProcessors(processor)) {
151       String name = ResolveUtil.getNameHint(each);
152       Processor<ResultWithContext> cacheProcessor = res -> each.execute(
153         res.getElement(), state.put(ClassHint.RESOLVE_CONTEXT, res.getFileContext())
154       );
155       boolean result = name != null ? cache.processForKey(name, cacheProcessor) : cache.processAllValues(cacheProcessor);
156       if (!result) return false;
157     }
158     return true;
159   }
160
161   @NotNull
162   private MostlySingularMultiMap<String, ResultWithContext> buildDeclarationCache() {
163     MostlySingularMultiMap<String, ResultWithContext> results = new MostlySingularMultiMap<>();
164     processDeclarationsNoGuess(new BaseScopeProcessor() {
165       @Override
166       public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
167         if (element instanceof PsiNamedElement) {
168           PsiElement context = state.get(ClassHint.RESOLVE_CONTEXT);
169           String name = getDeclarationName((PsiNamedElement)element, context);
170           if (name != null) {
171             results.add(name, new ResultWithContext((PsiNamedElement)element, context));
172           }
173         }
174         return true;
175       }
176
177       private String getDeclarationName(@NotNull PsiNamedElement element, @Nullable PsiElement context) {
178         String name = context instanceof GrImportStatement ? ((GrImportStatement)context).getImportedName() : null;
179         return name != null ? name : element.getName();
180       }
181     }, ResolveState.initial(), null, this);
182     return results;
183   }
184
185   private boolean processDeclarationsNoGuess(@NotNull PsiScopeProcessor processor,
186                                              @NotNull ResolveState state,
187                                              @Nullable PsiElement lastParent, @NotNull PsiElement place) {
188     ElementClassHint classHint = processor.getHint(ElementClassHint.KEY);
189
190     if (myContext != null) {
191       if (ResolveUtil.shouldProcessProperties(classHint)) {
192         if (!processChildrenScopes(processor, state, lastParent, place)) return false;
193       }
194       return true;
195     }
196
197     boolean processClasses = ResolveUtil.shouldProcessClasses(classHint);
198
199     GrImportStatement[] importStatements = getImportStatements();
200     if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.ALIAS, false)) return false;
201
202     GroovyScriptClass scriptClass = getScriptClass();
203     if (scriptClass != null && StringUtil.isJavaIdentifier(scriptClass.getName())) {
204
205       if (!(lastParent instanceof GrTypeDefinition)) {
206         if (!ResolveUtil.processClassDeclarations(scriptClass, processor, state, lastParent, place)) return false;
207       }
208
209       if (processClasses) {
210         if (!ResolveUtil.processElement(processor, scriptClass, state)) return false;
211       }
212     }
213
214     if (processClasses) {
215       for (GrTypeDefinition definition : getTypeDefinitions()) {
216         if (!ResolveUtil.processElement(processor, definition, state)) return false;
217       }
218     }
219
220     if (ResolveUtil.shouldProcessProperties(classHint)) {
221       if (!processChildrenScopes(processor, state, lastParent, place)) return false;
222     }
223
224     if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.ALIAS, true)) return false;
225     if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.SIMPLE, null)) return false;
226     if (!processDeclarationsInPackage(processor, state, lastParent, place)) return false;
227     if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.ON_DEMAND, null)) return false;
228     if (!ImplicitImportsKt.processImplicitImports(processor, state, lastParent, place, this)) return false;
229
230     if (ResolveUtil.shouldProcessPackages(classHint)) {
231
232       NameHint nameHint = processor.getHint(NameHint.KEY);
233       String expectedName = nameHint != null ? nameHint.getName(state) : null;
234
235       final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
236       if (expectedName != null) {
237         final PsiPackage pkg = facade.findPackage(expectedName);
238         if (pkg != null && !processor.execute(pkg, state)) {
239           return false;
240         }
241       }
242       else {
243         PsiPackage defaultPackage = facade.findPackage("");
244         if (defaultPackage != null) {
245           for (PsiPackage subPackage : defaultPackage.getSubPackages(getResolveScope())) {
246             if (!ResolveUtil.processElement(processor, subPackage, state)) return false;
247           }
248         }
249       }
250     }
251
252     if (ResolveUtil.shouldProcessProperties(classHint)) {
253       if (lastParent != null && !(lastParent instanceof GrTypeDefinition) && scriptClass != null) {
254         if (!ResolveUtil.processElement(processor, getSyntheticArgsParameter(), state)) return false;
255       }
256     }
257
258     return true;
259   }
260
261   public boolean isInScriptBody(PsiElement lastParent, PsiElement place) {
262     return isScript() &&
263         !(lastParent instanceof GrTypeDefinition) &&
264         PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class, false) == null;
265   }
266
267   protected boolean processImports(PsiScopeProcessor processor,
268                                    @NotNull ResolveState state,
269                                    @Nullable PsiElement lastParent,
270                                    @NotNull PsiElement place,
271                                    @NotNull GrImportStatement[] importStatements,
272                                    @NotNull ImportKind kind,
273                                    @Nullable Boolean processStatic) {
274     return GroovyImportHelper.processImports(state, lastParent, place, processor, importStatements, kind, processStatic);
275   }
276
277   @NotNull
278   public ConcurrentMap<String, GrBindingVariable> getBindings() {
279     return CachedValuesManager.getCachedValue(this, BINDING_PROVIDER);
280   }
281
282   @Override
283   public boolean isTopControlFlowOwner() {
284     return true;
285   }
286
287   private boolean processDeclarationsInPackage(@NotNull PsiScopeProcessor processor,
288                                                @NotNull ResolveState state,
289                                                @Nullable PsiElement lastParent,
290                                                @NotNull PsiElement place) {
291     if (ResolveUtil.shouldProcessClasses(processor.getHint(ElementClassHint.KEY))) {
292       PsiPackage aPackage = JavaPsiFacade.getInstance(getProject()).findPackage(getPackageName());
293       if (aPackage != null) {
294         return aPackage.processDeclarations(new PackageSkippingProcessor(processor), state, lastParent, place);
295       }
296     }
297     return true;
298   }
299
300   private boolean processChildrenScopes(@NotNull PsiScopeProcessor processor,
301                                         @NotNull ResolveState state,
302                                         @Nullable PsiElement lastParent,
303                                         @NotNull PsiElement place) {
304     final StubElement<?> stub = getStub();
305     if (stub != null) {
306       return true; // only local usages are traversed here. Having a stub means the clients are outside and won't see our variables
307     }
308
309     PsiElement run = lastParent == null ? getLastChild() : lastParent.getPrevSibling();
310     while (run != null) {
311       if (shouldProcess(lastParent, run) &&
312           !run.processDeclarations(processor, state, null, place)) {
313         return false;
314       }
315       run = run.getPrevSibling();
316     }
317
318     return true;
319   }
320
321   private static boolean shouldProcess(@Nullable PsiElement lastParent, @NotNull PsiElement run) {
322     return run instanceof GrAssignmentExpression // binding variables
323            || !(run instanceof GrTopLevelDefinition || run instanceof GrImportStatement || lastParent instanceof GrMember);
324   }
325
326   @Override
327   public GrImportStatement[] getImportStatements() {
328     final StubElement<?> stub = getStub();
329     if (stub != null) {
330       return stub.getChildrenByType(GroovyElementTypes.IMPORT_STATEMENT, GrImportStatement.ARRAY_FACTORY);
331     }
332
333     return calcTreeElement().getChildrenAsPsiElements(GroovyElementTypes.IMPORT_STATEMENT, GrImportStatement.ARRAY_FACTORY);
334   }
335
336   @Override
337   public GrImportStatement addImportForClass(@NotNull PsiClass aClass) {
338     try {
339       // Calculating position
340       GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject());
341
342       String qname = aClass.getQualifiedName();
343       if (qname != null) {
344         GrImportStatement importStatement = factory.createImportStatementFromText(qname, false, false, null);
345         return addImport(importStatement);
346       }
347     }
348     catch (IncorrectOperationException e) {
349       LOG.error(e);
350     }
351     return null;
352   }
353
354
355   @NotNull
356   @Override
357   public GrImportStatement addImport(@NotNull GrImportStatement statement) throws IncorrectOperationException {
358     return GroovyCodeStyleManager.getInstance(getProject()).addImport(this, statement);
359   }
360
361   @Override
362   public boolean isScript() {
363     final StubElement stub = getStub();
364     if (stub instanceof GrFileStub) {
365       return ((GrFileStub)stub).isScript();
366     }
367
368     Boolean isScript = myScript;
369     if (isScript == null) {
370       isScript = checkIsScript();
371       myScript = isScript;
372     }
373     return isScript;
374   }
375
376   private boolean checkIsScript() {
377     final GrTopStatement[] topStatements = findChildrenByClass(GrTopStatement.class);
378     boolean hasClassDefinitions = false;
379     boolean hasTopStatements = false;
380     for (GrTopStatement st : topStatements) {
381       if (st instanceof GrTypeDefinition) {
382         hasClassDefinitions = true;
383       }
384       else if (!(st instanceof GrImportStatement || st instanceof GrPackageDefinition)) {
385         hasTopStatements = true;
386         break;
387       }
388     }
389     return hasTopStatements || !hasClassDefinitions;
390   }
391
392   @Override
393   public void subtreeChanged() {
394     myScript = null;
395     super.subtreeChanged();
396   }
397
398   @Override
399   public GroovyScriptClass getScriptClass() {
400     if (!isScript()) {
401       return null;
402     }
403
404     GroovyScriptClass aClass = myScriptClass;
405     if (aClass == null) {
406       aClass = new GroovyScriptClass(this);
407       myScriptClass = aClass;
408     }
409
410     return aClass;
411   }
412
413   @Override
414   public void setPackageName(String packageName) {
415     final ASTNode fileNode = getNode();
416     final GrPackageDefinition currentPackage = getPackageDefinition();
417     if (packageName == null || packageName.isEmpty()) {
418       if (currentPackage != null) {
419         final ASTNode currNode = currentPackage.getNode();
420         fileNode.removeChild(currNode);
421       }
422       return;
423     }
424
425     final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject());
426     final GrPackageDefinition newPackage = (GrPackageDefinition)factory.createTopElementFromText("package " + packageName);
427
428     if (currentPackage != null) {
429       final GrCodeReferenceElement packageReference = currentPackage.getPackageReference();
430       if (packageReference != null) {
431         GrCodeReferenceElement ref = newPackage.getPackageReference();
432         if (ref != null) {
433           packageReference.replace(ref);
434         }
435         return;
436       }
437     }
438
439     final ASTNode newNode = newPackage.getNode();
440     if (currentPackage != null) {
441       final ASTNode currNode = currentPackage.getNode();
442       fileNode.replaceChild(currNode, newNode);
443     }
444     else {
445       ASTNode anchor = fileNode.getFirstChildNode();
446       if (anchor != null && anchor.getElementType() == GroovyTokenTypes.mSH_COMMENT) {
447         anchor = anchor.getTreeNext();
448         fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
449       }
450       fileNode.addChild(newNode, anchor);
451       if (anchor != null && !anchor.getText().startsWith("\n\n")) {
452         fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
453       }
454     }
455   }
456
457   @Nullable
458   @Override
459   public GrPackageDefinition setPackage(@Nullable GrPackageDefinition newPackage) {
460     final GrPackageDefinition oldPackage = getPackageDefinition();
461     if (oldPackage == null) {
462       if (newPackage != null) {
463         final GrPackageDefinition result = (GrPackageDefinition)addAfter(newPackage, null);
464         getNode().addLeaf(GroovyTokenTypes.mNLS, "\n", result.getNode().getTreeNext());
465         return result;
466       }
467     }
468     else {
469       if (newPackage != null) {
470         return (GrPackageDefinition)oldPackage.replace(newPackage);
471       }
472       else {
473         oldPackage.delete();
474       }
475     }
476     return null;
477   }
478
479   @Override
480   public PsiType getInferredScriptReturnType() {
481     return CachedValuesManager.getCachedValue(this, () -> CachedValueProvider.Result
482       .create(GroovyPsiManager.inferType(this, new MethodTypeInferencer(this)),
483               PsiModificationTracker.MODIFICATION_COUNT));
484   }
485
486   @Override
487   public void clearCaches() {
488     super.clearCaches();
489     myScriptClass = null;
490     mySyntheticArgsParameter = null;
491   }
492
493   @Override
494   public PsiElement getContext() {
495     return myContext != null && myContext.isValid() ? myContext : super.getContext();
496   }
497
498   @Override
499   @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
500   protected GroovyFileImpl clone() {
501     GroovyFileImpl clone = (GroovyFileImpl)super.clone();
502     clone.myContext = myContext;
503     return clone;
504   }
505
506   public void setContext(PsiElement context) {
507     if (context != null) {
508       myContext = context;
509     }
510   }
511
512   public void setContextNullable(PsiElement context) {
513     myContext = context;
514   }
515
516   @Override
517   @NotNull
518   public PsiClass[] getClasses() {
519     final PsiClass[] declaredDefs = super.getClasses();
520     if (!isScript()) return declaredDefs;
521     final PsiClass scriptClass = getScriptClass();
522     PsiClass[] result = new PsiClass[declaredDefs.length + 1];
523     result[result.length - 1] = scriptClass;
524     System.arraycopy(declaredDefs, 0, result, 0, declaredDefs.length);
525     return result;
526   }
527
528   @Override
529   public PsiElement getOriginalElement() {
530     final PsiClass scriptClass = getScriptClass();
531     if (scriptClass != null) {
532       final PsiElement originalElement = scriptClass.getOriginalElement();
533       if (originalElement != scriptClass && originalElement != null) {
534         return originalElement.getContainingFile();
535       }
536     }
537     return this;
538   }
539
540   @NotNull
541   @Override
542   public GrVariableDeclaration[] getScriptDeclarations(boolean topLevelOnly) {
543     return PsiImplUtilKt.getScriptDeclarations(this, topLevelOnly);
544   }
545
546   @Override
547   public boolean shouldChangeModificationCount(PsiElement place) {
548     // 1. We actually should never get GrTypeDefinion as a parent, because it is a PsiClass,
549     //    and PsiClasses prevent to go up in a tree any further
550     // 2. If place is under a variable then @BaseScript or @Field may be changed,
551     //    which actually is a change in Java Structure
552     return !isScript() || PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class, GrVariableDeclaration.class) != null;
553   }
554
555   @Override
556   public String toString() {
557     if (ApplicationManager.getApplication().isUnitTestMode()) {
558       return super.toString();
559     }
560     return "GroovyFileImpl:" + getName();
561   }
562 }
563