2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package org.jetbrains.plugins.groovy.lang.psi.impl;
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;
66 import java.util.concurrent.ConcurrentMap;
69 * Implements all abstractions related to Groovy file
73 public class GroovyFileImpl extends GroovyFileBaseImpl implements GroovyFile, PsiModifiableCodeBlock {
75 private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.lang.psi.impl.GroovyFileImpl");
77 private static final String SYNTHETIC_PARAMETER_NAME = "args";
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);
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;
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);
98 public String getPackageName() {
99 GrPackageDefinition packageDef = getPackageDefinition();
100 if (packageDef != null) {
101 final String name = packageDef.getPackageName();
110 public GrPackageDefinition getPackageDefinition() {
111 final StubElement<?> stub = getStub();
113 for (StubElement element : stub.getChildrenStubs()) {
114 if (element instanceof GrPackageDefinitionStub) return (GrPackageDefinition)element.getPsi();
119 ASTNode node = calcTreeElement().findChildByType(GroovyElementTypes.PACKAGE_DEFINITION);
120 return node != null ? (GrPackageDefinition)node.getPsi() : null;
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;
134 public boolean processDeclarations(@NotNull final PsiScopeProcessor processor,
135 @NotNull ResolveState state,
136 @Nullable PsiElement lastParent,
137 @NotNull PsiElement place) {
139 if (isPhysical() && !isScript() &&
140 (getUserData(PsiFileEx.BATCH_REFERENCE_PROCESSING) == Boolean.TRUE || myResolveCache.hasUpToDateValue())) {
141 return processCachedDeclarations(processor, state, myResolveCache.getValue());
144 return processDeclarationsNoGuess(processor, state, lastParent, place);
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())
155 boolean result = name != null ? cache.processForKey(name, cacheProcessor) : cache.processAllValues(cacheProcessor);
156 if (!result) return false;
162 private MostlySingularMultiMap<String, ResultWithContext> buildDeclarationCache() {
163 MostlySingularMultiMap<String, ResultWithContext> results = new MostlySingularMultiMap<>();
164 processDeclarationsNoGuess(new BaseScopeProcessor() {
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);
171 results.add(name, new ResultWithContext((PsiNamedElement)element, context));
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();
181 }, ResolveState.initial(), null, this);
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);
190 if (myContext != null) {
191 if (ResolveUtil.shouldProcessProperties(classHint)) {
192 if (!processChildrenScopes(processor, state, lastParent, place)) return false;
197 boolean processClasses = ResolveUtil.shouldProcessClasses(classHint);
199 GrImportStatement[] importStatements = getImportStatements();
200 if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.ALIAS, false)) return false;
202 GroovyScriptClass scriptClass = getScriptClass();
203 if (scriptClass != null && StringUtil.isJavaIdentifier(scriptClass.getName())) {
205 if (!(lastParent instanceof GrTypeDefinition)) {
206 if (!ResolveUtil.processClassDeclarations(scriptClass, processor, state, lastParent, place)) return false;
209 if (processClasses) {
210 if (!ResolveUtil.processElement(processor, scriptClass, state)) return false;
214 if (processClasses) {
215 for (GrTypeDefinition definition : getTypeDefinitions()) {
216 if (!ResolveUtil.processElement(processor, definition, state)) return false;
220 if (ResolveUtil.shouldProcessProperties(classHint)) {
221 if (!processChildrenScopes(processor, state, lastParent, place)) return false;
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;
230 if (ResolveUtil.shouldProcessPackages(classHint)) {
232 NameHint nameHint = processor.getHint(NameHint.KEY);
233 String expectedName = nameHint != null ? nameHint.getName(state) : null;
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)) {
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;
252 if (ResolveUtil.shouldProcessProperties(classHint)) {
253 if (lastParent != null && !(lastParent instanceof GrTypeDefinition) && scriptClass != null) {
254 if (!ResolveUtil.processElement(processor, getSyntheticArgsParameter(), state)) return false;
261 public boolean isInScriptBody(PsiElement lastParent, PsiElement place) {
263 !(lastParent instanceof GrTypeDefinition) &&
264 PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class, false) == null;
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);
278 public ConcurrentMap<String, GrBindingVariable> getBindings() {
279 return CachedValuesManager.getCachedValue(this, BINDING_PROVIDER);
283 public boolean isTopControlFlowOwner() {
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);
300 private boolean processChildrenScopes(@NotNull PsiScopeProcessor processor,
301 @NotNull ResolveState state,
302 @Nullable PsiElement lastParent,
303 @NotNull PsiElement place) {
304 final StubElement<?> stub = getStub();
306 return true; // only local usages are traversed here. Having a stub means the clients are outside and won't see our variables
309 PsiElement run = lastParent == null ? getLastChild() : lastParent.getPrevSibling();
310 while (run != null) {
311 if (shouldProcess(lastParent, run) &&
312 !run.processDeclarations(processor, state, null, place)) {
315 run = run.getPrevSibling();
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);
327 public GrImportStatement[] getImportStatements() {
328 final StubElement<?> stub = getStub();
330 return stub.getChildrenByType(GroovyElementTypes.IMPORT_STATEMENT, GrImportStatement.ARRAY_FACTORY);
333 return calcTreeElement().getChildrenAsPsiElements(GroovyElementTypes.IMPORT_STATEMENT, GrImportStatement.ARRAY_FACTORY);
337 public GrImportStatement addImportForClass(@NotNull PsiClass aClass) {
339 // Calculating position
340 GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject());
342 String qname = aClass.getQualifiedName();
344 GrImportStatement importStatement = factory.createImportStatementFromText(qname, false, false, null);
345 return addImport(importStatement);
348 catch (IncorrectOperationException e) {
357 public GrImportStatement addImport(@NotNull GrImportStatement statement) throws IncorrectOperationException {
358 return GroovyCodeStyleManager.getInstance(getProject()).addImport(this, statement);
362 public boolean isScript() {
363 final StubElement stub = getStub();
364 if (stub instanceof GrFileStub) {
365 return ((GrFileStub)stub).isScript();
368 Boolean isScript = myScript;
369 if (isScript == null) {
370 isScript = checkIsScript();
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;
384 else if (!(st instanceof GrImportStatement || st instanceof GrPackageDefinition)) {
385 hasTopStatements = true;
389 return hasTopStatements || !hasClassDefinitions;
393 public void subtreeChanged() {
395 super.subtreeChanged();
399 public GroovyScriptClass getScriptClass() {
404 GroovyScriptClass aClass = myScriptClass;
405 if (aClass == null) {
406 aClass = new GroovyScriptClass(this);
407 myScriptClass = aClass;
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);
425 final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject());
426 final GrPackageDefinition newPackage = (GrPackageDefinition)factory.createTopElementFromText("package " + packageName);
428 if (currentPackage != null) {
429 final GrCodeReferenceElement packageReference = currentPackage.getPackageReference();
430 if (packageReference != null) {
431 GrCodeReferenceElement ref = newPackage.getPackageReference();
433 packageReference.replace(ref);
439 final ASTNode newNode = newPackage.getNode();
440 if (currentPackage != null) {
441 final ASTNode currNode = currentPackage.getNode();
442 fileNode.replaceChild(currNode, newNode);
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);
450 fileNode.addChild(newNode, anchor);
451 if (anchor != null && !anchor.getText().startsWith("\n\n")) {
452 fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
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());
469 if (newPackage != null) {
470 return (GrPackageDefinition)oldPackage.replace(newPackage);
480 public PsiType getInferredScriptReturnType() {
481 return CachedValuesManager.getCachedValue(this, () -> CachedValueProvider.Result
482 .create(GroovyPsiManager.inferType(this, new MethodTypeInferencer(this)),
483 PsiModificationTracker.MODIFICATION_COUNT));
487 public void clearCaches() {
489 myScriptClass = null;
490 mySyntheticArgsParameter = null;
494 public PsiElement getContext() {
495 return myContext != null && myContext.isValid() ? myContext : super.getContext();
499 @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
500 protected GroovyFileImpl clone() {
501 GroovyFileImpl clone = (GroovyFileImpl)super.clone();
502 clone.myContext = myContext;
506 public void setContext(PsiElement context) {
507 if (context != null) {
512 public void setContextNullable(PsiElement context) {
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);
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();
542 public GrVariableDeclaration[] getScriptDeclarations(boolean topLevelOnly) {
543 return PsiImplUtilKt.getScriptDeclarations(this, topLevelOnly);
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;
556 public String toString() {
557 if (ApplicationManager.getApplication().isUnitTestMode()) {
558 return super.toString();
560 return "GroovyFileImpl:" + getName();