blob: 904d1b891abcabb1462ab1f6a1ac0244b50f28e0 [file] [log] [blame]
package org.jetbrains.android.dom.converters;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.HashSet;
import com.intellij.util.xml.ConvertContext;
import com.intellij.util.xml.Converter;
import com.intellij.util.xml.CustomReferenceConverter;
import com.intellij.util.xml.GenericDomValue;
import org.jetbrains.android.inspections.AndroidMissingOnClickHandlerInspection;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public abstract class OnClickConverter extends Converter<String> implements CustomReferenceConverter<String> {
private static final String DEFAULT_MENU_ITEM_CLASS = "android.view.MenuItem";
private static final String ABS_MENU_ITEM_CLASS = "com.actionbarsherlock.view.MenuItem";
public static final OnClickConverter CONVERTER_FOR_LAYOUT = new OnClickConverter() {
@NotNull
@Override
public String getDefaultMethodParameterType(@NotNull PsiClass parentClass) {
return AndroidUtils.VIEW_CLASS_NAME;
}
@Override
protected boolean isAllowedMethodParameterType(@NotNull String type) {
return AndroidUtils.VIEW_CLASS_NAME.equals(type);
}
@NotNull
@Override
public String getShortParameterName() {
return "View";
}
};
public static final OnClickConverter CONVERTER_FOR_MENU = new OnClickConverter() {
@NotNull
@Override
public String getDefaultMethodParameterType(@NotNull PsiClass parentClass) {
final Project project = parentClass.getProject();
final PsiClass watsonClass = JavaPsiFacade.getInstance(project).findClass(
"android.support.v4.app.Watson", GlobalSearchScope.projectScope(project));
return watsonClass != null && parentClass.isInheritor(watsonClass, true)
? ABS_MENU_ITEM_CLASS
: DEFAULT_MENU_ITEM_CLASS;
}
@Override
protected boolean isAllowedMethodParameterType(@NotNull String type) {
return DEFAULT_MENU_ITEM_CLASS.equals(type) || ABS_MENU_ITEM_CLASS.equals(type);
}
@NotNull
@Override
public String getShortParameterName() {
return "MenuItem";
}
};
@NotNull
public abstract String getDefaultMethodParameterType(@NotNull PsiClass parentClass);
protected abstract boolean isAllowedMethodParameterType(@NotNull String type);
@NotNull
public abstract String getShortParameterName();
@NotNull
@Override
public PsiReference[] createReferences(GenericDomValue<String> value, PsiElement element, ConvertContext context) {
final int length = element.getTextLength();
if (length > 1) {
return new PsiReference[]{new MyReference((XmlAttributeValue)element, new TextRange(1, length - 1))};
}
return PsiReference.EMPTY_ARRAY;
}
@Override
public String fromString(@Nullable @NonNls String s, ConvertContext context) {
return s;
}
@Override
public String toString(@Nullable String s, ConvertContext context) {
return s;
}
public class MyReference extends PsiPolyVariantReferenceBase<XmlAttributeValue> {
private MyReference(XmlAttributeValue value, TextRange range) {
super(value, range, true);
}
@NotNull
@Override
public ResolveResult[] multiResolve(boolean incompleteCode) {
return ResolveCache.getInstance(myElement.getProject())
.resolveWithCaching(this, new ResolveCache.PolyVariantResolver<MyReference>() {
@NotNull
@Override
public ResolveResult[] resolve(@NotNull MyReference myReference, boolean incompleteCode) {
return resolveInner();
}
}, false, incompleteCode);
}
@NotNull
private ResolveResult[] resolveInner() {
final String methodName = myElement.getValue();
if (methodName == null) {
return ResolveResult.EMPTY_ARRAY;
}
final Module module = ModuleUtilCore.findModuleForPsiElement(myElement);
if (module == null) {
return ResolveResult.EMPTY_ARRAY;
}
final Project project = myElement.getProject();
final PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project);
final PsiMethod[] methods = cache.getMethodsByName(methodName, module.getModuleWithDependenciesScope());
if (methods.length == 0) {
return ResolveResult.EMPTY_ARRAY;
}
final PsiClass activityBaseClass = JavaPsiFacade.getInstance(project).findClass(
AndroidUtils.ACTIVITY_BASE_CLASS_NAME, module.getModuleWithDependenciesAndLibrariesScope(false));
if (activityBaseClass == null) {
return ResolveResult.EMPTY_ARRAY;
}
final List<ResolveResult> result = new ArrayList<ResolveResult>();
final List<ResolveResult> resultsWithMistake = new ArrayList<ResolveResult>();
for (PsiMethod method : methods) {
final PsiClass parentClass = method.getContainingClass();
if (parentClass != null && parentClass.isInheritor(activityBaseClass, true)) {
if (checkSignature(method)) {
result.add(new MyResolveResult(method, true));
}
else {
resultsWithMistake.add(new MyResolveResult(method, false));
}
}
}
return result.size() > 0
? result.toArray(new ResolveResult[result.size()])
: resultsWithMistake.toArray(new ResolveResult[resultsWithMistake.size()]);
}
@NotNull
@Override
public Object[] getVariants() {
final Module module = ModuleUtilCore.findModuleForPsiElement(myElement);
if (module == null) {
return ResolveResult.EMPTY_ARRAY;
}
final PsiClass activityClass = AndroidMissingOnClickHandlerInspection.findActivityClass(module);
if (activityClass == null) {
return EMPTY_ARRAY;
}
final List<Object> result = new ArrayList<Object>();
final Set<String> methodNames = new HashSet<String>();
ClassInheritorsSearch.search(activityClass, module.getModuleWithDependenciesScope(), true).forEach(new Processor<PsiClass>() {
@Override
public boolean process(PsiClass c) {
for (PsiMethod method : c.getMethods()) {
if (checkSignature(method) && methodNames.add(method.getName())) {
result.add(createLookupElement(method));
}
}
return true;
}
});
return ArrayUtil.toObjectArray(result);
}
@NotNull
public OnClickConverter getConverter() {
return OnClickConverter.this;
}
}
public boolean checkSignature(@NotNull PsiMethod method) {
if (method.getReturnType() != PsiType.VOID) {
return false;
}
if (method.hasModifierProperty(PsiModifier.STATIC) ||
method.hasModifierProperty(PsiModifier.ABSTRACT) ||
!method.hasModifierProperty(PsiModifier.PUBLIC)) {
return false;
}
final PsiClass aClass = method.getContainingClass();
if (aClass == null || aClass.isInterface()) {
return false;
}
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length != 1) {
return false;
}
final PsiType paramType = parameters[0].getType();
if (!(paramType instanceof PsiClassType)) {
return false;
}
final PsiClass paramClass = ((PsiClassType)paramType).resolve();
if (paramClass == null) {
return false;
}
final String paramClassName = paramClass.getQualifiedName();
return paramClassName != null && isAllowedMethodParameterType(paramClassName);
}
public boolean findHandlerMethod(@NotNull PsiClass psiClass, @NotNull String methodName) {
final PsiMethod[] methods = psiClass.findMethodsByName(methodName, false);
for (PsiMethod method : methods) {
if (checkSignature(method)) {
return true;
}
}
return false;
}
private static LookupElement createLookupElement(PsiMethod method) {
final LookupElementBuilder builder = LookupElementBuilder.create(method, method.getName())
.withIcon(method.getIcon(Iconable.ICON_FLAG_VISIBILITY))
.withPresentableText(method.getName());
final PsiClass containingClass = method.getContainingClass();
return containingClass != null
? builder.withTailText(" (" + containingClass.getQualifiedName() + ')')
: builder;
}
public static class MyResolveResult extends PsiElementResolveResult {
private final boolean myHasCorrectSignature;
public MyResolveResult(@NotNull PsiElement element, boolean hasCorrectSignature) {
super(element);
myHasCorrectSignature = hasCorrectSignature;
}
public boolean hasCorrectSignature() {
return myHasCorrectSignature;
}
}
}