mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-69782 IDEA-67009 converter for onClick xml attribute
This commit is contained in:
@@ -23,6 +23,7 @@ import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import com.intellij.util.xml.Converter;
|
||||
import com.intellij.util.xml.DomElement;
|
||||
import com.intellij.util.xml.ResolvingConverter;
|
||||
import com.intellij.util.xml.XmlName;
|
||||
@@ -59,6 +60,7 @@ public class AndroidDomUtil {
|
||||
public static final StaticEnumConverter BOOLEAN_CONVERTER = new StaticEnumConverter("true", "false");
|
||||
public static final Map<String, String> SPECIAL_RESOURCE_TYPES = new HashMap<String, String>();
|
||||
private static final PackageClassConverter ACTIVITY_CONVERTER = new PackageClassConverter(AndroidUtils.ACTIVITY_BASE_CLASS_NAME);
|
||||
private static final OnClickConverter ON_CLICK_CONVERTER = new OnClickConverter();
|
||||
|
||||
static {
|
||||
addSpecialResourceType("string", "label", "description", "title");
|
||||
@@ -146,7 +148,7 @@ public class AndroidDomUtil {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ResolvingConverter getSpecificConverter(@NotNull XmlName attrName, DomElement context) {
|
||||
public static Converter getSpecificConverter(@NotNull XmlName attrName, DomElement context) {
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -168,6 +170,11 @@ public class AndroidDomUtil {
|
||||
return ACTIVITY_CONVERTER;
|
||||
}
|
||||
}
|
||||
else if (context instanceof LayoutViewElement) {
|
||||
if ("onClick".equals(localName)) {
|
||||
return ON_CLICK_CONVERTER;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package org.jetbrains.android.dom.converters;
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
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.PsiManagerEx;
|
||||
import com.intellij.psi.impl.source.resolve.ResolveCache;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.ProjectScope;
|
||||
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.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 class OnClickConverter extends Converter<String> implements CustomReferenceConverter<String> {
|
||||
@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;
|
||||
}
|
||||
|
||||
private static class MyReference extends PsiPolyVariantReferenceBase<XmlAttributeValue> {
|
||||
|
||||
public MyReference(XmlAttributeValue value, TextRange range) {
|
||||
super(value, range, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ResolveResult[] multiResolve(boolean incompleteCode) {
|
||||
return ((PsiManagerEx)getElement().getManager()).getResolveCache()
|
||||
.resolveWithCaching(this, new ResolveCache.PolyVariantResolver<MyReference>() {
|
||||
@Override
|
||||
public ResolveResult[] resolve(MyReference myReference, boolean incompleteCode) {
|
||||
return resolveInner();
|
||||
}
|
||||
}, false, incompleteCode);
|
||||
}
|
||||
|
||||
private ResolveResult[] resolveInner() {
|
||||
final String methodName = myElement.getValue();
|
||||
if (methodName == null) {
|
||||
return ResolveResult.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
final Project project = myElement.getProject();
|
||||
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
|
||||
final PsiShortNamesCache cache = psiFacade.getShortNamesCache();
|
||||
|
||||
final PsiMethod[] methods = cache.getMethodsByName(methodName, GlobalSearchScope.projectScope(project));
|
||||
if (methods.length == 0) {
|
||||
return ResolveResult.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
final List<ResolveResult> result = new ArrayList<ResolveResult>();
|
||||
for (PsiMethod method : methods) {
|
||||
if (checkSignature(method)) {
|
||||
result.add(new PsiElementResolveResult(method));
|
||||
}
|
||||
}
|
||||
return result.toArray(new ResolveResult[result.size()]);
|
||||
}
|
||||
|
||||
private static boolean checkSignature(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();
|
||||
return paramClass != null && AndroidUtils.VIEW_CLASS_NAME.equals(paramClass.getQualifiedName());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object[] getVariants() {
|
||||
final Project project = myElement.getProject();
|
||||
final GlobalSearchScope scope = ProjectScope.getAllScope(project);
|
||||
|
||||
final PsiClass activityClass = JavaPsiFacade.getInstance(project).findClass(AndroidUtils.ACTIVITY_BASE_CLASS_NAME, scope);
|
||||
if (activityClass == null) {
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
final List<Object> result = new ArrayList<Object>();
|
||||
final Set<String> methodNames = new HashSet<String>();
|
||||
|
||||
ClassInheritorsSearch.search(activityClass, scope, true).forEach(new Processor<PsiClass>() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private static LookupElement createLookupElement(PsiMethod method) {
|
||||
final LookupElementBuilder builder = LookupElementBuilder.create(method, method.getName())
|
||||
.setIcon(method.getIcon(Iconable.ICON_FLAG_VISIBILITY))
|
||||
.setPresentableText(method.getName());
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
return containingClass != null
|
||||
? builder.setTailText(" (" + containingClass.getQualifiedName() + ')')
|
||||
: builder;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.intellij.util.xml.WrappingConverter;
|
||||
import com.intellij.util.xml.highlighting.BasicDomElementsInspection;
|
||||
import org.jetbrains.android.dom.AndroidDomElement;
|
||||
import org.jetbrains.android.dom.converters.AndroidPackageConverter;
|
||||
import org.jetbrains.android.dom.converters.OnClickConverter;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
@@ -55,6 +56,7 @@ public class AndroidDomInspection extends BasicDomElementsInspection<AndroidDomE
|
||||
@Override
|
||||
protected boolean shouldCheckResolveProblems(GenericDomValue value) {
|
||||
final Converter realConverter = WrappingConverter.getDeepestConverter(value.getConverter(), value);
|
||||
return !(realConverter instanceof AndroidPackageConverter);
|
||||
return !(realConverter instanceof AndroidPackageConverter) &&
|
||||
!(realConverter instanceof OnClickConverter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package p1.p2;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
|
||||
public class OnClick_Class1 extends Activity {
|
||||
public void clickHandler1(View v) {
|
||||
}
|
||||
|
||||
void clickHandler2(View v) {
|
||||
}
|
||||
|
||||
public void clickHandler3(String s) {
|
||||
}
|
||||
|
||||
public abstract void clickHandler4(View v) {
|
||||
}
|
||||
|
||||
public static void clickHandler5(View v) {
|
||||
}
|
||||
|
||||
public int clickHandler6(View v) {
|
||||
}
|
||||
|
||||
public final void clickHandler7(View v) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package p1.p2;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
|
||||
public class OnClick_Class2 extends Activity {
|
||||
public final void clickHandler1(View v) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package p1.p2;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
|
||||
public class OnClick_Class3 {
|
||||
public void clickHandler1(View v) {
|
||||
}
|
||||
|
||||
public void clickHandler123(View v) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package p1.p2;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
|
||||
public class OnClick_Class4 {
|
||||
void clickHandler1(View v) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<TextView android:onClick="<caret>"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
>
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="clickHandler"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="nonexistentClickHandler"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<TextView android:onClick="clickH<caret>andler1"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package org.jetbrains.android.dom;
|
||||
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.intellij.codeInsight.TargetElementUtilBase;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -282,5 +281,37 @@ public class AndroidLayoutDomTest extends AndroidDomTest {
|
||||
PsiElement rootViewClass = rootReference.resolve();
|
||||
assertTrue("Must be PsiClass reference", rootViewClass instanceof PsiClass);
|
||||
}
|
||||
|
||||
public void testOnClickCompletion() throws Throwable {
|
||||
copyOnClickClasses();
|
||||
doTestCompletionVariants(getTestName(true) + ".xml", "clickHandler1", "clickHandler7");
|
||||
}
|
||||
|
||||
public void testOnClickHighlighting() throws Throwable {
|
||||
copyOnClickClasses();
|
||||
doTestHighlighting();
|
||||
}
|
||||
|
||||
public void testOnClickNavigation() throws Throwable {
|
||||
copyOnClickClasses();
|
||||
final VirtualFile file = copyFileToProject(getTestName(true) + ".xml");
|
||||
myFixture.configureFromExistingVirtualFile(file);
|
||||
|
||||
final PsiReference reference = TargetElementUtilBase.findReference(myFixture.getEditor(), myFixture.getCaretOffset());
|
||||
assertNotNull(reference);
|
||||
assertInstanceOf(reference, PsiPolyVariantReference.class);
|
||||
final ResolveResult[] results = ((PsiPolyVariantReference)reference).multiResolve(false);
|
||||
assertEquals(3, results.length);
|
||||
for (ResolveResult result : results) {
|
||||
assertInstanceOf(result.getElement(), PsiMethod.class);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyOnClickClasses() throws IOException {
|
||||
copyFileToProject("OnClick_Class1.java", "src/p1/p2/OnClick_Class1.java");
|
||||
copyFileToProject("OnClick_Class2.java", "src/p1/p2/OnClick_Class2.java");
|
||||
copyFileToProject("OnClick_Class3.java", "src/p1/p2/OnClick_Class3.java");
|
||||
copyFileToProject("OnClick_Class4.java", "src/p1/p2/OnClick_Class4.java");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user