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