Merge commit 'origin/master'
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / psi / impl / GroovyFileImpl.java
1 /*
2  * Copyright 2000-2009 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.diagnostic.Logger;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.roots.ProjectRootManager;
23 import com.intellij.openapi.util.TextRange;
24 import com.intellij.openapi.util.UserDataCache;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.psi.*;
28 import com.intellij.psi.codeStyle.CodeStyleManager;
29 import com.intellij.psi.impl.ElementBase;
30 import com.intellij.psi.impl.PsiManagerEx;
31 import com.intellij.psi.impl.file.impl.FileManagerImpl;
32 import com.intellij.psi.scope.DelegatingScopeProcessor;
33 import com.intellij.psi.scope.PsiScopeProcessor;
34 import com.intellij.psi.search.GlobalSearchScope;
35 import com.intellij.psi.stubs.StubElement;
36 import com.intellij.psi.util.CachedValue;
37 import com.intellij.psi.util.CachedValueProvider;
38 import com.intellij.psi.util.CachedValuesManager;
39 import com.intellij.util.IncorrectOperationException;
40 import com.intellij.util.containers.ContainerUtil;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 import org.jetbrains.plugins.groovy.GroovyFileType;
44 import org.jetbrains.plugins.groovy.GroovyIcons;
45 import org.jetbrains.plugins.groovy.extensions.GroovyScriptType;
46 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
47 import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
48 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
49 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
50 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrTopLevelDefintion;
51 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
52 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
53 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMembersDeclaration;
54 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.GrTopStatement;
55 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
56 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition;
57 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
58 import org.jetbrains.plugins.groovy.lang.psi.stubs.GrFileStub;
59 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
60 import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
61
62 import javax.swing.*;
63 import java.util.ArrayList;
64 import java.util.List;
65
66 /**
67  * Implements all abstractions related to Groovy file
68  *
69  * @author ilyas
70  */
71 public class GroovyFileImpl extends GroovyFileBaseImpl implements GroovyFile {
72   private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.lang.psi.impl.GroovyFileImpl");
73   private static final Object lock = new Object();
74
75   private volatile Boolean myScript;
76   private GroovyScriptClass myScriptClass;
77   private static final String SYNTHETIC_PARAMETER_NAME = "args";
78   private GrParameter mySyntheticArgsParameter = null;
79   private PsiElement myContext;
80
81   public GroovyFileImpl(FileViewProvider viewProvider) {
82     super(viewProvider, GroovyFileType.GROOVY_FILE_TYPE.getLanguage());
83   }
84
85   @NotNull
86   public String getPackageName() {
87     final StubElement stub = getStub();
88     if (stub instanceof GrFileStub) {
89       return ((GrFileStub)stub).getPackageName().toString();
90     }
91     GrPackageDefinition packageDef = getPackageDefinition();
92     if (packageDef != null) {
93       return packageDef.getPackageName();
94     }
95     return "";
96   }
97
98   public GrPackageDefinition getPackageDefinition() {
99     ASTNode node = calcTreeElement().findChildByType(GroovyElementTypes.PACKAGE_DEFINITION);
100     return node != null ? (GrPackageDefinition)node.getPsi() : null;
101   }
102
103   private GrParameter getSyntheticArgsParameter() {
104     if (mySyntheticArgsParameter == null) {
105       final GrParameter candidate =
106         GroovyPsiElementFactory.getInstance(getProject()).createParameter(SYNTHETIC_PARAMETER_NAME, "java.lang.String[]", this);
107       synchronized (lock) {
108         if (mySyntheticArgsParameter == null) {
109           mySyntheticArgsParameter = candidate;
110         }
111       }
112     }
113     return mySyntheticArgsParameter;
114   }
115
116   public boolean processDeclarations(@NotNull final PsiScopeProcessor processor,
117                                      @NotNull ResolveState state,
118                                      PsiElement lastParent,
119                                      @NotNull PsiElement place) {
120     PsiClass scriptClass = getScriptClass();
121     if (scriptClass != null) {
122       if (!scriptClass.processDeclarations(processor, state, lastParent, place)) return false;
123       if (!ResolveUtil.processElement(processor, scriptClass, state)) return false;
124     }
125
126     for (GrTypeDefinition definition : getTypeDefinitions()) {
127       if (!ResolveUtil.processElement(processor, definition, state)) return false;
128     }
129
130     if (lastParent != null && !(lastParent instanceof GrTypeDefinition) && scriptClass != null) {
131       if (!ResolveUtil.processElement(processor, getSyntheticArgsParameter(), state)) return false;
132     }
133
134     if (!processChildrenScopes(this, processor, state, lastParent, place)) return false;
135
136     final ClassHint classHint = processor.getHint(ClassHint.KEY);
137     final boolean processClasses = classHint == null || classHint.shouldProcess(ClassHint.ResolveKind.CLASS);
138
139     final String expectedName = ResolveUtil.getNameHint(processor);
140
141     PsiScopeProcessor importProcessor = !processClasses || expectedName == null ? processor : new DelegatingScopeProcessor(processor) {
142       public boolean execute(PsiElement element, ResolveState state) {
143         return isImplicitlyImported(element, expectedName) || super.execute(element, state);
144       }
145     };
146     for (GrImportStatement importStatement : getImportStatements()) {
147       if (!importStatement.processDeclarations(importProcessor, state, lastParent, place)) {
148         return false;
149       }
150     }
151
152     if (processClasses && !processImplicitImports(processor, state, lastParent, place)) {
153       return false;
154     }
155
156     if (classHint == null || classHint.shouldProcess(ClassHint.ResolveKind.PACKAGE)) {
157       final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
158       if (expectedName != null) {
159         final PsiPackage pkg = facade.findPackage(expectedName);
160         if (pkg != null && !processor.execute(pkg, state)) {
161           return false;
162         }
163       } else {
164         PsiPackage defaultPackage = facade.findPackage("");
165         if (defaultPackage != null) {
166           for (PsiPackage subPackage : defaultPackage.getSubPackages(getResolveScope())) {
167             if (!ResolveUtil.processElement(processor, subPackage, state)) return false;
168           }
169         }
170       }
171
172     }
173
174     return true;
175   }
176
177   private boolean isImplicitlyImported(PsiElement element, String expectedName) {
178     if (!(element instanceof PsiClass)) return false;
179
180     final PsiClass psiClass = (PsiClass)element;
181     if (!expectedName.equals(psiClass.getName())) return false;
182
183     final String qname = psiClass.getQualifiedName();
184     if (qname == null) return false;
185
186     for (String importedClass : IMPLICITLY_IMPORTED_CLASSES) {
187       if (qname.equals(importedClass)) {
188         return true;
189       }
190     }
191     for (String pkg : getImplicitlyImportedPackages()) {
192       if (qname.equals(pkg + "." + expectedName) || pkg.length() == 0 && qname.equals(expectedName)) {
193         return true;
194       }
195     }
196     return false;
197   }
198
199
200   private List<String> getImplicitlyImportedPackages() {
201     final ArrayList<String> result = new ArrayList<String>();
202     result.add(getPackageName());
203     ContainerUtil.addAll(result, IMPLICITLY_IMPORTED_PACKAGES);
204     if (isScript()) {
205       result.addAll(GroovyScriptType.getScriptType(this).appendImplicitImports(this));
206     }
207     return result;
208   }
209
210   private boolean processImplicitImports(PsiScopeProcessor processor, ResolveState state, PsiElement lastParent,
211                                          PsiElement place) {
212     JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
213
214     for (final String implicitlyImported : getImplicitlyImportedPackages()) {
215       PsiPackage aPackage = facade.findPackage(implicitlyImported);
216       if (aPackage != null && !aPackage.processDeclarations(processor, state, lastParent, place)) return false;
217     }
218
219     for (String implicitlyImportedClass : IMPLICITLY_IMPORTED_CLASSES) {
220       PsiClass clazz = facade.findClass(implicitlyImportedClass, getResolveScope());
221       if (clazz != null && !ResolveUtil.processElement(processor, clazz, state)) return false;
222     }
223     return true;
224   }
225
226   private static boolean processChildrenScopes(PsiElement element,
227                                                PsiScopeProcessor processor,
228                                                ResolveState state,
229                                                PsiElement lastParent,
230                                                PsiElement place) {
231     PsiElement run = lastParent == null ? element.getLastChild() : lastParent.getPrevSibling();
232     while (run != null) {
233       if (!(run instanceof GrTopLevelDefintion) &&
234           !(run instanceof GrImportStatement) &&
235           !run.processDeclarations(processor, state, null, place)) {
236         return false;
237       }
238       run = run.getPrevSibling();
239     }
240
241     return true;
242   }
243
244   public GrImportStatement[] getImportStatements() {
245     return findChildrenByClass(GrImportStatement.class);
246   }
247
248   @Nullable
249   public Icon getIcon(int flags) {
250     final Icon baseIcon = isScript() ? GroovyScriptType.getScriptType(this).getScriptIcon() : GroovyIcons.GROOVY_ICON_16x16;
251     return ElementBase.createLayeredIcon(baseIcon, ElementBase.transformFlags(this, flags));
252   }
253
254   public GrImportStatement addImportForClass(PsiClass aClass) {
255     try {
256       // Calculating position
257       Project project = aClass.getProject();
258       GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(project);
259       GrImportStatement importStatement = factory.createImportStatementFromText(aClass.getQualifiedName(), false, false, null);
260       PsiElement anchor = getAnchorToInsertImportAfter();
261       return (GrImportStatement)addAfter(importStatement, anchor);
262     }
263     catch (IncorrectOperationException e) {
264       LOG.error(e);
265       return null;
266     }
267   }
268
269   @Nullable
270   private PsiElement getAnchorToInsertImportAfter() {
271     GrImportStatement[] importStatements = getImportStatements();
272     if (importStatements.length > 0) {
273       return importStatements[importStatements.length - 1];
274     } else if (getPackageDefinition() != null) {
275       return getPackageDefinition();
276     }
277
278     return null;
279   }
280
281
282   public GrImportStatement addImport(GrImportStatement statement) throws IncorrectOperationException {
283     PsiElement anchor = getAnchorToInsertImportAfter();
284     final PsiElement result = addAfter(statement, anchor);
285
286     boolean isAliasedImport = false;
287     if (anchor instanceof GrImportStatement) {
288       isAliasedImport = !((GrImportStatement)anchor).isAliasedImport() && statement.isAliasedImport() ||
289                         ((GrImportStatement)anchor).isAliasedImport() && !statement.isAliasedImport();
290     }
291
292     if (anchor != null) {
293       int lineFeedCount = 0;
294       if (!(anchor instanceof GrImportStatement) || isAliasedImport) {
295         lineFeedCount++;
296       }
297       final PsiElement prev = result.getPrevSibling();
298       if (prev instanceof PsiWhiteSpace) {
299         lineFeedCount += StringUtil.getOccurenceCount(prev.getText(), '\n');
300       }
301       if (lineFeedCount > 0) {
302         getNode().addLeaf(GroovyTokenTypes.mNLS, StringUtil.repeatSymbol('\n', lineFeedCount), result.getNode());
303       }
304       if (prev instanceof PsiWhiteSpace) {
305         prev.delete();
306       }
307     }
308
309     GrImportStatement importStatement = (GrImportStatement)result;
310     PsiElement next = importStatement.getNextSibling();
311     if (next != null) {
312       ASTNode node = next.getNode();
313       if (node != null && GroovyTokenTypes.mNLS == node.getElementType()) {
314         next.replace(GroovyPsiElementFactory.getInstance(statement.getProject()).createLineTerminator(2));
315       }
316     }
317     return importStatement;
318   }
319
320   public boolean isScript() {
321     final StubElement stub = getStub();
322     if (stub instanceof GrFileStub) {
323       return ((GrFileStub)stub).isScript();
324     }
325
326     if (myScript == null) {
327       final GrTopStatement[] topStatements = findChildrenByClass(GrTopStatement.class);
328       boolean hasClassDefinitions = false;
329       boolean hasTopStatements = false;
330       for (GrTopStatement st : topStatements) {
331         if (st instanceof GrTypeDefinition) {
332           hasClassDefinitions = true;
333         }
334         else if (!(st instanceof GrImportStatement || st instanceof GrPackageDefinition)) {
335           hasTopStatements = true;
336           break;
337         }
338       }
339       myScript = hasTopStatements || !hasClassDefinitions;
340     }
341     return myScript;
342   }
343
344   @Override
345   public void subtreeChanged() {
346     myScript = null;
347     super.subtreeChanged();
348   }
349
350   public GroovyScriptClass getScriptClass() {
351     if (isScript()) {
352       if (myScriptClass == null) {
353         GroovyScriptClass candidate = new GroovyScriptClass(this);
354         synchronized (lock) {
355           if (myScriptClass == null) {
356             myScriptClass = candidate;
357           }
358         }
359       }
360       return myScriptClass;
361     }
362     else {
363       return null;
364     }
365   }
366
367   public void setPackageName(String packageName) {
368     final ASTNode fileNode = getNode();
369     assert fileNode != null;
370     final GrPackageDefinition currentPackage = getPackageDefinition();
371     if (packageName == null || packageName.length() == 0) {
372       if (currentPackage != null) {
373         final ASTNode currNode = currentPackage.getNode();
374         assert currNode != null;
375         fileNode.removeChild(currNode);
376       }
377       return;
378     }
379     final GrTopStatement newPackage = GroovyPsiElementFactory.getInstance(getProject()).createTopElementFromText("package " + packageName);
380     final ASTNode newNode = newPackage.getNode();
381     if (currentPackage != null) {
382       final ASTNode currNode = currentPackage.getNode();
383       assert currNode != null;
384       fileNode.replaceChild(currNode, newNode);
385     } else {
386       final ASTNode anchor = fileNode.getFirstChildNode();
387       fileNode.addChild(newNode, anchor);
388       fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
389     }
390   }
391
392   public <T extends GrMembersDeclaration> T addMemberDeclaration(@NotNull T decl, PsiElement anchorBefore)
393     throws IncorrectOperationException {
394     T result = (T)addBefore(decl, anchorBefore);
395     CodeStyleManager styleManager = getManager().getCodeStyleManager();
396     PsiElement parent = result.getContainingFile();
397     TextRange range = result.getTextRange();
398     styleManager.reformatRange(parent, range.getEndOffset() - 1, range.getEndOffset() + 1);
399     styleManager.reformatRange(parent, range.getStartOffset() - 1, range.getStartOffset() + 1);
400
401     return result;
402   }
403
404   public void removeMemberDeclaration(GrMembersDeclaration decl) {
405     try {
406       deleteChildRange(decl, decl);
407     }
408     catch (IncorrectOperationException e) {
409       throw new RuntimeException(e);
410     }
411   }
412
413   public void clearCaches() {
414     super.clearCaches();
415 //    myScriptClass = null;
416 //    myScriptClassInitialized = false;
417     synchronized (lock) {
418       mySyntheticArgsParameter = null;
419     }
420   }
421
422   public PsiElement getContext() {
423     if (myContext != null) {
424       return myContext;
425     }
426     return super.getContext();
427   }
428
429   @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
430   protected GroovyFileImpl clone() {
431     GroovyFileImpl clone = (GroovyFileImpl)super.clone();
432     clone.myContext = myContext;
433     return clone;
434   }
435
436   public void setContext(PsiElement context) {
437     if (context != null) {
438       myContext = context;
439     }
440   }
441
442   @NotNull
443   public PsiClass[] getClasses() {
444     final PsiClass[] declaredDefs = super.getClasses();
445     if (!isScript()) return declaredDefs;
446     final PsiClass scriptClass = getScriptClass();
447     PsiClass[] result = new PsiClass[declaredDefs.length + 1];
448     result[0] = scriptClass;
449     System.arraycopy(declaredDefs, 0, result, 1, declaredDefs.length);
450     return result;
451   }
452
453   public PsiElement getOriginalElement() {
454     final PsiClass scriptClass = getScriptClass();
455     if (scriptClass != null) {
456       final PsiElement originalElement = scriptClass.getOriginalElement();
457       if (originalElement != scriptClass) {
458         return originalElement.getContainingFile();
459       }
460     }
461     return this;
462   }
463
464   private static final UserDataCache<CachedValue<GlobalSearchScope>, GroovyFile, GlobalSearchScope> RESOLVE_SCOPE_CACHE = new UserDataCache<CachedValue<GlobalSearchScope>, GroovyFile, GlobalSearchScope>("RESOLVE_SCOPE_CACHE") {
465     @Override
466     protected CachedValue<GlobalSearchScope> compute(final GroovyFile file, final GlobalSearchScope baseScope) {
467       return CachedValuesManager.getManager(file.getProject()).createCachedValue(new CachedValueProvider<GlobalSearchScope>() {
468         public Result<GlobalSearchScope> compute() {
469           GlobalSearchScope scope = GroovyScriptType.getScriptType(file).patchResolveScope(file, baseScope);
470           return Result.create(scope, file, ProjectRootManager.getInstance(file.getProject()));
471         }
472       }, false);
473     }
474   };
475   public GlobalSearchScope getFileResolveScope() {
476     final PsiElement context = getContext();
477     if (context instanceof GroovyFile) {
478       return context.getResolveScope();
479     }
480
481     final VirtualFile vFile = getOriginalFile().getVirtualFile();                                        
482     if (vFile == null) {
483       return GlobalSearchScope.allScope(getProject());
484     }
485
486     final GlobalSearchScope baseScope = ((FileManagerImpl)((PsiManagerEx)getManager()).getFileManager()).getDefaultResolveScope(vFile);
487     if (isScript()) {
488       return RESOLVE_SCOPE_CACHE.get(this, baseScope).getValue();
489     }
490     return baseScope;
491   }
492 }
493