blob: 28ff3a75047b0cd97b652e4e615fda64a2628da8 [file] [log] [blame]
package org.jetbrains.android;
import com.android.ide.common.resources.ResourceUrl;
import com.android.resources.ResourceFolderType;
import com.intellij.ide.TitledHandler;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.refactoring.rename.RenameDialog;
import com.intellij.refactoring.rename.RenameHandler;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomManager;
import com.intellij.util.xml.GenericAttributeValue;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.dom.wrappers.ValueResourceElementWrapper;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidRenameHandler implements RenameHandler, TitledHandler {
@Override
public boolean isAvailableOnDataContext(DataContext dataContext) {
final Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
if (editor == null) {
return false;
}
final PsiFile file = CommonDataKeys.PSI_FILE.getData(dataContext);
if (file == null) {
return false;
}
if (AndroidUsagesTargetProvider.findValueResourceTagInContext(editor, file) != null) {
return true;
}
final Project project = CommonDataKeys.PROJECT.getData(dataContext);
if (project == null) {
return false;
}
final PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
return element != null && isPackageAttributeInManifest(project, element);
}
@Override
public boolean isRenaming(DataContext dataContext) {
return isAvailableOnDataContext(dataContext);
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
if (file == null || editor == null) {
return;
}
final XmlTag tag = AndroidUsagesTargetProvider.findValueResourceTagInContext(editor, file);
if (tag != null) {
// See if you've actually pointed at an XML value inside the value definition, e.g.
// <string name="my_alias">@string/my_string</string>
// If the caret is on my_string, you expect to rename my_string, not my_alias (the XmlTag)
ResourceUrl url = findResourceReferenceUnderCaret(editor, file);
if (url != null && !url.framework) {
performResourceReferenceRenaming(project, editor, dataContext, file, url);
}
else {
performValueResourceRenaming(project, editor, dataContext, tag);
}
}
else {
performApplicationPackageRenaming(project, editor, dataContext);
}
}
private static void performValueResourceRenaming(Project project, Editor editor, DataContext dataContext, XmlTag tag) {
final XmlAttribute nameAttribute = tag.getAttribute("name");
if (nameAttribute == null) {
return;
}
final XmlAttributeValue attributeValue = nameAttribute.getValueElement();
if (attributeValue == null) {
return;
}
RenameDialog.showRenameDialog(dataContext, new RenameDialog(project, new ValueResourceElementWrapper(attributeValue), null, editor));
}
private static void performResourceReferenceRenaming(Project project,
Editor editor,
DataContext dataContext,
PsiFile file,
ResourceUrl url) {
assert !url.framework;
final AndroidFacet facet = AndroidFacet.getInstance(file);
if (facet != null) {
// Treat the resource reference as if the user renamed the R field instead
PsiField[] resourceFields = AndroidResourceUtil.findResourceFields(facet, url.type.getName(), url.name, false);
if (resourceFields.length == 1) {
RenameDialog.showRenameDialog(dataContext, new RenameDialog(project, resourceFields[0], null, editor));
}
}
}
@Nullable
private static ResourceUrl findResourceReferenceUnderCaret(@NotNull Editor editor, @NotNull PsiFile file) {
if (!(file instanceof XmlFile)) {
return null;
}
final AndroidFacet facet = AndroidFacet.getInstance(file);
if (facet == null) {
return null;
}
if (!AndroidResourceUtil.isInResourceSubdirectory(file, ResourceFolderType.VALUES.getName())) {
return null;
}
final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
if (element == null) {
return null;
}
if (element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_DATA_CHARACTERS) {
XmlText text = PsiTreeUtil.getParentOfType(element, XmlText.class);
if (text != null) {
return ResourceUrl.parse(text.getText().trim());
}
}
return null;
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
final Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
if (editor == null) {
return;
}
final PsiFile file = CommonDataKeys.PSI_FILE.getData(dataContext);
if (file == null) {
return;
}
invoke(project, editor, file, dataContext);
}
@Override
public String getActionTitle() {
return "Rename Android value resource";
}
static boolean isPackageAttributeInManifest(@NotNull Project project, @Nullable PsiElement element) {
if (element == null) {
return false;
}
final PsiFile psiFile = element.getContainingFile();
if (!(psiFile instanceof XmlFile)) {
return false;
}
final AndroidFacet facet = AndroidFacet.getInstance(psiFile);
if (facet == null) {
return false;
}
final VirtualFile vFile = psiFile.getVirtualFile();
if (vFile == null || !vFile.equals(AndroidRootUtil.getManifestFile(facet))) {
return false;
}
if (!(element instanceof XmlAttributeValue)) {
return false;
}
final PsiElement parent = element.getParent();
if (!(parent instanceof XmlAttribute)) {
return false;
}
final GenericAttributeValue attrValue = DomManager.getDomManager(project).getDomElement((XmlAttribute)parent);
if (attrValue == null) {
return false;
}
final DomElement parentDomElement = attrValue.getParent();
return parentDomElement instanceof Manifest && attrValue.equals(((Manifest)parentDomElement).getPackage());
}
private static void performApplicationPackageRenaming(@NotNull Project project,
@NotNull Editor editor,
@NotNull DataContext context) {
PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(context);
if (!(element instanceof XmlAttributeValue)) {
return;
}
final Module module = ModuleUtilCore.findModuleForPsiElement(element);
if (module == null) {
return;
}
RenameDialog.showRenameDialog(context, new RenameDialog(project, element, null, editor) {
@NotNull
@Override
protected String getLabelText() {
return "Rename Android application package of module '" + module.getName() + "' to:";
}
@Override
protected void canRun() throws ConfigurationException {
final String name = getNewName();
if (name.length() == 0) {
throw new ConfigurationException(AndroidBundle.message("specify.package.name.error"));
}
if (!AndroidUtils.isValidAndroidPackageName(name)) {
throw new ConfigurationException(AndroidBundle.message("not.valid.package.name.error", name));
}
if (!AndroidCommonUtils.contains2Identifiers(name)) {
throw new ConfigurationException(AndroidBundle.message("package.name.must.contain.2.ids.error"));
}
super.canRun();
}
});
}
}