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); } } }