2 * Copyright 2000-2009 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.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;
63 import java.util.ArrayList;
64 import java.util.List;
67 * Implements all abstractions related to Groovy file
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();
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;
81 public GroovyFileImpl(FileViewProvider viewProvider) {
82 super(viewProvider, GroovyFileType.GROOVY_FILE_TYPE.getLanguage());
86 public String getPackageName() {
87 final StubElement stub = getStub();
88 if (stub instanceof GrFileStub) {
89 return ((GrFileStub)stub).getPackageName().toString();
91 GrPackageDefinition packageDef = getPackageDefinition();
92 if (packageDef != null) {
93 return packageDef.getPackageName();
98 public GrPackageDefinition getPackageDefinition() {
99 ASTNode node = calcTreeElement().findChildByType(GroovyElementTypes.PACKAGE_DEFINITION);
100 return node != null ? (GrPackageDefinition)node.getPsi() : null;
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;
113 return mySyntheticArgsParameter;
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;
126 for (GrTypeDefinition definition : getTypeDefinitions()) {
127 if (!ResolveUtil.processElement(processor, definition, state)) return false;
130 if (lastParent != null && !(lastParent instanceof GrTypeDefinition) && scriptClass != null) {
131 if (!ResolveUtil.processElement(processor, getSyntheticArgsParameter(), state)) return false;
134 if (!processChildrenScopes(this, processor, state, lastParent, place)) return false;
136 final ClassHint classHint = processor.getHint(ClassHint.KEY);
137 final boolean processClasses = classHint == null || classHint.shouldProcess(ClassHint.ResolveKind.CLASS);
139 final String expectedName = ResolveUtil.getNameHint(processor);
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);
146 for (GrImportStatement importStatement : getImportStatements()) {
147 if (!importStatement.processDeclarations(importProcessor, state, lastParent, place)) {
152 if (processClasses && !processImplicitImports(processor, state, lastParent, place)) {
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)) {
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;
177 private boolean isImplicitlyImported(PsiElement element, String expectedName) {
178 if (!(element instanceof PsiClass)) return false;
180 final PsiClass psiClass = (PsiClass)element;
181 if (!expectedName.equals(psiClass.getName())) return false;
183 final String qname = psiClass.getQualifiedName();
184 if (qname == null) return false;
186 for (String importedClass : IMPLICITLY_IMPORTED_CLASSES) {
187 if (qname.equals(importedClass)) {
191 for (String pkg : getImplicitlyImportedPackages()) {
192 if (qname.equals(pkg + "." + expectedName) || pkg.length() == 0 && qname.equals(expectedName)) {
200 private List<String> getImplicitlyImportedPackages() {
201 final ArrayList<String> result = new ArrayList<String>();
202 result.add(getPackageName());
203 ContainerUtil.addAll(result, IMPLICITLY_IMPORTED_PACKAGES);
205 result.addAll(GroovyScriptType.getScriptType(this).appendImplicitImports(this));
210 private boolean processImplicitImports(PsiScopeProcessor processor, ResolveState state, PsiElement lastParent,
212 JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
214 for (final String implicitlyImported : getImplicitlyImportedPackages()) {
215 PsiPackage aPackage = facade.findPackage(implicitlyImported);
216 if (aPackage != null && !aPackage.processDeclarations(processor, state, lastParent, place)) return false;
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;
226 private static boolean processChildrenScopes(PsiElement element,
227 PsiScopeProcessor processor,
229 PsiElement lastParent,
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)) {
238 run = run.getPrevSibling();
244 public GrImportStatement[] getImportStatements() {
245 return findChildrenByClass(GrImportStatement.class);
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));
254 public GrImportStatement addImportForClass(PsiClass aClass) {
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);
263 catch (IncorrectOperationException e) {
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();
282 public GrImportStatement addImport(GrImportStatement statement) throws IncorrectOperationException {
283 PsiElement anchor = getAnchorToInsertImportAfter();
284 final PsiElement result = addAfter(statement, anchor);
286 boolean isAliasedImport = false;
287 if (anchor instanceof GrImportStatement) {
288 isAliasedImport = !((GrImportStatement)anchor).isAliasedImport() && statement.isAliasedImport() ||
289 ((GrImportStatement)anchor).isAliasedImport() && !statement.isAliasedImport();
292 if (anchor != null) {
293 int lineFeedCount = 0;
294 if (!(anchor instanceof GrImportStatement) || isAliasedImport) {
297 final PsiElement prev = result.getPrevSibling();
298 if (prev instanceof PsiWhiteSpace) {
299 lineFeedCount += StringUtil.getOccurenceCount(prev.getText(), '\n');
301 if (lineFeedCount > 0) {
302 getNode().addLeaf(GroovyTokenTypes.mNLS, StringUtil.repeatSymbol('\n', lineFeedCount), result.getNode());
304 if (prev instanceof PsiWhiteSpace) {
309 GrImportStatement importStatement = (GrImportStatement)result;
310 PsiElement next = importStatement.getNextSibling();
312 ASTNode node = next.getNode();
313 if (node != null && GroovyTokenTypes.mNLS == node.getElementType()) {
314 next.replace(GroovyPsiElementFactory.getInstance(statement.getProject()).createLineTerminator(2));
317 return importStatement;
320 public boolean isScript() {
321 final StubElement stub = getStub();
322 if (stub instanceof GrFileStub) {
323 return ((GrFileStub)stub).isScript();
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;
343 public void subtreeChanged() {
345 super.subtreeChanged();
348 public GroovyScriptClass getScriptClass() {
350 if (myScriptClass == null) {
351 GroovyScriptClass candidate = new GroovyScriptClass(this);
352 synchronized (lock) {
353 if (myScriptClass == null) {
354 myScriptClass = candidate;
358 return myScriptClass;
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);
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);
384 final ASTNode anchor = fileNode.getFirstChildNode();
385 fileNode.addChild(newNode, anchor);
386 fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
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);
402 public void removeMemberDeclaration(GrMembersDeclaration decl) {
404 deleteChildRange(decl, decl);
406 catch (IncorrectOperationException e) {
407 throw new RuntimeException(e);
411 public void clearCaches() {
413 // myScriptClass = null;
414 // myScriptClassInitialized = false;
415 synchronized (lock) {
416 mySyntheticArgsParameter = null;
420 public PsiElement getContext() {
421 if (myContext != null) {
424 return super.getContext();
427 @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
428 protected GroovyFileImpl clone() {
429 GroovyFileImpl clone = (GroovyFileImpl)super.clone();
430 clone.myContext = myContext;
434 public void setContext(PsiElement context) {
435 if (context != null) {
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);
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();
462 private static final UserDataCache<CachedValue<GlobalSearchScope>, GroovyFile, GlobalSearchScope> RESOLVE_SCOPE_CACHE = new UserDataCache<CachedValue<GlobalSearchScope>, GroovyFile, GlobalSearchScope>("RESOLVE_SCOPE_CACHE") {
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()));
473 public GlobalSearchScope getFileResolveScope() {
474 final PsiElement context = getContext();
475 if (context instanceof GroovyFile) {
476 return context.getResolveScope();
479 final VirtualFile vFile = getOriginalFile().getVirtualFile();
481 return GlobalSearchScope.allScope(getProject());
484 final GlobalSearchScope baseScope = ((FileManagerImpl)((PsiManagerEx)getManager()).getFileManager()).getDefaultResolveScope(vFile);
486 return RESOLVE_SCOPE_CACHE.get(this, baseScope).getValue();