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