blob: f0a10e588ab0d907cc19430cbbd50fa5556f5608 [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.toplevel.imports;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.scope.DelegatingScopeProcessor;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrStubElementBase;
import org.jetbrains.plugins.groovy.lang.psi.stubs.GrImportStatementStub;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
import org.jetbrains.plugins.groovy.lang.resolve.processors.GrDelegatingScopeProcessorWithHints;
/**
* @author ilyas
*/
public class GrImportStatementImpl extends GrStubElementBase<GrImportStatementStub> implements GrImportStatement, StubBasedPsiElement<GrImportStatementStub> {
public GrImportStatementImpl(@NotNull ASTNode node) {
super(node);
}
public GrImportStatementImpl(GrImportStatementStub stub, IStubElementType nodeType) {
super(stub, nodeType);
}
@Override
public PsiElement getParent() {
return getParentByStub();
}
@Override
public void accept(GroovyElementVisitor visitor) {
visitor.visitImportStatement(this);
}
public String toString() {
return "Import statement";
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@Nullable PsiElement lastParent,
@NotNull PsiElement place) {
if (isAncestor(place)) {
return true;
}
if (isStatic() && lastParent instanceof GrImportStatement) return true;
if (isOnDemand()) {
if (!processDeclarationsForMultipleElements(processor, lastParent, place, state)) return false;
}
else {
if (!processDeclarationsForSingleElement(processor, lastParent, place, state)) return false;
}
return true;
}
private boolean isAncestor(@Nullable PsiElement place) {
while (place instanceof GrCodeReferenceElement) {
PsiElement parent = place.getParent();
if (parent == this) return true;
place = parent;
}
return false;
}
private boolean processDeclarationsForSingleElement(@NotNull PsiScopeProcessor processor,
@Nullable PsiElement lastParent,
@NotNull PsiElement place,
@NotNull ResolveState state) {
String name = getImportedName();
if (name == null) return true;
if (isStatic()) {
return processSingleStaticImport(processor, state, name, lastParent, place);
}
NameHint nameHint = processor.getHint(NameHint.KEY);
if (nameHint == null || name.equals(nameHint.getName(state))) {
return processSingleClassImport(processor, state);
}
return true;
}
@Nullable
private PsiClass resolveQualifier() {
return CachedValuesManager.getCachedValue(this, new CachedValueProvider<PsiClass>() {
@Nullable
@Override
public Result<PsiClass> compute() {
GrCodeReferenceElement reference = getImportReference();
GrCodeReferenceElement qualifier = reference == null ? null : reference.getQualifier();
PsiElement target = qualifier == null ? null : qualifier.resolve();
PsiClass clazz = target instanceof PsiClass ? (PsiClass)target : null;
return Result.create(clazz, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, GrImportStatementImpl.this);
}
});
}
private boolean processSingleStaticImport(@NotNull final PsiScopeProcessor processor,
@NotNull ResolveState state,
@NotNull String importedName,
@Nullable PsiElement lastParent,
@NotNull PsiElement place) {
final GrCodeReferenceElement ref = getImportReference();
if (ref == null) return true;
PsiClass clazz = resolveQualifier();
if (clazz == null) return true;
state = state.put(ClassHint.RESOLVE_CONTEXT, this);
final String refName = ref.getReferenceName();
final NameHint nameHint = processor.getHint(NameHint.KEY);
final String hintName = nameHint == null ? null : nameHint.getName(state);
if (hintName == null || importedName.equals(hintName)) {
if (!clazz.processDeclarations(new GrDelegatingScopeProcessorWithHints(processor, refName, null), state, lastParent, place)) {
return false;
}
}
if (ResolveUtil.shouldProcessMethods(processor.getHint(ClassHint.KEY))) {
if (hintName == null || importedName.equals(GroovyPropertyUtils.getPropertyNameByGetterName(hintName, true))) {
if (!clazz.processDeclarations(new StaticGetterProcessor(refName, processor), state, lastParent, place)) {
return false;
}
}
if (hintName == null || importedName.equals(GroovyPropertyUtils.getPropertyNameBySetterName(hintName))) {
if (!clazz.processDeclarations(new StaticSetterProcessor(refName, processor), state, lastParent, place)) {
return false;
}
}
}
return true;
}
private boolean processSingleClassImport(@NotNull PsiScopeProcessor processor, @NotNull ResolveState state) {
if (!ResolveUtil.shouldProcessClasses(processor.getHint(ClassHint.KEY))) return true;
GrCodeReferenceElement ref = getImportReference();
if (ref == null) return true;
final PsiElement resolved = ref.resolve();
if (!(resolved instanceof PsiClass)) {
return true;
}
if (!isAliasedImport() && isFromSamePackage((PsiClass)resolved)) {
return true; //don't process classes from the same package because such import statements are ignored by compiler
}
return processor.execute(resolved, state.put(ClassHint.RESOLVE_CONTEXT, this));
}
private boolean isFromSamePackage(@NotNull PsiClass resolved) {
final String qualifiedName = resolved.getQualifiedName();
final String packageName = ((GroovyFile)getContainingFile()).getPackageName();
final String assumed = packageName + '.' + resolved.getName();
return !packageName.isEmpty() && assumed.equals(qualifiedName);
}
private boolean processDeclarationsForMultipleElements(@NotNull final PsiScopeProcessor processor,
@Nullable PsiElement lastParent,
@NotNull PsiElement place,
@NotNull ResolveState state) {
GrCodeReferenceElement ref = getImportReference();
if (ref == null) return true;
if (isStatic()) {
final PsiElement resolved = ref.resolve();
if (resolved instanceof PsiClass) {
state = state.put(ClassHint.RESOLVE_CONTEXT, this);
final PsiClass clazz = (PsiClass)resolved;
if (!clazz.processDeclarations(new DelegatingScopeProcessor(processor) {
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if (element instanceof PsiMember && ((PsiMember)element).hasModifierProperty(PsiModifier.STATIC)) {
return super.execute(element, state);
}
return true;
}
}, state, lastParent, place)) return false;
}
}
else {
if (ResolveUtil.shouldProcessClasses(processor.getHint(ClassHint.KEY))) {
String qName = PsiUtil.getQualifiedReferenceText(ref);
if (qName != null) {
PsiPackage aPackage = JavaPsiFacade.getInstance(getProject()).findPackage(qName);
if (aPackage != null && !((GroovyFile)getContainingFile()).getPackageName().equals(aPackage.getQualifiedName())) {
state = state.put(ClassHint.RESOLVE_CONTEXT, this);
if (!aPackage.processDeclarations(processor, state, lastParent, place)) return false;
}
}
}
}
return true;
}
@Override
public GrCodeReferenceElement getImportReference() {
GrImportStatementStub stub = getStub();
if (stub != null) {
String referenceText = stub.getReferenceText();
if (referenceText == null) {
return null;
}
return GroovyPsiElementFactory.getInstance(getProject()).createCodeReferenceElementFromText(referenceText);
}
return (GrCodeReferenceElement)findChildByType(GroovyElementTypes.REFERENCE_ELEMENT);
}
@Override
@Nullable
public String getImportedName() {
if (isOnDemand()) return null;
GrImportStatementStub stub = getStub();
if (stub != null) {
String name = stub.getAliasName();
if (name != null) {
return name;
}
String referenceText = stub.getReferenceText();
if (referenceText == null) return null;
return StringUtil.getShortName(referenceText);
}
PsiElement aliasNameElement = getAliasNameElement();
if (aliasNameElement != null) {
return aliasNameElement.getText();
}
GrCodeReferenceElement ref = getImportReference();
return ref == null ? null : ref.getReferenceName();
}
@Override
public boolean isStatic() {
GrImportStatementStub stub = getStub();
if (stub != null) {
return stub.isStatic();
}
return findChildByType(GroovyTokenTypes.kSTATIC) != null;
}
@Override
public boolean isAliasedImport() {
GrImportStatementStub stub = getStub();
if (stub != null) {
return stub.getAliasName() != null;
}
return getAliasNameElement() != null;
}
@Override
public boolean isOnDemand() {
GrImportStatementStub stub = getStub();
if (stub != null) {
return stub.isOnDemand();
}
return findChildByType(GroovyTokenTypes.mSTAR) != null;
}
@Override
@NotNull
public GrModifierList getAnnotationList() {
GrImportStatementStub stub = getStub();
if (stub != null) {
return ObjectUtils.assertNotNull(getStubOrPsiChild(GroovyElementTypes.MODIFIERS));
}
return findNotNullChildByClass(GrModifierList.class);
}
@Nullable
@Override
public PsiClass resolveTargetClass() {
final GrCodeReferenceElement ref = getImportReference();
if (ref == null) return null;
final PsiElement resolved;
if (!isStatic() || isOnDemand()) {
resolved = ref.resolve();
}
else {
resolved = resolveQualifier();
}
return resolved instanceof PsiClass ? (PsiClass)resolved : null;
}
@Nullable
@Override
public PsiElement getAliasNameElement() {
GrImportStatementStub stub = getStub();
if (stub != null) {
String alias = stub.getAliasName();
if (alias == null) return null;
GrImportStatement imp = GroovyPsiElementFactory.getInstance(getProject()).createImportStatementFromText("import A as " + alias);
return imp.getAliasNameElement();
}
return findChildByType(GroovyTokenTypes.mIDENT);
}
private static class StaticSetterProcessor extends StaticAccessorProcessor {
public StaticSetterProcessor(String refName, PsiScopeProcessor processor) {
super(refName, processor);
}
@Override
protected boolean isAccessor(@NotNull PsiMethod method) {
return GroovyPropertyUtils.isSimplePropertySetter(method, getPropertyName());
}
}
private static class StaticGetterProcessor extends StaticAccessorProcessor {
public StaticGetterProcessor(String refName, PsiScopeProcessor processor) {
super(refName, processor);
}
@Override
protected boolean isAccessor(@NotNull PsiMethod method) {
return GroovyPropertyUtils.isSimplePropertyGetter(method, getPropertyName());
}
}
/**
* Created by Max Medvedev on 26/03/14
*/
private abstract static class StaticAccessorProcessor extends GrDelegatingScopeProcessorWithHints {
private final String myPropertyName;
public StaticAccessorProcessor(@NotNull String propertyName, @NotNull PsiScopeProcessor processor) {
super(processor, null, RESOLVE_KINDS_METHOD);
myPropertyName = propertyName;
}
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod)element;
if (method.hasModifierProperty(PsiModifier.STATIC) && isAccessor(method)) {
return super.execute(method, state);
}
}
return true;
}
protected abstract boolean isAccessor(@NotNull PsiMethod method);
public String getPropertyName() {
return myPropertyName;
}
@Override
public <T> T getHint(@NotNull Key<T> hintKey) {
if (hintKey == NameHint.KEY) return null;
return super.getHint(hintKey);
}
}
}