diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java index f9692f344727..a46ea84c3183 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java @@ -62,6 +62,7 @@ import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.maven.AndroidMavenUtil; import org.jetbrains.android.refactoring.AndroidExtractAsIncludeAction; import org.jetbrains.android.refactoring.AndroidExtractStyleAction; +import org.jetbrains.android.refactoring.AndroidInlineIncludeAction; import org.jetbrains.android.refactoring.AndroidInlineStyleReferenceAction; import org.jetbrains.android.sdk.AndroidPlatform; import org.jetbrains.android.sdk.AndroidSdkUtils; @@ -171,6 +172,9 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel { action = manager.getAction(AndroidExtractAsIncludeAction.ACTION_ID); group.add(new AndroidRefactoringActionWrapper("E_xtract Layout", action)); + + action = manager.getAction(AndroidInlineIncludeAction.ACTION_ID); + group.add(new AndroidRefactoringActionWrapper("I_nline Layout", action)); return group; } diff --git a/plugins/android/resources/messages/AndroidBundle.properties b/plugins/android/resources/messages/AndroidBundle.properties index 8b6f534025db..ec5dc1f84cd0 100644 --- a/plugins/android/resources/messages/AndroidBundle.properties +++ b/plugins/android/resources/messages/AndroidBundle.properties @@ -426,4 +426,8 @@ android.inline.style.title=Inline Android Style android.inline.style.command.name=Inline Style ''{0}'' android.inline.style.inline.all.text=Inline &all references and remove the style android.inline.style.inline.this.text=Inline &this usage and keep the style -android.extract.as.include.title=Extract Android Layout \ No newline at end of file +android.extract.as.include.title=Extract Android Layout +android.inline.layout.command.name=Inline Layout File ''{0}'' +android.inline.file.inline.all.text=Inline &all references and remove the file +android.inline.file.inline.this.text=Inline &this usage and keep the file +android.inline.layout.title=Inline Android Layout \ No newline at end of file diff --git a/plugins/android/src/META-INF/plugin.xml b/plugins/android/src/META-INF/plugin.xml index 64b3c6a4c6af..5c04e274c760 100644 --- a/plugins/android/src/META-INF/plugin.xml +++ b/plugins/android/src/META-INF/plugin.xml @@ -94,6 +94,10 @@ text="Inline _Style..." description="Inlines Android style"> + + + JUnit @@ -247,7 +251,8 @@ - + + @@ -255,4 +260,4 @@ - + \ No newline at end of file diff --git a/plugins/android/src/org/jetbrains/android/dom/converters/AndroidResourceReferenceBase.java b/plugins/android/src/org/jetbrains/android/dom/converters/AndroidResourceReferenceBase.java index 309647f84615..c433d26cc501 100644 --- a/plugins/android/src/org/jetbrains/android/dom/converters/AndroidResourceReferenceBase.java +++ b/plugins/android/src/org/jetbrains/android/dom/converters/AndroidResourceReferenceBase.java @@ -45,6 +45,11 @@ public class AndroidResourceReferenceBase extends PsiReferenceBase.Poly usages = new HashSet(); - addReferences(myStyleElement, usages); + AndroidInlineUtil.addReferences(myStyleElement, usages); for (PsiField field : AndroidResourceUtil.findResourceFieldsForValueResource(myStyleTag, false)) { - addReferences(field, usages); + AndroidInlineUtil.addReferences(field, usages); } return usages.toArray(new UsageInfo[usages.size()]); } - private static void addReferences(@NotNull PsiElement element, @NotNull Collection result) { - for (PsiReference reference : ReferencesSearch.search(element)) { - result.add(new UsageInfo(reference.getElement())); - } - } - @Override protected void performRefactoring(UsageInfo[] usages) { @@ -112,7 +98,7 @@ class AndroidInlineAllStyleUsagesProcessor extends BaseRefactoringProcessor { continue; } final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); - StyleUsageData usageData = tag != null ? AndroidInlineUtil.getUsageData(tag) : null; + StyleUsageData usageData = tag != null ? AndroidInlineUtil.getStyleUsageData(tag) : null; if (usageData == null) { if (usage.getReference() instanceof ResourceNameConverter.MyParentStyleReference) { @@ -135,7 +121,8 @@ class AndroidInlineAllStyleUsagesProcessor extends BaseRefactoringProcessor { unambiguousUsages.size() > 0 || unsupportedUsages.size() > 0 || implicitlyInherited.size() > 0) { - final String errorMessage = buildErrorMessage(myProject, nonXmlUsages, unambiguousUsages, unsupportedUsages, implicitlyInherited); + final String errorMessage = AndroidInlineUtil + .buildErrorMessage(myProject, nonXmlUsages, unambiguousUsages, unsupportedUsages, implicitlyInherited); AndroidUtils.reportError(myProject, errorMessage, AndroidBundle.message("android.inline.style.title")); return; } @@ -156,88 +143,4 @@ class AndroidInlineAllStyleUsagesProcessor extends BaseRefactoringProcessor { // do it because the refactoring can be invoked from UI designer return UndoConfirmationPolicy.REQUEST_CONFIRMATION; } - - @NotNull - private static String buildErrorMessage(Project project, - Collection nonXmlUsages, - Collection unambiguousUsages, - Collection unsupportedUsages, - Collection implicitlyInherited) { - final StringBuilder builder = new StringBuilder("Cannot perform refactoring\n\n"); - - if (nonXmlUsages.size() > 0) { - builder.append("Non-XML references are not supported:\n"); - buildString(builder, project, nonXmlUsages); - builder.append("\n\n"); - } - - if (unambiguousUsages.size() > 0) { - builder.append("Unambiguous references:\n"); - buildString(builder, project, unambiguousUsages); - builder.append("\n\n"); - } - - if (unsupportedUsages.size() > 0) { - builder.append("Unsupported references:\n"); - buildString(builder, project, unsupportedUsages); - builder.append("\n\n"); - } - - if (implicitlyInherited.size() > 0) { - builder.append("Implicit inheritance is not supported:\n"); - buildString(builder, project, implicitlyInherited); - builder.append("\n\n"); - } - builder.delete(builder.length() - 2, builder.length()); - return builder.toString(); - } - - private static void buildString(StringBuilder builder, Project project, Collection invalidRefs) { - final OrderedSet lines = new OrderedSet(); - - for (PsiElement usage : invalidRefs) { - final PsiFile psiFile = usage.getContainingFile(); - final VirtualFile file = psiFile != null - ? psiFile.getVirtualFile() - : null; - if (file != null) { - lines.add(" in '" + getPresentableFilePath(project, file) + "'"); - } - else { - lines.add(" in unknown file"); - } - } - - for (Iterator it = lines.iterator(); it.hasNext(); ) { - final String line = it.next(); - builder.append(line); - - if (it.hasNext()) { - builder.append('\n'); - } - } - } - - private static String getPresentableFilePath(Project project, VirtualFile file) { - final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project); - final int contentRootCount = projectRootManager.getContentRoots().length; - - if (contentRootCount == 0) { - return file.getPath(); - } - final VirtualFile contentRoot = projectRootManager.getFileIndex().getContentRootForFile(file); - - if (contentRoot == null) { - return file.getPath(); - } - final String relativePath = VfsUtilCore.getRelativePath(file, contentRoot, '/'); - - if (relativePath == null) { - return file.getPath(); - } - final String presentableRelativePath = contentRootCount == 1 - ? relativePath - : contentRoot.getName() + '/' + relativePath; - return FileUtil.toSystemDependentName(".../" + presentableRelativePath); - } } diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineIncludeAction.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineIncludeAction.java new file mode 100644 index 000000000000..96ecdd07588c --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineIncludeAction.java @@ -0,0 +1,81 @@ +package org.jetbrains.android.refactoring; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.util.xml.DomManager; +import org.jetbrains.android.dom.converters.AndroidResourceReferenceBase; +import org.jetbrains.android.dom.layout.LayoutDomFileDescription; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.android.util.AndroidUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidInlineIncludeAction extends AndroidBaseXmlRefactoringAction { + private final AndroidInlineTestConfig myTestConfig; + + public static final String ACTION_ID = "AndroidInlineIncludeAction"; + + @SuppressWarnings("UnusedDeclaration") + public AndroidInlineIncludeAction() { + myTestConfig = null; + } + + @TestOnly + public AndroidInlineIncludeAction(AndroidInlineTestConfig testConfig) { + myTestConfig = testConfig; + } + + @Override + protected void doRefactorForTags(@NotNull Project project, @NotNull final XmlTag[] tags) { + assert tags.length == 1; + final XmlTag tag = tags[0]; + + final PsiFile file = tag.getContainingFile(); + if (file == null) { + return; + } + final LayoutUsageData usageData = AndroidInlineUtil.getLayoutUsageData(tag); + + if (usageData == null) { + return; + } + final AndroidResourceReferenceBase reference = usageData.getReference(); + final String title = AndroidBundle.message("android.inline.layout.title"); + final PsiElement[] resolvedElements = reference.computeTargetElements(); + + if (resolvedElements.length == 0) { + AndroidUtils.reportError(project, "Cannot find layout by reference '" + reference.getValue() + "'", title); + return; + } + + if (resolvedElements.length > 1) { + AndroidUtils.reportError(project, RefactoringBundle.getCannotRefactorMessage("Unambiguous layout reference."), title); + return; + } + final PsiElement resolvedElement = resolvedElements[0]; + + if (!(resolvedElement instanceof XmlFile)) { + AndroidUtils.reportError(project, "Cannot find layout by reference '" + reference.getValue() + "'", title); + return; + } + AndroidInlineUtil.doInlineLayoutFile(project, (XmlFile)resolvedElement, usageData.getReference().getElement(), myTestConfig); + } + + @Override + protected boolean isEnabledForTags(@NotNull XmlTag[] tags) { + return tags.length == 1 && AndroidInlineUtil.getLayoutUsageData(tags[0]) != null; + } + + @Override + protected boolean isMyFile(PsiFile file) { + return DomManager.getDomManager(file.getProject()). + getDomFileDescription((XmlFile)file) instanceof LayoutDomFileDescription; + } +} diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineLayoutDialog.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineLayoutDialog.java new file mode 100644 index 000000000000..543fdc233a9e --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineLayoutDialog.java @@ -0,0 +1,63 @@ +package org.jetbrains.android.refactoring; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.refactoring.inline.InlineOptionsDialog; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidInlineLayoutDialog extends InlineOptionsDialog { + private final XmlFile myLayoutFile; + private final XmlTag myLayoutRootTag; + private final PsiElement myUsageElement; + + public AndroidInlineLayoutDialog(@NotNull Project project, + @NotNull XmlFile layoutFile, + @NotNull XmlTag layoutRootTag, + @Nullable PsiElement usageElement) { + super(project, true, layoutFile); + myLayoutFile = layoutFile; + myLayoutRootTag = layoutRootTag; + myUsageElement = usageElement; + myInvokedOnReference = usageElement != null; + setTitle(AndroidBundle.message("android.inline.layout.title")); + init(); + } + + @Override + protected String getNameLabelText() { + return "Layout file '" + myLayoutFile.getName() + "'"; + } + + @Override + protected String getBorderTitle() { + return "Inline"; + } + + @Override + protected String getInlineAllText() { + return AndroidBundle.message("android.inline.file.inline.all.text"); + } + + @Override + protected String getInlineThisText() { + return AndroidBundle.message("android.inline.file.inline.this.text"); + } + + @Override + protected boolean isInlineThis() { + return myUsageElement != null; + } + + @Override + protected void doAction() { + final PsiElement usageElement = isInlineThisOnly() ? myUsageElement : null; + invokeRefactoring(new AndroidInlineLayoutProcessor(myProject, myLayoutFile, myLayoutRootTag, usageElement)); + } +} diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineLayoutHandler.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineLayoutHandler.java new file mode 100644 index 000000000000..ba13e20f52cf --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineLayoutHandler.java @@ -0,0 +1,122 @@ +package org.jetbrains.android.refactoring; + +import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.lang.Language; +import com.intellij.lang.refactoring.InlineActionHandler; +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.*; +import com.intellij.util.xml.DomManager; +import org.jetbrains.android.dom.converters.AndroidResourceReferenceBase; +import org.jetbrains.android.dom.layout.LayoutDomFileDescription; +import org.jetbrains.android.dom.wrappers.ResourceElementWrapper; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.android.util.ErrorReporter; +import org.jetbrains.android.util.HintBasedErrorReporter; +import org.jetbrains.android.util.ProjectBasedErrorReporter; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidInlineLayoutHandler extends InlineActionHandler { + private static AndroidInlineTestConfig ourTestConfig; + + @TestOnly + public static void setTestConfig(@Nullable AndroidInlineTestConfig testConfig) { + ourTestConfig = testConfig; + } + + @Override + public boolean isEnabledForLanguage(Language l) { + return l == XMLLanguage.INSTANCE; + } + + @Override + public boolean canInlineElement(PsiElement element) { + if (element instanceof ResourceElementWrapper) { + element = ((ResourceElementWrapper)element).getWrappee(); + } + if (!(element instanceof XmlElement)) { + return false; + } + if (AndroidFacet.getInstance(element) == null) { + return false; + } + if (element instanceof XmlFile) { + if (((XmlFile)element).getRootTag() == null) { + return false; + } + return DomManager.getDomManager(element.getProject()).getDomFileDescription((XmlFile)element) + instanceof LayoutDomFileDescription; + } + else if (element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_NAME) { + return getLayoutUsageDataFromContext(element) != null; + } + return false; + } + + @Nullable + private static LayoutUsageData getLayoutUsageDataFromContext(PsiElement context) { + final XmlTag tag = PsiTreeUtil.getParentOfType(context, XmlTag.class); + return tag != null + ? AndroidInlineUtil.getLayoutUsageData(tag) + : null; + } + + @Override + public void inlineElement(Project project, Editor editor, PsiElement element) { + if (element instanceof ResourceElementWrapper) { + element = ((ResourceElementWrapper)element).getWrappee(); + } + + if (element instanceof XmlFile) { + PsiElement usageElement = null; + + if (editor != null) { + final PsiReference reference = TargetElementUtilBase.findReference(editor); + + if (reference != null) { + usageElement = reference.getElement(); + } + } + AndroidInlineUtil.doInlineLayoutFile(project, (XmlFile)element, usageElement, ourTestConfig); + return; + } + final LayoutUsageData usageData = getLayoutUsageDataFromContext(element); + assert usageData != null; + final AndroidResourceReferenceBase ref = usageData.getReference(); + final PsiElement[] elements = ref.computeTargetElements(); + final ErrorReporter errorReporter = editor != null + ? new HintBasedErrorReporter(editor) + : new ProjectBasedErrorReporter(project); + final String title = AndroidBundle.message("android.inline.layout.title"); + + if (elements.length == 0) { + final String resName = ref.getResourceValue().getResourceName(); + final String message = resName != null + ? "Cannot find layout '" + resName + "'" + : "Error: cannot find the layout"; + errorReporter.report(message, title); + return; + } + + if (elements.length > 1) { + errorReporter.report("Error: unambiguous reference", title); + return; + } + + final PsiElement resolvedElement = elements[0]; + if (!(resolvedElement instanceof XmlFile)) { + errorReporter.report("Cannot inline reference '" + ref.getValue() + "'", title); + return; + } + AndroidInlineUtil.doInlineLayoutFile(project, (XmlFile)resolvedElement, usageData.getIncludeTag(), ourTestConfig); + } +} diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineLayoutProcessor.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineLayoutProcessor.java new file mode 100644 index 000000000000..582fc9b6ca45 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineLayoutProcessor.java @@ -0,0 +1,155 @@ +package org.jetbrains.android.refactoring; + +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.openapi.command.UndoConfirmationPolicy; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +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.containers.HashSet; +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.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; + + protected AndroidInlineLayoutProcessor(@NotNull Project project, + @NotNull XmlFile file, + @NotNull XmlTag rootTag, + @Nullable PsiElement usageElement) { + super(project); + myLayoutFile = file; + myLayoutRootTag = rootTag; + myUsageElement = usageElement; + } + + @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 usages = new HashSet(); + 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 inlineInfos = new ArrayList(); + final List nonXmlUsages = new ArrayList(); + final List unsupportedUsages = new ArrayList(); + final List unambiguousUsages = new ArrayList(); + + 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); + continue; + } + inlineInfos.add(usageData); + } + + if (nonXmlUsages.size() > 0 || unambiguousUsages.size() > 0 || unsupportedUsages.size() > 0) { + final String errorMessage = AndroidInlineUtil.buildErrorMessage( + myProject, nonXmlUsages, unambiguousUsages, unsupportedUsages, + Collections.emptyList()); + AndroidUtils.reportError(myProject, errorMessage, AndroidBundle.message("android.inline.style.title")); + return; + } + + 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) { + myLayoutFile.delete(); + } + } + + @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; + } +} diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleHandler.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleHandler.java index 198d9afa087a..866e65c6c37b 100644 --- a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleHandler.java +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleHandler.java @@ -76,6 +76,6 @@ public class AndroidInlineStyleHandler extends InlineActionHandler { return null; } final XmlTag tag = PsiTreeUtil.getParentOfType(usageElement, XmlTag.class, false); - return tag != null ? AndroidInlineUtil.getUsageData(tag) : null; + return tag != null ? AndroidInlineUtil.getStyleUsageData(tag) : null; } } diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleReferenceAction.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleReferenceAction.java index 5a69d1965029..363e8322ffc7 100644 --- a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleReferenceAction.java +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleReferenceAction.java @@ -47,7 +47,7 @@ public class AndroidInlineStyleReferenceAction extends AndroidBaseXmlRefactoring if (file == null) { return; } - final StyleUsageData usageData = AndroidInlineUtil.getUsageData(tag); + final StyleUsageData usageData = AndroidInlineUtil.getStyleUsageData(tag); if (usageData == null) { return; @@ -87,7 +87,7 @@ public class AndroidInlineStyleReferenceAction extends AndroidBaseXmlRefactoring @Override protected boolean isEnabledForTags(@NotNull XmlTag[] tags) { - return tags.length == 1 && AndroidInlineUtil.getUsageData(tags[0]) != null; + return tags.length == 1 && AndroidInlineUtil.getStyleUsageData(tags[0]) != null; } @Override diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineUtil.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineUtil.java index 3c94d0cb27e2..9d43e8588a2c 100644 --- a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineUtil.java +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineUtil.java @@ -5,18 +5,27 @@ import com.intellij.openapi.application.Result; import com.intellij.openapi.command.UndoConfirmationPolicy; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; +import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlAttributeValue; +import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.refactoring.RefactoringBundle; +import com.intellij.usageView.UsageInfo; import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.OrderedSet; import com.intellij.util.xml.DomElement; import com.intellij.util.xml.DomManager; import com.intellij.util.xml.GenericAttributeValue; import org.jetbrains.android.dom.converters.AndroidResourceReferenceBase; +import org.jetbrains.android.dom.layout.Include; import org.jetbrains.android.dom.layout.LayoutViewElement; import org.jetbrains.android.dom.resources.ResourceValue; import org.jetbrains.android.dom.resources.Style; @@ -28,6 +37,8 @@ import org.jetbrains.android.util.ErrorReporter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.Iterator; import java.util.Map; /** @@ -59,7 +70,7 @@ class AndroidInlineUtil { } @Nullable - static StyleUsageData getUsageData(@NotNull XmlTag tag) { + static StyleUsageData getStyleUsageData(@NotNull XmlTag tag) { final DomElement domElement = DomManager.getDomManager(tag.getProject()).getDomElement(tag); if (domElement instanceof LayoutViewElement) { @@ -80,23 +91,39 @@ class AndroidInlineUtil { return null; } + @Nullable + static LayoutUsageData getLayoutUsageData(@NotNull XmlTag tag) { + final Project project = tag.getProject(); + final DomElement domElement = DomManager.getDomManager(project).getDomElement(tag); + + if (domElement instanceof Include) { + final GenericAttributeValue layoutAttribute = ((Include)domElement).getLayout(); + final AndroidResourceReferenceBase reference = getAndroidResourceReference(layoutAttribute); + + if (reference != null) { + return new LayoutUsageData(project, tag, reference); + } + } + return null; + } + @Nullable private static AndroidResourceReferenceBase getAndroidResourceReference(@Nullable GenericAttributeValue attribute) { if (attribute == null) { return null; } - final ResourceValue styleValue = attribute.getValue(); - if (styleValue == null || styleValue.getPackage() != null) { + final ResourceValue resValue = attribute.getValue(); + if (resValue == null || resValue.getPackage() != null) { return null; } - final XmlAttributeValue styleAttributeValue = attribute.getXmlAttributeValue(); - if (styleAttributeValue == null) { + final XmlAttributeValue value = attribute.getXmlAttributeValue(); + if (value == null) { return null; } - for (PsiReference reference : styleAttributeValue.getReferences()) { + for (PsiReference reference : value.getReferences()) { if (reference instanceof AndroidResourceReferenceBase) { return (AndroidResourceReferenceBase)reference; } @@ -237,6 +264,115 @@ class AndroidInlineUtil { ? data : null; } + static void addReferences(@NotNull PsiElement element, @NotNull Collection result) { + for (PsiReference reference : ReferencesSearch.search(element)) { + result.add(new UsageInfo(reference.getElement())); + } + } + + @NotNull + static String buildErrorMessage(Project project, + Collection nonXmlUsages, + Collection unambiguousUsages, + Collection unsupportedUsages, + Collection implicitlyInherited) { + final StringBuilder builder = new StringBuilder("Cannot perform refactoring\n\n"); + + if (nonXmlUsages.size() > 0) { + builder.append("Non-XML references are not supported:\n"); + buildString(builder, project, nonXmlUsages); + builder.append("\n\n"); + } + + if (unambiguousUsages.size() > 0) { + builder.append("Unambiguous references:\n"); + buildString(builder, project, unambiguousUsages); + builder.append("\n\n"); + } + + if (unsupportedUsages.size() > 0) { + builder.append("Unsupported references:\n"); + buildString(builder, project, unsupportedUsages); + builder.append("\n\n"); + } + + if (implicitlyInherited.size() > 0) { + builder.append("Implicit inheritance is not supported:\n"); + buildString(builder, project, implicitlyInherited); + builder.append("\n\n"); + } + builder.delete(builder.length() - 2, builder.length()); + return builder.toString(); + } + + private static void buildString(StringBuilder builder, Project project, Collection invalidRefs) { + final OrderedSet lines = new OrderedSet(); + + for (PsiElement usage : invalidRefs) { + final PsiFile psiFile = usage.getContainingFile(); + final VirtualFile file = psiFile != null + ? psiFile.getVirtualFile() + : null; + if (file != null) { + lines.add(" in '" + getPresentableFilePath(project, file) + "'"); + } + else { + lines.add(" in unknown file"); + } + } + + for (Iterator it = lines.iterator(); it.hasNext(); ) { + final String line = it.next(); + builder.append(line); + + if (it.hasNext()) { + builder.append('\n'); + } + } + } + + private static String getPresentableFilePath(Project project, VirtualFile file) { + final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project); + final int contentRootCount = projectRootManager.getContentRoots().length; + + if (contentRootCount == 0) { + return file.getPath(); + } + final VirtualFile contentRoot = projectRootManager.getFileIndex().getContentRootForFile(file); + + if (contentRoot == null) { + return file.getPath(); + } + final String relativePath = VfsUtilCore.getRelativePath(file, contentRoot, '/'); + + if (relativePath == null) { + return file.getPath(); + } + final String presentableRelativePath = contentRootCount == 1 + ? relativePath + : contentRoot.getName() + '/' + relativePath; + return FileUtil.toSystemDependentName(".../" + presentableRelativePath); + } + + static void doInlineLayoutFile(@NotNull Project project, + @NotNull XmlFile layoutFile, + @Nullable PsiElement usageElement, + @Nullable AndroidInlineTestConfig testConfig) { + final XmlTag rootTag = layoutFile.getRootTag(); + assert rootTag != null; + + if (testConfig == null) { + final AndroidInlineLayoutDialog dialog = new AndroidInlineLayoutDialog(project, layoutFile, rootTag, usageElement); + dialog.show(); + } + else { + final AndroidInlineLayoutProcessor processor = + new AndroidInlineLayoutProcessor(project, layoutFile, rootTag, testConfig.isInlineThisOnly() ? usageElement : null); + processor.setPreviewUsages(false); + processor.run(); + } + } + static class MyStyleData { private final String myStyleName; private final Style myStyleElement; diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidRefactoringErrorException.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidRefactoringErrorException.java new file mode 100644 index 000000000000..1cb5f4cb9e99 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidRefactoringErrorException.java @@ -0,0 +1,13 @@ +package org.jetbrains.android.refactoring; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidRefactoringErrorException extends Exception { + public AndroidRefactoringErrorException() { + } + + public AndroidRefactoringErrorException(String message) { + super(message); + } +} diff --git a/plugins/android/src/org/jetbrains/android/refactoring/LayoutUsageData.java b/plugins/android/src/org/jetbrains/android/refactoring/LayoutUsageData.java new file mode 100644 index 000000000000..520fd62a1d24 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/refactoring/LayoutUsageData.java @@ -0,0 +1,200 @@ +package org.jetbrains.android.refactoring; + +import com.android.sdklib.SdkConstants; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.XmlElementFactory; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlChildRole; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.containers.HashMap; +import org.jetbrains.android.dom.converters.AndroidResourceReferenceBase; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author Eugene.Kudelevsky + */ +public class LayoutUsageData { + private final Project myProject; + private final XmlTag myIncludeTag; + private final AndroidResourceReferenceBase myReference; + + public LayoutUsageData(@NotNull Project project, + @NotNull XmlTag includeTag, + @NotNull AndroidResourceReferenceBase reference) { + myProject = project; + myIncludeTag = includeTag; + myReference = reference; + } + + @NotNull + public AndroidResourceReferenceBase getReference() { + return myReference; + } + + public void inline(@NotNull XmlTag layoutRootTag) throws AndroidRefactoringErrorException { + final XmlTag parent = myIncludeTag.getParentTag(); + + if ("merge".equals(layoutRootTag.getName()) && parent != null) { + inlineMultiTags(myIncludeTag, parent, layoutRootTag, myProject); + } + else { + inlineSingleTag(myIncludeTag, parent, layoutRootTag); + } + } + + @NotNull + public XmlTag getIncludeTag() { + return myIncludeTag; + } + + private static void inlineSingleTag(XmlTag includeTag, XmlTag includeParentTag, XmlTag layoutRootTag) { + final Map attributesToAdd = new HashMap(); + + for (XmlAttribute attribute : includeTag.getAttributes()) { + final String namespace = attribute.getNamespace(); + + if (SdkConstants.NS_RESOURCES.equals(namespace)) { + attributesToAdd.put(attribute.getLocalName(), attribute.getValue()); + } + } + final XmlTag newTag = (XmlTag)includeTag.replace(layoutRootTag.copy()); + final List toDelete = new ArrayList(); + + for (XmlAttribute attribute : newTag.getAttributes()) { + if (attribute.isNamespaceDeclaration()) { + final String localName = attribute.getLocalName(); + final String prefix = localName.equals(attribute.getName()) ? "" : localName; + final String namespace = attribute.getValue(); + + if (includeParentTag != null && namespace.equals(includeParentTag.getNamespaceByPrefix(prefix))) { + toDelete.add(attribute); + } + } + } + + for (XmlAttribute attribute : toDelete) { + attribute.delete(); + } + + for (Map.Entry entry : attributesToAdd.entrySet()) { + final String localName = entry.getKey(); + final String value = entry.getValue(); + newTag.setAttribute(localName, SdkConstants.NS_RESOURCES, value); + } + CodeStyleManager.getInstance(newTag.getManager()).reformat(newTag); + } + + private static void inlineMultiTags(XmlTag includeTag, XmlTag includeTagParent, XmlTag mergeTag, Project project) + throws AndroidRefactoringErrorException { + final Map namespacesFromParent = includeTagParent.getLocalNamespaceDeclarations(); + final Map namespacesToAddToParent = new HashMap(); + final Map namespacesToAddToEachTag = new HashMap(); + + for (Map.Entry entry : mergeTag.getLocalNamespaceDeclarations().entrySet()) { + final String prefix = entry.getKey(); + final String namespace = entry.getValue(); + final String declaredNamespace = namespacesFromParent.get(prefix); + + if (declaredNamespace != null && !declaredNamespace.equals(namespace)) { + namespacesToAddToEachTag.put(prefix, namespace); + } + else { + namespacesToAddToParent.put(prefix, namespace); + } + } + final XmlTag mergeTagCopy = (XmlTag)mergeTag.copy(); + final XmlElementFactory xmlElementFactory = XmlElementFactory.getInstance(project); + + for (XmlTag subtag : mergeTagCopy.getSubTags()) { + final XmlAttribute[] attributes = subtag.getAttributes(); + final XmlAttribute firstAttribute = attributes.length > 0 ? attributes[0] : null; + + for (Map.Entry entry : namespacesToAddToEachTag.entrySet()) { + final String prefix = entry.getKey(); + final String namespace = entry.getValue(); + + if (!subtag.getLocalNamespaceDeclarations().containsKey(prefix)) { + final XmlAttribute xmlnsAttr = xmlElementFactory.createXmlAttribute("xmlns:" + prefix, namespace); + + if (firstAttribute != null) { + subtag.addBefore(xmlnsAttr, firstAttribute); + } + else { + subtag.add(xmlnsAttr); + } + } + } + } + replaceByTagContent(project, includeTag, mergeTagCopy); + addNamespaceAttributes(includeTagParent, namespacesToAddToParent, project); + } + + private static void addNamespaceAttributes(XmlTag tag, Map namespaces, Project project) { + final XmlAttribute[] parentAttributes = tag.getAttributes(); + final XmlAttribute firstParentAttribute = parentAttributes.length > 0 ? parentAttributes[0] : null; + final XmlElementFactory factory = XmlElementFactory.getInstance(project); + + for (Map.Entry entry : namespaces.entrySet()) { + final String prefix = entry.getKey(); + final String namespace = entry.getValue(); + + if (!namespace.equals(tag.getNamespaceByPrefix(prefix))) { + final XmlAttribute xmlnsAttr = factory.createXmlAttribute("xmlns:" + prefix, namespace); + + if (firstParentAttribute != null) { + tag.addBefore(xmlnsAttr, firstParentAttribute); + } + else { + tag.add(xmlnsAttr); + } + } + } + } + + private static void replaceByTagContent(Project project, XmlTag tagToReplace, XmlTag tagToInline) + throws AndroidRefactoringErrorException { + final ASTNode node = tagToInline.getNode(); + + if (node == null) { + throw new AndroidRefactoringErrorException(); + } + final ASTNode startTagEnd = XmlChildRole.START_TAG_END_FINDER.findChild(node); + final ASTNode closingTagStart = XmlChildRole.CLOSING_TAG_START_FINDER.findChild(node); + + if (startTagEnd == null || closingTagStart == null) { + throw new AndroidRefactoringErrorException(); + } + final int contentStart = startTagEnd.getTextRange().getEndOffset(); + final int contentEnd = closingTagStart.getTextRange().getStartOffset(); + + if (contentStart < 0 || contentEnd < 0 || contentStart >= contentEnd) { + throw new AndroidRefactoringErrorException(); + } + final PsiFile file = tagToInline.getContainingFile(); + + if(file == null) { + throw new AndroidRefactoringErrorException(); + } + final String textToInline = file.getText(). + substring(contentStart, contentEnd).trim(); + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + final Document document = documentManager.getDocument(tagToReplace.getContainingFile()); + + if (document == null) { + throw new AndroidRefactoringErrorException(); + } + final TextRange range = tagToReplace.getTextRange(); + document.replaceString(range.getStartOffset(), range.getEndOffset(), textToInline); + documentManager.commitDocument(document); + } +} diff --git a/plugins/android/testData/refactoring/inlineLayout/1.xml b/plugins/android/testData/refactoring/inlineLayout/1.xml new file mode 100644 index 000000000000..432fb78b96cc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/1.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/10.xml b/plugins/android/testData/refactoring/inlineLayout/10.xml new file mode 100644 index 000000000000..432fb78b96cc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/10.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/10_after.xml b/plugins/android/testData/refactoring/inlineLayout/10_after.xml new file mode 100644 index 000000000000..3c39c3a7b1e9 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/10_after.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/10_included.xml b/plugins/android/testData/refactoring/inlineLayout/10_included.xml new file mode 100644 index 000000000000..a0b0b89a54dc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/10_included.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/11.xml b/plugins/android/testData/refactoring/inlineLayout/11.xml new file mode 100644 index 000000000000..27d2a7cf8075 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/11.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/11_after.xml b/plugins/android/testData/refactoring/inlineLayout/11_after.xml new file mode 100644 index 000000000000..533bfb3edb22 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/11_after.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/11_included.xml b/plugins/android/testData/refactoring/inlineLayout/11_included.xml new file mode 100644 index 000000000000..a0b0b89a54dc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/11_included.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/12.xml b/plugins/android/testData/refactoring/inlineLayout/12.xml new file mode 100644 index 000000000000..460a641a9dab --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/12.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/12_after.xml b/plugins/android/testData/refactoring/inlineLayout/12_after.xml new file mode 100644 index 000000000000..717a237560a2 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/12_after.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/12_included.xml b/plugins/android/testData/refactoring/inlineLayout/12_included.xml new file mode 100644 index 000000000000..a0b0b89a54dc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/12_included.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/13.xml b/plugins/android/testData/refactoring/inlineLayout/13.xml new file mode 100644 index 000000000000..f66fb8a50468 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/13.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/13_after.xml b/plugins/android/testData/refactoring/inlineLayout/13_after.xml new file mode 100644 index 000000000000..cfef7670cc56 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/13_after.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/13_included.xml b/plugins/android/testData/refactoring/inlineLayout/13_included.xml new file mode 100644 index 000000000000..a0b0b89a54dc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/13_included.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/14.xml b/plugins/android/testData/refactoring/inlineLayout/14.xml new file mode 100644 index 000000000000..00521b8785fa --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/14.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/14_after.xml b/plugins/android/testData/refactoring/inlineLayout/14_after.xml new file mode 100644 index 000000000000..0b8ffbeec7ed --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/14_after.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/14_included.xml b/plugins/android/testData/refactoring/inlineLayout/14_included.xml new file mode 100644 index 000000000000..a0b0b89a54dc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/14_included.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/15.xml b/plugins/android/testData/refactoring/inlineLayout/15.xml new file mode 100644 index 000000000000..130ff600280a --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/15.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/15_included.xml b/plugins/android/testData/refactoring/inlineLayout/15_included.xml new file mode 100644 index 000000000000..64bc7d040fa4 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/15_included.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/16.xml b/plugins/android/testData/refactoring/inlineLayout/16.xml new file mode 100644 index 000000000000..130ff600280a --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/16.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/16_included.xml b/plugins/android/testData/refactoring/inlineLayout/16_included.xml new file mode 100644 index 000000000000..64bc7d040fa4 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/16_included.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/17.xml b/plugins/android/testData/refactoring/inlineLayout/17.xml new file mode 100644 index 000000000000..73cc1905c349 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/17.xml @@ -0,0 +1,7 @@ + + + nclude layout="@layout/included"/> + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/17_after.xml b/plugins/android/testData/refactoring/inlineLayout/17_after.xml new file mode 100644 index 000000000000..4f1e2160c5fd --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/17_after.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/17_included.xml b/plugins/android/testData/refactoring/inlineLayout/17_included.xml new file mode 100644 index 000000000000..fba76aca4891 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/17_included.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/18.xml b/plugins/android/testData/refactoring/inlineLayout/18.xml new file mode 100644 index 000000000000..b1b42eb09834 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/18.xml @@ -0,0 +1,8 @@ + + + nclude layout="@layout/included"/> + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/18_after.xml b/plugins/android/testData/refactoring/inlineLayout/18_after.xml new file mode 100644 index 000000000000..b8e654710d15 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/18_after.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/18_included.xml b/plugins/android/testData/refactoring/inlineLayout/18_included.xml new file mode 100644 index 000000000000..fba76aca4891 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/18_included.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/19.xml b/plugins/android/testData/refactoring/inlineLayout/19.xml new file mode 100644 index 000000000000..82ad3f2a1c27 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/19.xml @@ -0,0 +1,8 @@ + + + + xtView/> + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/19_included.xml b/plugins/android/testData/refactoring/inlineLayout/19_included.xml new file mode 100644 index 000000000000..fba76aca4891 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/19_included.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/1_after.xml b/plugins/android/testData/refactoring/inlineLayout/1_after.xml new file mode 100644 index 000000000000..d97195a5cf8e --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/1_after.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/1_included.xml b/plugins/android/testData/refactoring/inlineLayout/1_included.xml new file mode 100644 index 000000000000..fba76aca4891 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/1_included.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/2.xml b/plugins/android/testData/refactoring/inlineLayout/2.xml new file mode 100644 index 000000000000..432fb78b96cc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/2.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/20.xml b/plugins/android/testData/refactoring/inlineLayout/20.xml new file mode 100644 index 000000000000..cee084174407 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/20.xml @@ -0,0 +1,7 @@ + + + lude layout="@layout/unknown"/> + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/20_included.xml b/plugins/android/testData/refactoring/inlineLayout/20_included.xml new file mode 100644 index 000000000000..fba76aca4891 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/20_included.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/2_after.xml b/plugins/android/testData/refactoring/inlineLayout/2_after.xml new file mode 100644 index 000000000000..017fdc571efb --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/2_after.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/2_included.xml b/plugins/android/testData/refactoring/inlineLayout/2_included.xml new file mode 100644 index 000000000000..8199c040c69c --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/2_included.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/3.xml b/plugins/android/testData/refactoring/inlineLayout/3.xml new file mode 100644 index 000000000000..432fb78b96cc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/3.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/3_after.xml b/plugins/android/testData/refactoring/inlineLayout/3_after.xml new file mode 100644 index 000000000000..4f13ce2113ba --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/3_after.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/3_included.xml b/plugins/android/testData/refactoring/inlineLayout/3_included.xml new file mode 100644 index 000000000000..1bc01f0be6ab --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/3_included.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/4.xml b/plugins/android/testData/refactoring/inlineLayout/4.xml new file mode 100644 index 000000000000..432fb78b96cc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/4.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/4_after.xml b/plugins/android/testData/refactoring/inlineLayout/4_after.xml new file mode 100644 index 000000000000..1fdd46f71e4a --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/4_after.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/4_included.xml b/plugins/android/testData/refactoring/inlineLayout/4_included.xml new file mode 100644 index 000000000000..a9c7f1f55d2f --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/4_included.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/5.xml b/plugins/android/testData/refactoring/inlineLayout/5.xml new file mode 100644 index 000000000000..432fb78b96cc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/5.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/5_after.xml b/plugins/android/testData/refactoring/inlineLayout/5_after.xml new file mode 100644 index 000000000000..d57a17976619 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/5_after.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/5_included.xml b/plugins/android/testData/refactoring/inlineLayout/5_included.xml new file mode 100644 index 000000000000..d1aa7763e450 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/5_included.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/6.xml b/plugins/android/testData/refactoring/inlineLayout/6.xml new file mode 100644 index 000000000000..d772acd34a5b --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/6.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/6_after.xml b/plugins/android/testData/refactoring/inlineLayout/6_after.xml new file mode 100644 index 000000000000..f2fa163390f3 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/6_after.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/6_included.xml b/plugins/android/testData/refactoring/inlineLayout/6_included.xml new file mode 100644 index 000000000000..a9c7f1f55d2f --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/6_included.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/7.xml b/plugins/android/testData/refactoring/inlineLayout/7.xml new file mode 100644 index 000000000000..b38cca09d5a9 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/7.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/7_after.xml b/plugins/android/testData/refactoring/inlineLayout/7_after.xml new file mode 100644 index 000000000000..d329c61ee793 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/7_after.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/7_included.xml b/plugins/android/testData/refactoring/inlineLayout/7_included.xml new file mode 100644 index 000000000000..d1aa7763e450 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/7_included.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/8.xml b/plugins/android/testData/refactoring/inlineLayout/8.xml new file mode 100644 index 000000000000..432fb78b96cc --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/8.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/8_after.xml b/plugins/android/testData/refactoring/inlineLayout/8_after.xml new file mode 100644 index 000000000000..4585f28858b7 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/8_after.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/8_included.xml b/plugins/android/testData/refactoring/inlineLayout/8_included.xml new file mode 100644 index 000000000000..fba76aca4891 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/8_included.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/9.xml b/plugins/android/testData/refactoring/inlineLayout/9.xml new file mode 100644 index 000000000000..a6ddd4eb44d2 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/9.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/9_after.xml b/plugins/android/testData/refactoring/inlineLayout/9_after.xml new file mode 100644 index 000000000000..4585f28858b7 --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/9_after.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/9_included.xml b/plugins/android/testData/refactoring/inlineLayout/9_included.xml new file mode 100644 index 000000000000..c3233a50d44f --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/9_included.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/inlineLayout/MyActivity.java b/plugins/android/testData/refactoring/inlineLayout/MyActivity.java new file mode 100644 index 000000000000..5c53b74d2cdf --- /dev/null +++ b/plugins/android/testData/refactoring/inlineLayout/MyActivity.java @@ -0,0 +1,12 @@ +package p1.p2; + +import android.app.Activity; +import android.view.View; + +public class MyActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + int n = R.layout.included; + } +} diff --git a/plugins/android/testSrc/org/jetbrains/android/refactoring/AndroidInlineLayoutTest.java b/plugins/android/testSrc/org/jetbrains/android/refactoring/AndroidInlineLayoutTest.java new file mode 100644 index 000000000000..0ee42919cb2f --- /dev/null +++ b/plugins/android/testSrc/org/jetbrains/android/refactoring/AndroidInlineLayoutTest.java @@ -0,0 +1,192 @@ +package org.jetbrains.android.refactoring; + +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.refactoring.actions.InlineAction; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.android.AndroidTestCase; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidInlineLayoutTest extends AndroidTestCase { + private static final String BASE_PATH = "refactoring/inlineLayout/"; + + public void test1() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test2() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test3() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test4() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test5() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test6() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test7() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test8() throws Exception { + doTestCommonInlineAll(); + } + + public void test9() throws Exception { + final String testName = getTestName(true); + myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test.xml"); + final VirtualFile f = myFixture.copyFileToProject(BASE_PATH + testName + "_included.xml", "res/layout/included.xml"); + myFixture.configureFromExistingVirtualFile(f); + AndroidInlineLayoutHandler.setTestConfig(new AndroidInlineTestConfig(true)); + try { + final Presentation p = myFixture.testAction(new InlineAction()); + assertTrue(p.isEnabled()); + assertTrue(p.isVisible()); + } + finally { + AndroidInlineLayoutHandler.setTestConfig(null); + } + myFixture.checkResultByFile("res/layout/test.xml", BASE_PATH + testName + "_after.xml", true); + assertNull(myFixture.getTempDirFixture().getFile("res/layout/included.xml")); + } + + public void test10() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test11() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test12() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test13() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test14() throws Exception { + doTestCommonInlineThisOnly(); + } + + public void test15() throws Exception { + myFixture.copyFileToProject(BASE_PATH + getTestName(true) + "_included.xml", "res/layout-land/included.xml"); + doTestCommonInlineActionError(false, true); + } + + public void test16() throws Exception { + myFixture.copyFileToProject("R.java", "gen/p1/p2/R.java"); + myFixture.copyFileToProject(BASE_PATH + "MyActivity.java", "src/p1/p2/MyActivity.java"); + doTestCommonInlineActionError(false, true); + } + + public void test17() throws Exception { + doTestInlineIncludeAction(true); + } + + public void test18() throws Exception { + doTestInlineIncludeAction(false); + } + + public void test19() throws Exception { + doTestInlineIncludeActionDisabled(); + } + + public void test20() throws Exception { + doTestInlineIncludeActionError(true); + } + + private void doTestCommonInlineThisOnly() throws Exception { + doTestCommonInlineAction(true); + myFixture.checkResultByFile("res/layout/included.xml", + BASE_PATH + getTestName(true) + "_included.xml", true); + } + + private void doTestCommonInlineAll() throws Exception { + doTestCommonInlineAction(false); + assertNull(myFixture.getTempDirFixture().getFile("res/layout/included.xml")); + } + + private void doTestCommonInlineActionError(boolean inlineThisOnly, boolean configureFromIncludedFile) { + final String testName = getTestName(true); + final VirtualFile included = myFixture.copyFileToProject(BASE_PATH + testName + "_included.xml", "res/layout/included.xml"); + final VirtualFile f = myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test.xml"); + myFixture.configureFromExistingVirtualFile(configureFromIncludedFile ? included : f); + AndroidInlineLayoutHandler.setTestConfig(new AndroidInlineTestConfig(inlineThisOnly)); + try { + myFixture.testAction(new InlineAction()); + fail(); + } + catch (IncorrectOperationException e) { + assertTrue(e.getMessage().length() > 0); + } + finally { + AndroidInlineLayoutHandler.setTestConfig(null); + } + } + + private void doTestCommonInlineAction(boolean inlineThisOnly) { + final String testName = getTestName(true); + myFixture.copyFileToProject(BASE_PATH + testName + "_included.xml", "res/layout/included.xml"); + final VirtualFile f = myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test.xml"); + myFixture.configureFromExistingVirtualFile(f); + AndroidInlineLayoutHandler.setTestConfig(new AndroidInlineTestConfig(inlineThisOnly)); + try { + final Presentation p = myFixture.testAction(new InlineAction()); + assertTrue(p.isEnabled()); + assertTrue(p.isVisible()); + } + finally { + AndroidInlineLayoutHandler.setTestConfig(null); + } + myFixture.checkResultByFile(BASE_PATH + testName + "_after.xml", true); + } + + private void doTestInlineIncludeAction(boolean inlineThisOnly) { + final String testName = getTestName(true); + myFixture.copyFileToProject(BASE_PATH + testName + "_included.xml", "res/layout/included.xml"); + final VirtualFile f = myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test.xml"); + myFixture.configureFromExistingVirtualFile(f); + final Presentation p = myFixture.testAction(new AndroidInlineIncludeAction(new AndroidInlineTestConfig(inlineThisOnly))); + assertTrue(p.isEnabled()); + assertTrue(p.isVisible()); + myFixture.checkResultByFile(BASE_PATH + testName + "_after.xml", true); + } + + private void doTestInlineIncludeActionDisabled() { + final String testName = getTestName(true); + myFixture.copyFileToProject(BASE_PATH + testName + "_included.xml", "res/layout/included.xml"); + final VirtualFile f = myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test.xml"); + myFixture.configureFromExistingVirtualFile(f); + final Presentation p = myFixture.testAction(new AndroidInlineIncludeAction(new AndroidInlineTestConfig(true))); + assertTrue(p.isVisible()); + assertFalse(p.isEnabled()); + } + + private void doTestInlineIncludeActionError(boolean inlineThisOnly) { + final String testName = getTestName(true); + myFixture.copyFileToProject(BASE_PATH + testName + "_included.xml", "res/layout/included.xml"); + final VirtualFile f = myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test.xml"); + myFixture.configureFromExistingVirtualFile(f); + try { + myFixture.testAction(new AndroidInlineIncludeAction(new AndroidInlineTestConfig(inlineThisOnly))); + fail(); + } + catch (IncorrectOperationException e) { + assertTrue(e.getMessage().length() > 0); + } + } +}