blob: a0590a5b5729c69518181e483774af4e3088b223 [file] [log] [blame]
package org.jetbrains.android.dom;
import com.intellij.codeInsight.completion.JavaLookupElementBuilder;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlChildRole;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ProcessingContext;
import com.intellij.util.Processor;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidXmlReferenceProvider extends PsiReferenceProvider {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
if (!(element instanceof XmlTag)) {
return PsiReference.EMPTY_ARRAY;
}
final Module module = ModuleUtilCore.findModuleForPsiElement(element);
if (module == null || AndroidFacet.getInstance(module) == null) {
return PsiReference.EMPTY_ARRAY;
}
final ASTNode startTagName = XmlChildRole.START_TAG_NAME_FINDER.findChild(element.getNode());
final String baseClassQName = computeBaseClass((XmlTag)element);
if (baseClassQName == null) {
return PsiReference.EMPTY_ARRAY;
}
final List<PsiReference> result = new ArrayList<PsiReference>();
final XmlTag tag = (XmlTag)element;
if (startTagName != null && areReferencesProvidedByReferenceProvider(startTagName)) {
addReferences(tag, startTagName.getPsi(), result, module, baseClassQName, true);
}
final ASTNode closingTagName = XmlChildRole.CLOSING_TAG_NAME_FINDER.findChild(element.getNode());
if (closingTagName != null && areReferencesProvidedByReferenceProvider(closingTagName)) {
addReferences(tag, closingTagName.getPsi(), result, module, baseClassQName, false);
}
return result.toArray(new PsiReference[result.size()]);
}
private static void addReferences(@NotNull XmlTag tag,
@NotNull PsiElement nameElement,
@NotNull List<PsiReference> result,
@NotNull Module module,
@NotNull String baseClassQName,
boolean startTag) {
final String text = nameElement.getText();
if (text == null) {
return;
}
final String[] nameParts = text.split("\\.");
if (nameParts.length == 0) {
return;
}
int offset = 0;
for (int i = 0; i < nameParts.length; i++) {
final String name = nameParts[i];
if (name.length() > 0) {
offset += name.length();
final TextRange range = new TextRange(offset - name.length(), offset);
final boolean isPackage = i < nameParts.length - 1;
result.add(new MyClassOrPackageReference(tag, nameElement, range, isPackage, module, baseClassQName, startTag));
}
offset++;
}
}
public static boolean areReferencesProvidedByReferenceProvider(ASTNode nameElement) {
if (nameElement != null) {
final PsiElement psiNameElement = nameElement.getPsi();
final XmlTag tag = psiNameElement != null
? PsiTreeUtil.getParentOfType(psiNameElement, XmlTag.class)
: null;
if (tag != null) {
final String baseClassQName = computeBaseClass(tag);
if (baseClassQName != null) {
final String text = nameElement.getText();
return text != null && text.contains(".");
}
}
}
return false;
}
@Nullable
private static String computeBaseClass(XmlTag context) {
final XmlTag parentTag = context.getParentTag();
final Pair<AndroidDomElement, String> pair =
AndroidDomElementDescriptorProvider.getDomElementAndBaseClassQName(parentTag != null ? parentTag : context);
return pair != null ? pair.getSecond() : null;
}
private static class MyClassOrPackageReference extends PsiReferenceBase<PsiElement> {
private final PsiElement myNameElement;
private final TextRange myRangeInNameElement;
private final boolean myIsPackage;
private final Module myModule;
private final String myBaseClassQName;
private final boolean myStartTag;
public MyClassOrPackageReference(@NotNull XmlTag tag,
@NotNull PsiElement nameElement,
@NotNull TextRange rangeInNameElement,
boolean isPackage,
@NotNull Module module,
@NotNull String baseClassQName,
boolean startTag) {
super(tag, rangeInParent(nameElement, rangeInNameElement), true);
myNameElement = nameElement;
myRangeInNameElement = rangeInNameElement;
myIsPackage = isPackage;
myModule = module;
myBaseClassQName = baseClassQName;
myStartTag = startTag;
}
private static TextRange rangeInParent(PsiElement element, TextRange range) {
final int offset = element.getStartOffsetInParent();
return new TextRange(range.getStartOffset() + offset, range.getEndOffset() + offset);
}
@Override
public PsiElement resolve() {
return ResolveCache.getInstance(myElement.getProject()).resolveWithCaching(this, new ResolveCache.Resolver() {
@Nullable
@Override
public PsiElement resolve(@NotNull PsiReference reference, boolean incompleteCode) {
return resolveInner();
}
}, false, false);
}
@Nullable
private PsiElement resolveInner() {
final int end = myRangeInNameElement.getEndOffset();
final String value = myNameElement.getText().substring(0, end);
final JavaPsiFacade facade = JavaPsiFacade.getInstance(myElement.getProject());
return myIsPackage ?
facade.findPackage(value) :
facade.findClass(value, myModule.getModuleWithDependenciesAndLibrariesScope(false));
}
@NotNull
@Override
public Object[] getVariants() {
final String prefix = myNameElement.getText().substring(0, myRangeInNameElement.getStartOffset());
if (!myStartTag) {
final ASTNode startTagNode = XmlChildRole.START_TAG_NAME_FINDER.findChild(myElement.getNode());
if (startTagNode != null) {
final String startTagName = startTagNode.getText();
if (startTagName != null) {
if (startTagName.startsWith(prefix)) {
return new Object[]{startTagName.substring(prefix.length())};
}
}
}
return EMPTY_ARRAY;
}
final Project project = myModule.getProject();
final PsiClass baseClass =
JavaPsiFacade.getInstance(project).findClass(myBaseClassQName, myModule.getModuleWithDependenciesAndLibrariesScope(false));
if (baseClass == null) {
return EMPTY_ARRAY;
}
final List<Object> result = new ArrayList<Object>();
ClassInheritorsSearch.search(baseClass, myModule.getModuleWithDependenciesAndLibrariesScope(false), true, true, false).forEach(
new Processor<PsiClass>() {
@Override
public boolean process(PsiClass psiClass) {
if (psiClass.getContainingClass() != null) {
return true;
}
String name = psiClass.getQualifiedName();
if (name != null && name.startsWith(prefix)) {
name = name.substring(prefix.length());
result.add(JavaLookupElementBuilder.forClass(psiClass, name, true));
}
return true;
}
});
return ArrayUtil.toObjectArray(result);
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
final String newName = myIsPackage
? ((PsiPackage)element).getQualifiedName()
: ((PsiClass)element).getQualifiedName();
final ElementManipulator<PsiElement> manipulator = ElementManipulators.getManipulator(myNameElement);
final TextRange range = new TextRange(0, myRangeInNameElement.getEndOffset());
return manipulator != null ? manipulator.handleContentChange(myNameElement, range, newName) : element;
}
@Nullable
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
final ElementManipulator<PsiElement> manipulator = ElementManipulators.getManipulator(myNameElement);
assert manipulator != null : "Cannot find manipulator for " + myNameElement;
return manipulator.handleContentChange(myNameElement, myRangeInNameElement, newElementName);
}
}
}