blob: 6888ed107e56a6a2884ac4040abc3b33ad52d30f [file] [log] [blame]
package org.jetbrains.android.dom;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.intention.AbstractIntentionAction;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.ide.util.ClassFilter;
import com.intellij.ide.util.TreeClassChooser;
import com.intellij.ide.util.TreeClassChooserFactory;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.PsiNavigateUtil;
import com.intellij.util.xml.DomManager;
import com.intellij.util.xml.GenericAttributeValue;
import org.jetbrains.android.dom.converters.OnClickConverter;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidCreateOnClickHandlerAction extends AbstractIntentionAction implements HighPriorityAction {
@NotNull
@Override
public String getText() {
return AndroidBundle.message("create.on.click.handler.intention.text");
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
if (editor == null || !(file instanceof XmlFile)) {
return false;
}
final AndroidFacet facet = AndroidFacet.getInstance(file);
if (facet == null) {
return false;
}
final XmlAttributeValue attrValue = getXmlAttributeValue(file, editor);
if (attrValue == null) {
return false;
}
final PsiElement parent = attrValue.getParent();
if (!(parent instanceof XmlAttribute)) {
return false;
}
final GenericAttributeValue domValue = DomManager.getDomManager(project).getDomElement((XmlAttribute)parent);
if (domValue == null || !(domValue.getConverter() instanceof OnClickConverter)) {
return false;
}
final String methodName = attrValue.getValue();
return methodName != null && StringUtil.isJavaIdentifier(methodName);
}
@Nullable
private static XmlAttributeValue getXmlAttributeValue(PsiFile file, Editor editor) {
final int offset = editor.getCaretModel().getOffset();
final PsiElement element = file.findElementAt(offset);
return element != null ? PsiTreeUtil.getParentOfType(element, XmlAttributeValue.class) : null;
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
final AndroidFacet facet = AndroidFacet.getInstance(file);
assert facet != null;
final XmlAttributeValue attrValue = getXmlAttributeValue(file, editor);
assert attrValue != null;
final String methodName = attrValue.getValue();
assert methodName != null;
final GenericAttributeValue domValue = DomManager.getDomManager(project).getDomElement((XmlAttribute)attrValue.getParent());
assert domValue != null;
final OnClickConverter converter = (OnClickConverter)domValue.getConverter();
final PsiClass activityBaseClass = JavaPsiFacade.getInstance(project).findClass(
AndroidUtils.ACTIVITY_BASE_CLASS_NAME, facet.getModule().getModuleWithDependenciesAndLibrariesScope(false));
if (activityBaseClass == null) {
return;
}
final GlobalSearchScope scope = facet.getModule().getModuleScope(false);
final PsiClass selectedClass;
if (ApplicationManager.getApplication().isUnitTestMode()) {
final Ref<PsiClass> selClassRef = Ref.create();
ClassInheritorsSearch.search(activityBaseClass, scope, true, true, false).forEach(new Processor<PsiClass>() {
@Override
public boolean process(PsiClass psiClass) {
if (!psiClass.isInterface() && !psiClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
selClassRef.set(psiClass);
return false;
}
return true;
}
});
selectedClass = selClassRef.get();
}
else {
final TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project).createInheritanceClassChooser(
"Choose Activity to Create the Method", scope, activityBaseClass, null, new ClassFilter() {
@Override
public boolean isAccepted(PsiClass aClass) {
return !converter.findHandlerMethod(aClass, methodName);
}
});
chooser.showDialog();
selectedClass = chooser.getSelected();
}
if (selectedClass != null) {
addHandlerMethodAndNavigate(project, selectedClass, methodName, converter.getDefaultMethodParameterType(selectedClass));
}
}
@NotNull
private static String suggestVarName(@NotNull String type) {
for (int i = type.length() - 1; i >= 0; i--) {
final char c = type.charAt(i);
if (Character.isUpperCase(c)) {
return type.substring(i).toLowerCase();
}
}
return "o";
}
@Nullable
public static PsiMethod addHandlerMethod(@NotNull Project project,
@NotNull PsiClass psiClass,
@NotNull String methodName,
@NotNull String methodParamType) {
final PsiFile file = psiClass.getContainingFile();
if (file == null || !FileModificationService.getInstance().prepareFileForWrite(file)) {
return null;
}
final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
final String varName = suggestVarName(methodParamType);
PsiMethod method = (PsiMethod)psiClass.add(factory.createMethodFromText(
"public void " + methodName + "(" + methodParamType + " " + varName + ") {}", psiClass));
PsiMethod method1 = (PsiMethod)CodeStyleManager.getInstance(project).reformat(method);
method1 = (PsiMethod)JavaCodeStyleManager.getInstance(project).shortenClassReferences(method1);
return (PsiMethod)method.replace(method1);
}
public static void addHandlerMethodAndNavigate(@NotNull final Project project,
@NotNull final PsiClass psiClass,
@NotNull final String methodName,
@NotNull final String methodParamType) {
if (!AndroidUtils.isIdentifier(methodName)) {
Messages.showErrorDialog(project, String.format("%1$s is not a valid Java identifier/method name.", methodName), "Invalid Name");
return;
}
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
final PsiMethod method = addHandlerMethod(project, psiClass, methodName, methodParamType);
if (method == null) {
return;
}
if (!ApplicationManager.getApplication().isUnitTestMode()) {
PsiNavigateUtil.navigate(method);
}
final PsiFile javaFile = method.getContainingFile();
if (javaFile == null) {
return;
}
final Editor javaEditor = PsiUtilBase.findEditor(method);
if (javaEditor == null) {
return;
}
final PsiCodeBlock body = method.getBody();
if (body != null) {
final PsiJavaToken lBrace = body.getLBrace();
if (lBrace != null) {
javaEditor.getCaretModel().moveToOffset(lBrace.getTextRange().getEndOffset());
}
}
}
});
}
}