blob: eb50db946d5484e1214e18d364073caa4025ed9b [file] [log] [blame]
package org.jetbrains.android;
import com.android.SdkConstants;
import com.android.resources.ResourceType;
import com.android.tools.idea.AndroidPsiUtils;
import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.navigation.GotoRelatedItem;
import com.intellij.navigation.GotoRelatedProvider;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.FileIndexFacade;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.Processor;
import com.intellij.util.containers.HashSet;
import org.jetbrains.android.dom.AndroidAttributeValue;
import org.jetbrains.android.dom.AndroidDomUtil;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidGotoRelatedProvider extends GotoRelatedProvider {
public static boolean ourAddDeclarationToManifest = false;
private static final String[] CONTEXT_CLASSES = {
SdkConstants.CLASS_ACTIVITY,
SdkConstants.CLASS_FRAGMENT,
SdkConstants.CLASS_V4_FRAGMENT,
"android.widget.Adapter"
};
@NotNull
@Override
public List<? extends GotoRelatedItem> getItems(@NotNull PsiElement element) {
final Computable<List<GotoRelatedItem>> items = getLazyItemsComputable(element);
return items != null ? items.compute() : Collections.<GotoRelatedItem>emptyList();
}
@Nullable
private static Computable<List<GotoRelatedItem>> getLazyItemsComputable(@NotNull PsiElement element) {
final PsiFile file = element.getContainingFile();
if (!(file instanceof XmlFile) && !(file instanceof PsiJavaFile)) {
return null;
}
final VirtualFile vFile = file.getVirtualFile();
if (vFile == null) {
return null;
}
final Project project = element.getProject();
if (!FileIndexFacade.getInstance(project).isInContent(vFile)) {
return null;
}
final Module module = ModuleUtilCore.findModuleForFile(vFile, project);
if (module == null) {
return null;
}
final AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet == null) {
return null;
}
if (file instanceof PsiJavaFile) {
PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class, false);
if (aClass == null) {
final PsiClass[] rootClasses = ((PsiJavaFile)file).getClasses();
if (rootClasses.length == 1) {
aClass = rootClasses[0];
}
}
if (aClass != null) {
return getLazyItemsForClass(aClass, facet, ourAddDeclarationToManifest);
}
}
else {
return getLazyItemsForXmlFile((XmlFile)file, facet);
}
return null;
}
@Nullable
public static Computable<List<GotoRelatedItem>> getLazyItemsForXmlFile(@NotNull XmlFile file, @NotNull AndroidFacet facet) {
final String resourceType = facet.getLocalResourceManager().getFileResourceType(file);
// TODO: Handle menus as well!
if (ResourceType.LAYOUT.getName().equals(resourceType)) {
return collectRelatedJavaFiles(file, facet);
}
return null;
}
@Nullable
static Computable<List<GotoRelatedItem>> getLazyItemsForClass(@NotNull PsiClass aClass,
@NotNull AndroidFacet facet,
boolean addDeclarationInManifest) {
final GotoRelatedItem item = findDeclarationInManifest(aClass);
final boolean isContextClass = isInheritorOfContextClass(aClass, facet.getModule());
if (!isContextClass && item == null) {
return null;
}
final List<GotoRelatedItem> items;
if (isContextClass) {
items = new ArrayList<GotoRelatedItem>(collectRelatedLayoutFiles(facet, aClass));
if (addDeclarationInManifest) {
if (item != null) {
items.add(item);
}
}
if (items.isEmpty()) {
return null;
}
}
else {
items = Collections.singletonList(item);
}
return new Computable<List<GotoRelatedItem>>() {
@Override
public List<GotoRelatedItem> compute() {
return items;
}
};
}
@Nullable
private static GotoRelatedItem findDeclarationInManifest(@NotNull PsiClass psiClass) {
final AndroidAttributeValue<PsiClass> domAttrValue = AndroidDomUtil.findComponentDeclarationInManifest(psiClass);
if (domAttrValue == null) {
return null;
}
final XmlAttributeValue attrValue = domAttrValue.getXmlAttributeValue();
return attrValue != null ? new MyGotoManifestItem(attrValue) : null;
}
private static boolean isInheritorOfContextClass(@NotNull PsiClass psiClass, @NotNull Module module) {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(module.getProject());
for (String contextClassName : CONTEXT_CLASSES) {
final PsiClass contextClass = facade.findClass(
contextClassName, module.getModuleWithDependenciesAndLibrariesScope(false));
if (contextClass != null && psiClass.isInheritor(contextClass, true)) {
return true;
}
}
return false;
}
@Nullable
private static Computable<List<GotoRelatedItem>> collectRelatedJavaFiles(@NotNull final XmlFile file,
@NotNull final AndroidFacet facet) {
final String resType = ResourceType.LAYOUT.getName();
final String resourceName = AndroidCommonUtils.getResourceName(resType, file.getName());
final PsiField[] fields = AndroidResourceUtil.findResourceFields(facet, resType, resourceName, true);
if (fields.length == 0 || fields.length > 1) {
return null;
}
final PsiField field = fields[0];
final Module module = facet.getModule();
final GlobalSearchScope scope = module.getModuleScope(false);
return new Computable<List<GotoRelatedItem>>() {
@Override
public List<GotoRelatedItem> compute() {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(module.getProject());
final List<PsiClass> psiContextClasses = new ArrayList<PsiClass>();
// Explicitly chosen in the layout/menu file with a tools:context attribute?
PsiClass declared = AndroidPsiUtils.getContextClass(module, file);
if (declared != null) {
return Collections.singletonList(new GotoRelatedItem(declared, "JAVA"));
}
for (String contextClassName : CONTEXT_CLASSES) {
final PsiClass contextClass = facade.findClass(
contextClassName, module.getModuleWithDependenciesAndLibrariesScope(false));
if (contextClass != null) {
psiContextClasses.add(contextClass);
}
}
if (psiContextClasses.isEmpty()) {
return Collections.emptyList();
}
final List<GotoRelatedItem> result = new ArrayList<GotoRelatedItem>();
ReferencesSearch.search(field, scope).forEach(new Processor<PsiReference>() {
@Override
public boolean process(PsiReference reference) {
PsiElement element = reference.getElement();
if (!(element instanceof PsiReferenceExpression)) {
return true;
}
element = element.getParent();
if (!(element instanceof PsiExpressionList)) {
return true;
}
element = element.getParent();
if (!(element instanceof PsiMethodCallExpression)) {
return true;
}
final String methodName = ((PsiMethodCallExpression)element).
getMethodExpression().getReferenceName();
if ("setContentView".equals(methodName) || "inflate".equals(methodName)) {
final PsiClass relatedClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
if (relatedClass != null && isInheritorOfOne(relatedClass, psiContextClasses)) {
result.add(new GotoRelatedItem(relatedClass, "JAVA"));
}
}
return true;
}
});
return result;
}
};
}
private static boolean isInheritorOfOne(@NotNull PsiClass psiClass, @NotNull Collection<PsiClass> possibleBaseClasses) {
for (PsiClass baseClass : possibleBaseClasses) {
if (psiClass.isInheritor(baseClass, true)) {
return true;
}
}
return false;
}
@NotNull
private static List<GotoRelatedItem> collectRelatedLayoutFiles(@NotNull final AndroidFacet facet, @NotNull PsiClass context) {
final Set<PsiFile> files = new HashSet<PsiFile>();
context.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
final String resClassName = ResourceType.LAYOUT.getName();
final AndroidResourceUtil.MyReferredResourceFieldInfo
info = AndroidResourceUtil.getReferredResourceOrManifestField(facet, expression, resClassName, true);
if (info == null || info.isFromManifest()) {
return;
}
final String resFieldName = info.getFieldName();
final List<PsiElement> resources = facet.getLocalResourceManager().findResourcesByFieldName(resClassName, resFieldName);
for (PsiElement resource : resources) {
if (resource instanceof PsiFile) {
files.add((PsiFile)resource);
}
}
}
});
if (files.isEmpty()) {
return Collections.emptyList();
}
final List<GotoRelatedItem> result = new ArrayList<GotoRelatedItem>(files.size());
for (PsiFile file : files) {
result.add(new MyGotoRelatedLayoutItem(file));
}
return result;
}
private static class MyGotoRelatedLayoutItem extends GotoRelatedItem {
private final PsiFile myFile;
public MyGotoRelatedLayoutItem(@NotNull PsiFile file) {
super(file, "Layout Files");
myFile = file;
}
@Nullable
@Override
public String getCustomContainerName() {
final PsiDirectory directory = myFile.getContainingDirectory();
return directory != null ? "(" + directory.getName() + ")" : null;
}
}
private static class MyGotoManifestItem extends GotoRelatedItem {
public MyGotoManifestItem(@NotNull XmlAttributeValue attributeValue) {
super(attributeValue);
}
@Nullable
@Override
public String getCustomName() {
return "AndroidManifest.xml";
}
@Nullable
@Override
public String getCustomContainerName() {
return "";
}
@Nullable
@Override
public Icon getCustomIcon() {
return XmlFileType.INSTANCE.getIcon();
}
}
}