blob: f7c557d54b2da6cf6945576c494b2022b91a752e [file] [log] [blame]
package org.jetbrains.android.refactoring;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.ui.UsageViewDescriptorAdapter;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidInlineLayoutProcessor extends BaseRefactoringProcessor {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.refactoring.AndroidInlineLayoutProcessor");
private final XmlFile myLayoutFile;
private final XmlTag myLayoutRootTag;
private final PsiElement myUsageElement;
private final AndroidInlineTestConfig myTestConfig;
protected AndroidInlineLayoutProcessor(@NotNull Project project,
@NotNull XmlFile file,
@NotNull XmlTag rootTag,
@Nullable PsiElement usageElement,
@Nullable AndroidInlineTestConfig config) {
super(project);
myLayoutFile = file;
myLayoutRootTag = rootTag;
myUsageElement = usageElement;
myTestConfig = config;
}
@NotNull
@Override
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new UsageViewDescriptorAdapter() {
@NotNull
@Override
public PsiElement[] getElements() {
return new PsiElement[]{myLayoutFile};
}
@Override
public String getCodeReferencesText(int usagesCount, int filesCount) {
return "References to be inlined" + UsageViewBundle.getReferencesString(usagesCount, filesCount);
}
@Override
public String getProcessedElementsHeader() {
return "Layout file to inline";
}
};
}
@NotNull
@Override
protected UsageInfo[] findUsages() {
if (myUsageElement != null) {
return new UsageInfo[] {new UsageInfo(myUsageElement)};
}
final Set<UsageInfo> usages = new HashSet<UsageInfo>();
AndroidInlineUtil.addReferences(myLayoutFile, usages);
for (PsiField field : AndroidResourceUtil.findResourceFieldsForFileResource(myLayoutFile, false)) {
AndroidInlineUtil.addReferences(field, usages);
}
return usages.toArray(new UsageInfo[usages.size()]);
}
@Override
protected void performRefactoring(UsageInfo[] usages) {
final List<LayoutUsageData> inlineInfos = new ArrayList<LayoutUsageData>();
for (UsageInfo usage : usages) {
final PsiElement element = usage.getElement();
if (element == null) continue;
final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
final LayoutUsageData usageData = tag != null
? AndroidInlineUtil.getLayoutUsageData(tag)
: null;
if (usageData != null && usageData.getReference().computeTargetElements().length == 1) {
inlineInfos.add(usageData);
}
}
for (LayoutUsageData info : inlineInfos) {
try {
info.inline(myLayoutRootTag);
}
catch (AndroidRefactoringErrorException e) {
LOG.info(e);
String message = e.getMessage();
if (message == null) {
message = "Refactoring was performed with errors";
}
AndroidUtils.reportError(myProject, message, AndroidBundle.message("android.inline.style.title"));
return;
}
}
if (myUsageElement == null) {
try {
myLayoutFile.delete();
}
catch (IncorrectOperationException e) {
// see IDEA-90562 and http://code.google.com/p/android/issues/detail?id=36435
final Throwable c = e.getCause();
if (c instanceof IOException && c.getMessage() != null) {
AndroidUtils.reportError(myProject, c.getMessage(), AndroidBundle.message("android.inline.style.title"));
return;
}
throw e;
}
}
}
@Override
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
final UsageInfo[] usages = refUsages.get();
final MultiMap<PsiElement, String> conflicts = detectConflicts(usages);
if (ApplicationManager.getApplication().isUnitTestMode()) {
myTestConfig.setConflicts(conflicts);
return true;
}
return showConflicts(conflicts, usages);
}
private static MultiMap<PsiElement, String> detectConflicts(UsageInfo[] usages) {
final List<PsiElement> nonXmlUsages = new ArrayList<PsiElement>();
final List<PsiElement> unsupportedUsages = new ArrayList<PsiElement>();
final List<PsiElement> unambiguousUsages = new ArrayList<PsiElement>();
for (UsageInfo usage : usages) {
final PsiElement element = usage.getElement();
if (element == null) continue;
if (element.getLanguage() != XMLLanguage.INSTANCE) {
nonXmlUsages.add(element);
continue;
}
final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
final LayoutUsageData usageData = tag != null
? AndroidInlineUtil.getLayoutUsageData(tag)
: null;
if (usageData == null) {
unsupportedUsages.add(element);
continue;
}
if (usageData.getReference().computeTargetElements().length > 1) {
unambiguousUsages.add(element);
}
}
return AndroidInlineUtil.buildConflicts(nonXmlUsages, unambiguousUsages, unsupportedUsages,
Collections.<PsiElement>emptyList());
}
@Override
protected String getCommandName() {
return AndroidBundle.message("android.inline.layout.command.name", myLayoutFile.getName());
}
@Override
protected UndoConfirmationPolicy getUndoConfirmationPolicy() {
// do it because the refactoring can be invoked from UI designer
return UndoConfirmationPolicy.REQUEST_CONFIRMATION;
}
}