PHP HTML injection
[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     Boolean isScript = myScript;
327     if (isScript == null) {
328       final GrTopStatement[] topStatements = findChildrenByClass(GrTopStatement.class);
329       isScript = topStatements.length == 0;
330       for (GrTopStatement st : topStatements) {
331         if (!(st instanceof GrTypeDefinition || st instanceof GrImportStatement || st instanceof GrPackageDefinition)) {
332           isScript = Boolean.TRUE;
333           break;
334         }
335       }
336       myScript = isScript;
337     }
338
339     return isScript;
340   }
341
342   @Override
343   public void subtreeChanged() {
344     myScript = null;
345     super.subtreeChanged();
346   }
347
348   public GroovyScriptClass getScriptClass() {
349     if (isScript()) {
350       if (myScriptClass == null) {
351         GroovyScriptClass candidate = new GroovyScriptClass(this);
352         synchronized (lock) {
353           if (myScriptClass == null) {
354             myScriptClass = candidate;
355           }
356         }
357       }
358       return myScriptClass;
359     }
360     else {
361       return null;
362     }
363   }
364
365   public void setPackageName(String packageName) {
366     final ASTNode fileNode = getNode();
367     assert fileNode != null;
368     final GrPackageDefinition currentPackage = getPackageDefinition();
369     if (packageName == null || packageName.length() == 0) {
370       if (currentPackage != null) {
371         final ASTNode currNode = currentPackage.getNode();
372         assert currNode != null;
373         fileNode.removeChild(currNode);
374       }
375       return;
376     }
377     final GrTopStatement newPackage = GroovyPsiElementFactory.getInstance(getProject()).createTopElementFromText("package " + packageName);
378     final ASTNode newNode = newPackage.getNode();
379     if (currentPackage != null) {
380       final ASTNode currNode = currentPackage.getNode();
381       assert currNode != null;
382       fileNode.replaceChild(currNode, newNode);
383     } else {
384       final ASTNode anchor = fileNode.getFirstChildNode();
385       fileNode.addChild(newNode, anchor);
386       fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
387     }
388   }
389
390   public <T extends GrMembersDeclaration> T addMemberDeclaration(@NotNull T decl, PsiElement anchorBefore)
391     throws IncorrectOperationException {
392     T result = (T)addBefore(decl, anchorBefore);
393     CodeStyleManager styleManager = getManager().getCodeStyleManager();
394     PsiElement parent = result.getContainingFile();
395     TextRange range = result.getTextRange();
396     styleManager.reformatRange(parent, range.getEndOffset() - 1, range.getEndOffset() + 1);
397     styleManager.reformatRange(parent, range.getStartOffset() - 1, range.getStartOffset() + 1);
398
399     return result;
400   }
401
402   public void removeMemberDeclaration(GrMembersDeclaration decl) {
403     try {
404       deleteChildRange(decl, decl);
405     }
406     catch (IncorrectOperationException e) {
407       throw new RuntimeException(e);
408     }
409   }
410
411   public void clearCaches() {
412     super.clearCaches();
413 //    myScriptClass = null;
414 //    myScriptClassInitialized = false;
415     synchronized (lock) {
416       mySyntheticArgsParameter = null;
417     }
418   }
419
420   public PsiElement getContext() {
421     if (myContext != null) {
422       return myContext;
423     }
424     return super.getContext();
425   }
426
427   @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
428   protected GroovyFileImpl clone() {
429     GroovyFileImpl clone = (GroovyFileImpl)super.clone();
430     clone.myContext = myContext;
431     return clone;
432   }
433
434   public void setContext(PsiElement context) {
435     if (context != null) {
436       myContext = context;
437     }
438   }
439
440   @NotNull
441   public PsiClass[] getClasses() {
442     final PsiClass[] declaredDefs = super.getClasses();
443     if (!isScript()) return declaredDefs;
444     final PsiClass scriptClass = getScriptClass();
445     PsiClass[] result = new PsiClass[declaredDefs.length + 1];
446     result[0] = scriptClass;
447     System.arraycopy(declaredDefs, 0, result, 1, declaredDefs.length);
448     return result;
449   }
450
451   public PsiElement getOriginalElement() {
452     final PsiClass scriptClass = getScriptClass();
453     if (scriptClass != null) {
454       final PsiElement originalElement = scriptClass.getOriginalElement();
455       if (originalElement != scriptClass) {
456         return originalElement.getContainingFile();
457       }
458     }
459     return this;
460   }
461
462   private static final UserDataCache<CachedValue<GlobalSearchScope>, GroovyFile, GlobalSearchScope> RESOLVE_SCOPE_CACHE = new UserDataCache<CachedValue<GlobalSearchScope>, GroovyFile, GlobalSearchScope>("RESOLVE_SCOPE_CACHE") {
463     @Override
464     protected CachedValue<GlobalSearchScope> compute(final GroovyFile file, final GlobalSearchScope baseScope) {
465       return CachedValuesManager.getManager(file.getProject()).createCachedValue(new CachedValueProvider<GlobalSearchScope>() {
466         public Result<GlobalSearchScope> compute() {
467           GlobalSearchScope scope = GroovyScriptType.getScriptType(file).patchResolveScope(file, baseScope);
468           return Result.create(scope, file, ProjectRootManager.getInstance(file.getProject()));
469         }
470       }, false);
471     }
472   };
473   public GlobalSearchScope getFileResolveScope() {
474     final PsiElement context = getContext();
475     if (context instanceof GroovyFile) {
476       return context.getResolveScope();
477     }
478
479     final VirtualFile vFile = getOriginalFile().getVirtualFile();                                        
480     if (vFile == null) {
481       return GlobalSearchScope.allScope(getProject());
482     }
483
484     final GlobalSearchScope baseScope = ((FileManagerImpl)((PsiManagerEx)getManager()).getFileManager()).getDefaultResolveScope(vFile);
485     if (isScript()) {
486       return RESOLVE_SCOPE_CACHE.get(this, baseScope).getValue();
487     }
488     return baseScope;
489   }
490 }
491