blob: bd8cc3ecc4ed95b729edc87ba8afc7971bef4e25 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.lang.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.impl.ElementBase;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import icons.JetgroovyIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyLanguage;
import org.jetbrains.plugins.groovy.extensions.GroovyScriptTypeDetector;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrTopLevelDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.GrTopStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrBindingVariable;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightParameter;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
import org.jetbrains.plugins.groovy.lang.psi.stubs.GrFileStub;
import org.jetbrains.plugins.groovy.lang.psi.stubs.GrPackageDefinitionStub;
import org.jetbrains.plugins.groovy.lang.resolve.MethodTypeInferencer;
import org.jetbrains.plugins.groovy.lang.resolve.PackageSkippingProcessor;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
import javax.swing.*;
import java.util.concurrent.ConcurrentMap;
/**
* Implements all abstractions related to Groovy file
*
* @author ilyas
*/
public class GroovyFileImpl extends GroovyFileBaseImpl implements GroovyFile {
private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.lang.psi.impl.GroovyFileImpl");
private static final String SYNTHETIC_PARAMETER_NAME = "args";
private static final CachedValueProvider<ConcurrentMap<String, GrBindingVariable>> BINDING_PROVIDER = new CachedValueProvider<ConcurrentMap<String, GrBindingVariable>>() {
@Nullable
@Override
public Result<ConcurrentMap<String, GrBindingVariable>> compute() {
final ConcurrentMap<String, GrBindingVariable> map = ContainerUtil.newConcurrentMap();
return Result.create(map, PsiModificationTracker.MODIFICATION_COUNT);
}
};
private final Object lock = new Object();
private volatile Boolean myScript;
private volatile GroovyScriptClass myScriptClass;
private volatile GrParameter mySyntheticArgsParameter = null;
private PsiElement myContext;
public GroovyFileImpl(FileViewProvider viewProvider) {
super(viewProvider, GroovyLanguage.INSTANCE);
}
@Override
@NotNull
public String getPackageName() {
GrPackageDefinition packageDef = getPackageDefinition();
if (packageDef != null) {
final String name = packageDef.getPackageName();
if (name != null) {
return name;
}
}
return "";
}
@Override
public GrPackageDefinition getPackageDefinition() {
final StubElement<?> stub = getStub();
if (stub != null) {
for (StubElement element : stub.getChildrenStubs()) {
if (element instanceof GrPackageDefinitionStub) return (GrPackageDefinition)element.getPsi();
}
return null;
}
ASTNode node = calcTreeElement().findChildByType(GroovyElementTypes.PACKAGE_DEFINITION);
return node != null ? (GrPackageDefinition)node.getPsi() : null;
}
private GrParameter getSyntheticArgsParameter() {
if (mySyntheticArgsParameter == null) {
final PsiType psiType = JavaPsiFacade.getElementFactory(getProject()).createTypeFromText("java.lang.String[]", this);
final GrParameter candidate = new GrLightParameter(SYNTHETIC_PARAMETER_NAME, psiType, this);
synchronized (lock) {
if (mySyntheticArgsParameter == null) {
mySyntheticArgsParameter = candidate;
}
}
}
return mySyntheticArgsParameter;
}
@Override
public boolean processDeclarations(@NotNull final PsiScopeProcessor processor,
@NotNull ResolveState state,
@Nullable PsiElement lastParent,
@NotNull PsiElement place) {
ClassHint classHint = processor.getHint(ClassHint.KEY);
if (myContext != null) {
if (ResolveUtil.shouldProcessProperties(classHint)) {
if (!processChildrenScopes(processor, state, lastParent, place)) return false;
}
return true;
}
boolean processClasses = ResolveUtil.shouldProcessClasses(classHint);
GroovyScriptClass scriptClass = getScriptClass();
if (scriptClass != null && scriptClass.getName() != null) {
if (!(lastParent instanceof GrTypeDefinition)) {
if (!scriptClass.processDeclarations(processor, state, lastParent, place)) return false;
}
if (processClasses) {
if (!ResolveUtil.processElement(processor, scriptClass, state)) return false;
}
}
if (processClasses) {
for (GrTypeDefinition definition : getTypeDefinitions()) {
if (!ResolveUtil.processElement(processor, definition, state)) return false;
}
}
if (ResolveUtil.shouldProcessProperties(classHint)) {
if (!processChildrenScopes(processor, state, lastParent, place)) return false;
}
GrImportStatement[] importStatements = getImportStatements();
if (!processImports(processor, state, lastParent, place, importStatements, false)) return false;
if (!processDeclarationsInPackage(processor, state, lastParent, place)) return false;
if (!processImports(processor, state, lastParent, place, importStatements, true)) return false;
if (!GroovyImportHelper.processImplicitImports(processor, state, lastParent, place, this)) return false;
if (ResolveUtil.shouldProcessPackages(classHint)) {
NameHint nameHint = processor.getHint(NameHint.KEY);
String expectedName = nameHint != null ? nameHint.getName(state) : null;
final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
if (expectedName != null) {
final PsiPackage pkg = facade.findPackage(expectedName);
if (pkg != null && !processor.execute(pkg, state)) {
return false;
}
}
else {
PsiPackage defaultPackage = facade.findPackage("");
if (defaultPackage != null) {
for (PsiPackage subPackage : defaultPackage.getSubPackages(getResolveScope())) {
if (!ResolveUtil.processElement(processor, subPackage, state)) return false;
}
}
}
}
if (ResolveUtil.shouldProcessProperties(classHint)) {
if (lastParent != null && !(lastParent instanceof GrTypeDefinition) && scriptClass != null) {
if (!ResolveUtil.processElement(processor, getSyntheticArgsParameter(), state)) return false;
}
if (isInScriptBody(lastParent, place)) {
if (!processBindings(processor, state, place)) return false;
}
}
return true;
}
private boolean isInScriptBody(PsiElement lastParent, PsiElement place) {
return isScript() &&
!(lastParent instanceof GrTypeDefinition) &&
PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class, false) == null;
}
protected boolean processImports(PsiScopeProcessor processor,
@NotNull ResolveState state,
@Nullable PsiElement lastParent,
@NotNull PsiElement place,
@NotNull GrImportStatement[] importStatements,
boolean onDemand) {
return GroovyImportHelper.processImports(state, lastParent, place, processor, importStatements, onDemand);
}
private boolean processBindings(@NotNull final PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement place) {
if (!isPhysical()) return true;
final NameHint nameHint = processor.getHint(NameHint.KEY);
if (nameHint == null) return true;
final String name = nameHint.getName(state);
if (name == null) return true;
final ClassHint classHint = processor.getHint(ClassHint.KEY);
if (classHint != null && !classHint.shouldProcess(ClassHint.ResolveKind.PROPERTY)) return true;
final ConcurrentMap<String, GrBindingVariable> bindings = getBindings();
GrBindingVariable variable = bindings.get(name);
if (variable == null) {
variable = ConcurrencyUtil.cacheOrGet(bindings, name, new GrBindingVariable(this, name, null));
}
variable.updateWriteAccessIfNeeded(place);
if (!variable.hasWriteAccess()) return true;
return processor.execute(variable, state);
}
@NotNull
private ConcurrentMap<String, GrBindingVariable> getBindings() {
return CachedValuesManager.getCachedValue(this, BINDING_PROVIDER);
}
@Override
public boolean isTopControlFlowOwner() {
return true;
}
private boolean processDeclarationsInPackage(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@Nullable PsiElement lastParent,
@NotNull PsiElement place) {
if (ResolveUtil.shouldProcessClasses(processor.getHint(ClassHint.KEY))) {
PsiPackage aPackage = JavaPsiFacade.getInstance(getProject()).findPackage(getPackageName());
if (aPackage != null) {
return aPackage.processDeclarations(new PackageSkippingProcessor(processor), state, lastParent, place);
}
}
return true;
}
private boolean processChildrenScopes(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@Nullable PsiElement lastParent,
@NotNull PsiElement place) {
final StubElement<?> stub = getStub();
if (stub != null) {
return true; // only local usages are traversed here. Having a stub means the clients are outside and won't see our variables
}
PsiElement run = lastParent == null ? getLastChild() : lastParent.getPrevSibling();
while (run != null) {
if (shouldProcess(lastParent, run) &&
!run.processDeclarations(processor, state, null, place)) {
return false;
}
run = run.getPrevSibling();
}
return true;
}
private static boolean shouldProcess(@Nullable PsiElement lastParent, @NotNull PsiElement run) {
return !(run instanceof GrTopLevelDefinition || run instanceof GrImportStatement || lastParent instanceof GrMember);
}
@Override
public GrImportStatement[] getImportStatements() {
final StubElement<?> stub = getStub();
if (stub != null) {
return stub.getChildrenByType(GroovyElementTypes.IMPORT_STATEMENT, GrImportStatement.ARRAY_FACTORY);
}
return calcTreeElement().getChildrenAsPsiElements(GroovyElementTypes.IMPORT_STATEMENT, GrImportStatement.ARRAY_FACTORY);
}
@Override
@Nullable
public Icon getIcon(int flags) {
final Icon baseIcon = isScript() ? GroovyScriptTypeDetector.getIcon(this) : JetgroovyIcons.Groovy.Groovy_16x16;
return ElementBase.createLayeredIcon(this, baseIcon, ElementBase.transformFlags(this, flags));
}
@Override
public GrImportStatement addImportForClass(@NotNull PsiClass aClass) {
try {
// Calculating position
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject());
String qname = aClass.getQualifiedName();
if (qname != null) {
GrImportStatement importStatement = factory.createImportStatementFromText(qname, false, false, null);
return addImport(importStatement);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
return null;
}
@NotNull
@Override
public GrImportStatement addImport(@NotNull GrImportStatement statement) throws IncorrectOperationException {
return GroovyCodeStyleManager.getInstance(getProject()).addImport(this, statement);
}
@Override
public boolean isScript() {
final StubElement stub = getStub();
if (stub instanceof GrFileStub) {
return ((GrFileStub)stub).isScript();
}
if (myScript == null) {
synchronized (lock) {
boolean isScript = checkIsScript();
if (myScript == null) {
myScript = isScript;
}
}
}
return myScript;
}
private boolean checkIsScript() {
final GrTopStatement[] topStatements = findChildrenByClass(GrTopStatement.class);
boolean hasClassDefinitions = false;
boolean hasTopStatements = false;
for (GrTopStatement st : topStatements) {
if (st instanceof GrTypeDefinition) {
hasClassDefinitions = true;
}
else if (!(st instanceof GrImportStatement || st instanceof GrPackageDefinition)) {
hasTopStatements = true;
break;
}
}
return hasTopStatements || !hasClassDefinitions;
}
@Override
public void subtreeChanged() {
synchronized (lock) {
myScript = null;
}
super.subtreeChanged();
}
@Override
public GroovyScriptClass getScriptClass() {
if (isScript()) {
if (myScriptClass == null) {
GroovyScriptClass candidate = new GroovyScriptClass(this);
synchronized (lock) {
if (myScriptClass == null) {
myScriptClass = candidate;
}
}
}
return myScriptClass;
}
else {
return null;
}
}
@Override
public void setPackageName(String packageName) {
final ASTNode fileNode = getNode();
assert fileNode != null;
final GrPackageDefinition currentPackage = getPackageDefinition();
if (packageName == null || packageName.isEmpty()) {
if (currentPackage != null) {
final ASTNode currNode = currentPackage.getNode();
fileNode.removeChild(currNode);
}
return;
}
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject());
final GrPackageDefinition newPackage = (GrPackageDefinition)factory.createTopElementFromText("package " + packageName);
if (currentPackage != null) {
final GrCodeReferenceElement packageReference = currentPackage.getPackageReference();
if (packageReference != null) {
GrCodeReferenceElement ref = newPackage.getPackageReference();
if (ref != null) {
packageReference.replace(ref);
}
return;
}
}
final ASTNode newNode = newPackage.getNode();
if (currentPackage != null) {
final ASTNode currNode = currentPackage.getNode();
fileNode.replaceChild(currNode, newNode);
} else {
ASTNode anchor = fileNode.getFirstChildNode();
if (anchor != null && anchor.getElementType() == GroovyTokenTypes.mSH_COMMENT) {
anchor = anchor.getTreeNext();
fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
}
fileNode.addChild(newNode, anchor);
if (anchor != null && !anchor.getText().startsWith("\n\n")) {
fileNode.addLeaf(GroovyTokenTypes.mNLS, "\n", anchor);
}
}
}
@Nullable
@Override
public GrPackageDefinition setPackage(@Nullable GrPackageDefinition newPackage) {
final GrPackageDefinition oldPackage = getPackageDefinition();
if (oldPackage == null) {
if (newPackage != null) {
final GrPackageDefinition result = (GrPackageDefinition)addAfter(newPackage, null);
getNode().addLeaf(GroovyTokenTypes.mNLS, "\n", result.getNode().getTreeNext());
return result;
}
}
else {
if (newPackage != null) {
return (GrPackageDefinition)oldPackage.replace(newPackage);
}
else {
oldPackage.delete();
}
}
return null;
}
@Override
public PsiType getInferredScriptReturnType() {
return CachedValuesManager.getCachedValue(this, new CachedValueProvider<PsiType>() {
@Override
public Result<PsiType> compute() {
return Result.create(GroovyPsiManager.inferType(GroovyFileImpl.this, new MethodTypeInferencer(GroovyFileImpl.this)),
PsiModificationTracker.MODIFICATION_COUNT);
}
});
}
@Override
public void clearCaches() {
super.clearCaches();
synchronized (lock) {
myScriptClass = null;
mySyntheticArgsParameter = null;
}
}
@Override
public PsiElement getContext() {
if (myContext != null) {
return myContext;
}
return super.getContext();
}
@Override
@SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
protected GroovyFileImpl clone() {
GroovyFileImpl clone = (GroovyFileImpl)super.clone();
clone.myContext = myContext;
return clone;
}
public void setContext(PsiElement context) {
if (context != null) {
myContext = context;
}
}
public void setContextNullable(PsiElement context) {
myContext = context;
}
@Override
@NotNull
public PsiClass[] getClasses() {
final PsiClass[] declaredDefs = super.getClasses();
if (!isScript()) return declaredDefs;
final PsiClass scriptClass = getScriptClass();
PsiClass[] result = new PsiClass[declaredDefs.length + 1];
result[result.length - 1] = scriptClass;
System.arraycopy(declaredDefs, 0, result, 0, declaredDefs.length);
return result;
}
@Override
public PsiElement getOriginalElement() {
final PsiClass scriptClass = getScriptClass();
if (scriptClass != null) {
final PsiElement originalElement = scriptClass.getOriginalElement();
if (originalElement != scriptClass && originalElement != null) {
return originalElement.getContainingFile();
}
}
return this;
}
@Override
public String toString() {
if (ApplicationManager.getApplication().isUnitTestMode()){
return super.toString();
}
return "GroovyFileImpl:" + getName();
}
}