IDEA-80866 inline Android layout refactoring

This commit is contained in:
Eugene Kudelevsky
2012-08-21 16:25:16 +04:00
parent 635edcac8a
commit b70bde1b5c
72 changed files with 1508 additions and 114 deletions
@@ -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;
}
@@ -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
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
+7 -2
View File
@@ -94,6 +94,10 @@
text="Inline _Style..." description="Inlines Android style">
<add-to-group group-id="RefactoringMenu"/>
</action>
<action id="AndroidInlineIncludeAction" class="org.jetbrains.android.refactoring.AndroidInlineIncludeAction"
text="Inline _Layout..." description="Inlines included Android layout">
<add-to-group group-id="RefactoringMenu"/>
</action>
</actions>
<depends>JUnit</depends>
@@ -247,7 +251,8 @@
<compileServer.plugin classpath="jps/android-jps-plugin.jar;android-common.jar;android-rt.jar"/>
<compileServer.plugin classpath="sdklib.jar;common.jar;jarutils.jar;androidprefs.jar"/>
<inlineActionHandler implementation="org.jetbrains.android.refactoring.AndroidInlineStyleHandler" order="last"/>
<inlineActionHandler implementation="org.jetbrains.android.refactoring.AndroidInlineStyleHandler"/>
<inlineActionHandler implementation="org.jetbrains.android.refactoring.AndroidInlineLayoutHandler"/>
</extensions>
<extensionPoints>
@@ -255,4 +260,4 @@
<extensionPoint name="lightBuildProvider" interface="org.jetbrains.android.compiler.AndroidLightBuildProvider"/>
<extensionPoint name="refactoringContextProvider" interface="org.jetbrains.android.refactoring.AndroidRefactoringContextProvider"/>
</extensionPoints>
</idea-plugin>
</idea-plugin>
@@ -45,6 +45,11 @@ public class AndroidResourceReferenceBase extends PsiReferenceBase.Poly<XmlEleme
return null;
}
@NotNull
public ResourceValue getResourceValue() {
return myResourceValue;
}
@NotNull
public PsiElement[] computeTargetElements() {
final ResolveResult[] resolveResults = multiResolve(false);
@@ -3,15 +3,8 @@ package org.jetbrains.android.refactoring;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.command.UndoConfirmationPolicy;
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.PsiField;
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.XmlTag;
import com.intellij.refactoring.BaseRefactoringProcessor;
@@ -20,7 +13,6 @@ import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.OrderedSet;
import org.jetbrains.android.dom.resources.ResourceNameConverter;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidResourceUtil;
@@ -80,20 +72,14 @@ class AndroidInlineAllStyleUsagesProcessor extends BaseRefactoringProcessor {
@Override
protected UsageInfo[] findUsages() {
final Set<UsageInfo> usages = new HashSet<UsageInfo>();
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<UsageInfo> 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<PsiElement> nonXmlUsages,
Collection<PsiElement> unambiguousUsages,
Collection<PsiElement> unsupportedUsages,
Collection<PsiElement> 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<PsiElement> invalidRefs) {
final OrderedSet<String> lines = new OrderedSet<String>();
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<String> 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);
}
}
@@ -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;
}
}
@@ -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));
}
}
@@ -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);
}
}
@@ -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<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>();
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);
continue;
}
inlineInfos.add(usageData);
}
if (nonXmlUsages.size() > 0 || unambiguousUsages.size() > 0 || unsupportedUsages.size() > 0) {
final String errorMessage = AndroidInlineUtil.buildErrorMessage(
myProject, nonXmlUsages, unambiguousUsages, unsupportedUsages,
Collections.<PsiElement>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;
}
}
@@ -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;
}
}
@@ -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
@@ -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<ResourceValue> 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<ResourceValue> 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<UsageInfo> result) {
for (PsiReference reference : ReferencesSearch.search(element)) {
result.add(new UsageInfo(reference.getElement()));
}
}
@NotNull
static String buildErrorMessage(Project project,
Collection<PsiElement> nonXmlUsages,
Collection<PsiElement> unambiguousUsages,
Collection<PsiElement> unsupportedUsages,
Collection<PsiElement> 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<PsiElement> invalidRefs) {
final OrderedSet<String> lines = new OrderedSet<String>();
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<String> 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;
@@ -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);
}
}
@@ -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<String, String> attributesToAdd = new HashMap<String, String>();
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<XmlAttribute> toDelete = new ArrayList<XmlAttribute>();
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<String, String> 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<String, String> namespacesFromParent = includeTagParent.getLocalNamespaceDeclarations();
final Map<String, String> namespacesToAddToParent = new HashMap<String, String>();
final Map<String, String> namespacesToAddToEachTag = new HashMap<String, String>();
for (Map.Entry<String, String> 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<String, String> 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<String, String> 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<String, String> 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);
}
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
</merge>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed"/>
</LinearLayout>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
</LinearLayout>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
</merge>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed"/>
</LinearLayout>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
</LinearLayout>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
</merge>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://wrongNamespace"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed"/>
</LinearLayout>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://wrongNamespace"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
</LinearLayout>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
</merge>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andr="http://schemas.android.com/apk/res/android"
andr:orientation="vertical"
andr:layout_width="fill_parent"
andr:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed"/>
</LinearLayout>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:andr="http://schemas.android.com/apk/res/android"
andr:orientation="vertical"
andr:layout_width="fill_parent"
andr:layout_height="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
</LinearLayout>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"/>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="2"/>
</merge>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/included"/>
</LinearLayout>
@@ -0,0 +1,5 @@
<caret><?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"/>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/included"/>
</LinearLayout>
@@ -0,0 +1,5 @@
<caret><?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"/>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<i<caret>nclude layout="@layout/included"/>
</LinearLayout>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<i<caret>nclude layout="@layout/included"/>
<include layout="@layout/included"/>
</LinearLayout>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/included"/>
<Te<caret>xtView/>
</LinearLayout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<inc<caret>lude layout="@layout/unknown"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView xmlns:andr="http://schemas.android.com/apk/res/android"
andr:layout_width="fill_parent"
andr:layout_height="wrap_content"
andr:text="Hello World, MyActivity"
andr:background="@android:color/holo_red_dark"/>
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:andr="http://schemas.android.com/apk/res/android"
andr:layout_width="fill_parent"
andr:layout_height="fill_parent"
andr:text="Hello World, MyActivity"
andr:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
xmlns:app="http://schemas.android.com/apk/res/com.example"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"
app:customAttr="true"/>
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"
app:customAttr="true"/>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
xmlns="http://myNamespace"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns="http://myNamespace"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"
app:customAttr="true"/>
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"
app:customAttr="true"/>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns="http://myNamespace"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns="http://myNamespace"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns="http://myNamespace"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/includ<caret>ed" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent" />
<include layout="@layout/included" android:layout_height="wrap_content" android:layout_width="fill_parent"/>
</LinearLayout>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
</LinearLayout>
@@ -0,0 +1,6 @@
<caret><?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello World, MyActivity"
android:background="@android:color/holo_red_dark"/>
@@ -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;
}
}
@@ -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);
}
}
}