mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
IDEA-70314 android extract style refactoring
This commit is contained in:
+7
-49
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.intellij.android.designer.propertyTable.editors;
|
||||
|
||||
import com.android.resources.ResourceFolderType;
|
||||
import com.android.resources.ResourceType;
|
||||
import com.intellij.android.designer.model.RadViewComponent;
|
||||
import com.intellij.android.designer.propertyTable.renderers.ResourceRenderer;
|
||||
@@ -28,7 +27,6 @@ import com.intellij.ide.util.treeView.AbstractTreeStructure;
|
||||
import com.intellij.ide.util.treeView.NodeDescriptor;
|
||||
import com.intellij.ide.util.treeView.NodeRenderer;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
@@ -38,7 +36,7 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.ui.components.JBTabbedPane;
|
||||
import com.intellij.ui.speedSearch.SpeedSearchUtil;
|
||||
@@ -50,7 +48,7 @@ import org.jetbrains.android.actions.CreateResourceFileAction;
|
||||
import org.jetbrains.android.actions.CreateXmlResourceDialog;
|
||||
import org.jetbrains.android.dom.resources.ResourceElement;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.facet.AndroidRootUtil;
|
||||
import org.jetbrains.android.refactoring.AndroidExtractStyleAction;
|
||||
import org.jetbrains.android.resourceManagers.FileResourceProcessor;
|
||||
import org.jetbrains.android.resourceManagers.ResourceManager;
|
||||
import org.jetbrains.android.util.AndroidResourceUtil;
|
||||
@@ -205,15 +203,10 @@ public class ResourceDialog extends DialogWrapper implements TreeSelectionListen
|
||||
actionGroup.add(myNewResourceFileAction);
|
||||
}
|
||||
if (myComponent != null && ResourceType.STYLE.equals(resourceGroup.getType())) {
|
||||
boolean enabled = false;
|
||||
for (XmlAttribute attribute : myComponent.getTag().getAttributes()) {
|
||||
if (attribute.getName().startsWith("android:")) {
|
||||
enabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final XmlTag componentTag = myComponent.getTag();
|
||||
final boolean enabled = AndroidExtractStyleAction.isEnabled(componentTag);
|
||||
myExtractStyleAction.getTemplatePresentation().setEnabled(enabled);
|
||||
//actionGroup.add(myExtractStyleAction); // XXX
|
||||
actionGroup.add(myExtractStyleAction);
|
||||
}
|
||||
|
||||
return actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN, actionGroup);
|
||||
@@ -263,45 +256,10 @@ public class ResourceDialog extends DialogWrapper implements TreeSelectionListen
|
||||
}
|
||||
|
||||
private void extractStyle() {
|
||||
String fileName = AndroidResourceUtil.getDefaultResourceFileName(ResourceType.STYLE);
|
||||
|
||||
VirtualFile[] dirs = {AndroidRootUtil.getResourceDir(AndroidFacet.getInstance(myModule))};
|
||||
List<VirtualFile> subDirs = AndroidResourceUtil.getResourceSubdirs(ResourceFolderType.VALUES.getName(), dirs);
|
||||
List<String> dirNames = new ArrayList<String>();
|
||||
for (VirtualFile dir : subDirs) {
|
||||
dirNames.add(dir.getName());
|
||||
}
|
||||
|
||||
ExtractStyleDialog dialog = new ExtractStyleDialog(myModule, fileName, dirNames, myComponent.getTag());
|
||||
dialog.show();
|
||||
|
||||
if (!dialog.isOK()) {
|
||||
final String resName = AndroidExtractStyleAction.doExtractStyle(myModule, myComponent.getTag(), false, null);
|
||||
if (resName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<XmlAttribute> attributes = dialog.getStyledAttributes();
|
||||
|
||||
StringBuilder value = new StringBuilder();
|
||||
for (XmlAttribute attribute : attributes) {
|
||||
value.append("<item name=\"").append(attribute.getName()).append("\">").append(attribute.getValue()).append("</item>\n");
|
||||
}
|
||||
|
||||
String resName = dialog.getStyleName();
|
||||
if (!AndroidResourceUtil.createValueResource(myModule, resName, ResourceType.STYLE, fileName, dirNames, value.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiDocumentManager.getInstance(myModule.getProject()).commitAllDocuments();
|
||||
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (XmlAttribute attribute : attributes) {
|
||||
attribute.delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
myResultResourceName = "@style/" + resName;
|
||||
close(OK_EXIT_CODE);
|
||||
}
|
||||
|
||||
@@ -420,4 +420,5 @@ directory.not.specified.error=Directory is not specified
|
||||
android.manifest.merger.not.supported.error=Manifest merging is not supported. Please, reconfigure your manifest files
|
||||
invalid.file.resource.name.error=Resource file name must contain only lowercase a-z, 0-9, or _
|
||||
invalid.file.resource.name.error1=Invalid resource file name
|
||||
invalid.resource.name.error=Invalid resource name ''{0}''
|
||||
invalid.resource.name.error=Invalid resource name ''{0}''
|
||||
android.extract.style.title=Extract Android Style
|
||||
@@ -82,6 +82,10 @@
|
||||
icon="/icons/attachDebugger.png">
|
||||
<add-to-group group-id="ToolbarRunGroup" anchor="after" relative-to-action="RunnerActions"/>
|
||||
</action>
|
||||
<action id="AndroidExtractStyleAction" class="org.jetbrains.android.refactoring.AndroidExtractStyleAction" text="Android St_yle..."
|
||||
description="Pull out style-related attributes from layout and extract them as a new style">
|
||||
<add-to-group group-id="IntroduceActionsGroup" relative-to-action="ExtractSuperclass" anchor="after"/>
|
||||
</action>
|
||||
</actions>
|
||||
|
||||
<depends>JUnit</depends>
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.resourceManagers.ResourceManager;
|
||||
import org.jetbrains.android.util.AndroidResourceUtil;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -57,12 +58,24 @@ import static org.jetbrains.android.util.AndroidUtils.SYSTEM_RESOURCE_PACKAGE;
|
||||
*/
|
||||
@SuppressWarnings({"EnumSwitchStatementWhichMissesCases"})
|
||||
public class AndroidDomUtil {
|
||||
|
||||
|
||||
public static final StaticEnumConverter BOOLEAN_CONVERTER = new StaticEnumConverter("true", "false");
|
||||
public static final Map<String, String> SPECIAL_RESOURCE_TYPES = new HashMap<String, String>();
|
||||
private static final PackageClassConverter ACTIVITY_CONVERTER = new PackageClassConverter(AndroidUtils.ACTIVITY_BASE_CLASS_NAME);
|
||||
private static final OnClickConverter ON_CLICK_CONVERTER = new OnClickConverter();
|
||||
private static final FragmentClassConverter FRAGMENT_CLASS_CONVERTER = new FragmentClassConverter();
|
||||
|
||||
@NonNls public static final String ATTR_ID = "id";
|
||||
@NonNls public static final String ATTR_STYLE = "style";
|
||||
@NonNls public static final String ATTR_LAYOUT_PREFIX = "layout_";
|
||||
@NonNls public static final String ATTR_LAYOUT_MARGIN = "layout_margin";
|
||||
@NonNls public static final String ATTR_TEXT = "text";
|
||||
@NonNls public static final String ATTR_HINT = "hint";
|
||||
@NonNls public static final String ATTR_SRC = "src";
|
||||
@NonNls public static final String ATTR_ON_CLICK = "onClick";
|
||||
|
||||
|
||||
static {
|
||||
addSpecialResourceType(ResourceType.STRING.getName(), "label", "description", "title");
|
||||
addSpecialResourceType(ResourceType.DRAWABLE.getName(), "icon");
|
||||
|
||||
@@ -33,6 +33,8 @@ import java.util.List;
|
||||
public interface Style extends ResourceElement {
|
||||
List<StyleItem> getItems();
|
||||
|
||||
StyleItem addItem();
|
||||
|
||||
@Attribute("parent")
|
||||
@Convert(ParentStyleConverter.class)
|
||||
GenericAttributeValue<ResourceValue> getParentStyle();
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
package org.jetbrains.android.refactoring;
|
||||
|
||||
import com.android.resources.ResourceFolderType;
|
||||
import com.android.resources.ResourceType;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.lang.xml.XMLLanguage;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.Result;
|
||||
import com.intellij.openapi.command.UndoConfirmationPolicy;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.psi.xml.XmlText;
|
||||
import com.intellij.refactoring.RefactoringActionHandler;
|
||||
import com.intellij.refactoring.actions.BaseRefactoringAction;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import com.intellij.util.xml.DomElement;
|
||||
import com.intellij.util.xml.DomManager;
|
||||
import org.jetbrains.android.dom.AndroidDomUtil;
|
||||
import org.jetbrains.android.dom.layout.LayoutDomFileDescription;
|
||||
import org.jetbrains.android.dom.layout.LayoutViewElement;
|
||||
import org.jetbrains.android.dom.resources.ResourceElement;
|
||||
import org.jetbrains.android.dom.resources.ResourceValue;
|
||||
import org.jetbrains.android.dom.resources.Style;
|
||||
import org.jetbrains.android.dom.resources.StyleItem;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
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 org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidExtractStyleAction extends BaseRefactoringAction {
|
||||
private static String[] NON_EXTRACTABLE_ATTRIBUTES =
|
||||
new String[]{AndroidDomUtil.ATTR_ID, AndroidDomUtil.ATTR_TEXT, AndroidDomUtil.ATTR_HINT, AndroidDomUtil.ATTR_SRC,
|
||||
AndroidDomUtil.ATTR_ON_CLICK};
|
||||
|
||||
private final MyTestConfig myTestConfig;
|
||||
|
||||
public AndroidExtractStyleAction() {
|
||||
myTestConfig = null;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public AndroidExtractStyleAction(@Nullable MyTestConfig testConfig) {
|
||||
myTestConfig = testConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAvailableInEditorOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAvailableOnElementInEditorAndFile(PsiElement element, Editor editor, PsiFile file, DataContext context) {
|
||||
if (element == null ||
|
||||
AndroidFacet.getInstance(element) == null ||
|
||||
PsiTreeUtil.getParentOfType(element, XmlText.class) != null) {
|
||||
return false;
|
||||
}
|
||||
final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
|
||||
return tag != null && isEnabled(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabledOnElements(PsiElement[] elements) {
|
||||
if (elements.length != 1) {
|
||||
return false;
|
||||
}
|
||||
final PsiElement element = elements[0];
|
||||
return element instanceof XmlTag &&
|
||||
AndroidFacet.getInstance(element) != null &&
|
||||
isEnabled((XmlTag)element);
|
||||
}
|
||||
|
||||
public static boolean isEnabled(@NotNull XmlTag tag) {
|
||||
return getLayoutViewElement(tag) != null &&
|
||||
getExtractableAttributes(tag).size() > 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LayoutViewElement getLayoutViewElement(@NotNull XmlTag tag) {
|
||||
final DomElement domElement = DomManager.getDomManager(tag.getProject()).getDomElement(tag);
|
||||
return domElement instanceof LayoutViewElement
|
||||
? (LayoutViewElement)domElement
|
||||
: null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RefactoringActionHandler getHandler(DataContext dataContext) {
|
||||
return new MyHandler(myTestConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAvailableForLanguage(Language language) {
|
||||
return language == XMLLanguage.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAvailableForFile(PsiFile file) {
|
||||
return file instanceof XmlFile &&
|
||||
AndroidFacet.getInstance(file) != null &&
|
||||
DomManager.getDomManager(file.getProject()).getDomFileDescription((XmlFile)file)
|
||||
instanceof LayoutDomFileDescription;
|
||||
}
|
||||
|
||||
private static void doExtractStyle(@NotNull XmlTag viewTag, @Nullable MyTestConfig testConfig) {
|
||||
final Module module = ModuleUtilCore.findModuleForPsiElement(viewTag);
|
||||
assert module != null;
|
||||
doExtractStyle(module, viewTag, true, testConfig);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String doExtractStyle(@NotNull Module module,
|
||||
@NotNull final XmlTag viewTag,
|
||||
final boolean addStyleAttributeToTag,
|
||||
@Nullable MyTestConfig testConfig) {
|
||||
final PsiFile file = viewTag.getContainingFile();
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
final String dialogTitle = AndroidBundle.message("android.extract.style.title");
|
||||
final String fileName = AndroidResourceUtil.getDefaultResourceFileName(ResourceType.STYLE);
|
||||
assert fileName != null;
|
||||
final List<String> dirNames = Arrays.asList(ResourceFolderType.VALUES.getName());
|
||||
final List<XmlAttribute> extractableAttributes = getExtractableAttributes(viewTag);
|
||||
final Project project = module.getProject();
|
||||
|
||||
if (extractableAttributes.size() == 0) {
|
||||
AndroidUtils.reportError(project, "The tag doesn't contain any attributes that can be extracted", dialogTitle);
|
||||
return null;
|
||||
}
|
||||
|
||||
final LayoutViewElement viewElement = getLayoutViewElement(viewTag);
|
||||
assert viewElement != null;
|
||||
final ResourceValue parentStyleVlaue = viewElement.getStyle().getValue();
|
||||
final String parentStyle;
|
||||
boolean supportImplicitParent = false;
|
||||
|
||||
if (parentStyleVlaue != null) {
|
||||
parentStyle = parentStyleVlaue.getResourceName();
|
||||
if (!ResourceType.STYLE.getName().equals(parentStyleVlaue.getResourceType()) || parentStyle == null || parentStyle.length() == 0) {
|
||||
AndroidUtils.reportError(project, "Invalid parent style reference " + parentStyleVlaue.toString(), dialogTitle);
|
||||
return null;
|
||||
}
|
||||
supportImplicitParent = parentStyleVlaue.getPackage() == null;
|
||||
}
|
||||
else {
|
||||
parentStyle = null;
|
||||
}
|
||||
|
||||
final String styleName;
|
||||
final List<XmlAttribute> styledAttributes;
|
||||
final Module chosenModule;
|
||||
|
||||
if (testConfig == null) {
|
||||
final ExtractStyleDialog dialog =
|
||||
new ExtractStyleDialog(module, fileName, supportImplicitParent ? parentStyle : null, dirNames, extractableAttributes);
|
||||
dialog.setTitle(dialogTitle);
|
||||
dialog.show();
|
||||
|
||||
if (!dialog.isOK()) {
|
||||
return null;
|
||||
}
|
||||
chosenModule = dialog.getChosenModule();
|
||||
assert chosenModule != null;
|
||||
|
||||
styledAttributes = dialog.getStyledAttributes();
|
||||
styleName = dialog.getStyleName();
|
||||
}
|
||||
else {
|
||||
testConfig.validate(extractableAttributes);
|
||||
|
||||
chosenModule = testConfig.getModule();
|
||||
styleName = testConfig.getStyleName();
|
||||
final Set<String> attrsToExtract = new HashSet<String>(Arrays.asList(testConfig.getAttributesToExtract()));
|
||||
styledAttributes = new ArrayList<XmlAttribute>();
|
||||
|
||||
for (XmlAttribute attribute : extractableAttributes) {
|
||||
if (attrsToExtract.contains(attribute.getName())) {
|
||||
styledAttributes.add(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
final boolean[] success = {false};
|
||||
final boolean finalSupportImplicitParent = supportImplicitParent;
|
||||
|
||||
new WriteCommandAction(project, "Extract Android Style '" + styleName + "'", file) {
|
||||
@Override
|
||||
protected void run(final Result result) throws Throwable {
|
||||
final List<XmlAttribute> attributesToDelete = new ArrayList<XmlAttribute>();
|
||||
|
||||
if (!AndroidResourceUtil
|
||||
.createValueResource(chosenModule, styleName, ResourceType.STYLE, fileName, dirNames, new Processor<ResourceElement>() {
|
||||
@Override
|
||||
public boolean process(ResourceElement element) {
|
||||
assert element instanceof Style;
|
||||
final Style style = (Style)element;
|
||||
|
||||
for (XmlAttribute attribute : styledAttributes) {
|
||||
if (SdkConstants.NS_RESOURCES.equals(attribute.getNamespace())) {
|
||||
final StyleItem item = style.addItem();
|
||||
item.getName().setStringValue("android:" + attribute.getLocalName());
|
||||
item.setStringValue(attribute.getValue());
|
||||
attributesToDelete.add(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
if (parentStyleVlaue != null && (!finalSupportImplicitParent || !styleName.startsWith(parentStyle + "."))) {
|
||||
final String aPackage = parentStyleVlaue.getPackage();
|
||||
style.getParentStyle().setStringValue((aPackage != null ? aPackage + ":" : "") + parentStyle);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (XmlAttribute attribute : attributesToDelete) {
|
||||
attribute.delete();
|
||||
}
|
||||
if (addStyleAttributeToTag) {
|
||||
final LayoutViewElement viewElement = getLayoutViewElement(viewTag);
|
||||
assert viewElement != null;
|
||||
viewElement.getStyle().setStringValue("@style/" + styleName);
|
||||
}
|
||||
}
|
||||
});
|
||||
success[0] = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UndoConfirmationPolicy getUndoConfirmationPolicy() {
|
||||
return UndoConfirmationPolicy.REQUEST_CONFIRMATION;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
return success[0] ? styleName : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static List<XmlAttribute> getExtractableAttributes(@NotNull XmlTag viewTag) {
|
||||
final List<XmlAttribute> extractableAttributes = new ArrayList<XmlAttribute>();
|
||||
|
||||
for (XmlAttribute attribute : viewTag.getAttributes()) {
|
||||
if (canBeExtracted(attribute)) {
|
||||
extractableAttributes.add(attribute);
|
||||
}
|
||||
}
|
||||
return extractableAttributes;
|
||||
}
|
||||
|
||||
private static boolean canBeExtracted(@NotNull XmlAttribute attribute) {
|
||||
if (!(SdkConstants.NS_RESOURCES.equals(attribute.getNamespace()))) {
|
||||
return false;
|
||||
}
|
||||
final String name = attribute.getLocalName();
|
||||
if (ArrayUtil.find(NON_EXTRACTABLE_ATTRIBUTES, name) >= 0) {
|
||||
return false;
|
||||
}
|
||||
if (name.startsWith(AndroidDomUtil.ATTR_STYLE)) {
|
||||
return false;
|
||||
}
|
||||
if (name.startsWith(AndroidDomUtil.ATTR_LAYOUT_PREFIX) && !name.startsWith(AndroidDomUtil.ATTR_LAYOUT_MARGIN)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class MyHandler implements RefactoringActionHandler {
|
||||
private final MyTestConfig myTestConfig;
|
||||
|
||||
private MyHandler(@Nullable MyTestConfig testConfig) {
|
||||
myTestConfig = testConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
|
||||
final PsiElement element = getElementAtCaret(editor, file);
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
|
||||
if (tag == null) {
|
||||
return;
|
||||
}
|
||||
doExtractStyle(tag, myTestConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
|
||||
if (elements.length != 1) {
|
||||
return;
|
||||
}
|
||||
final PsiElement element = elements[0];
|
||||
if (!(element instanceof XmlTag)) {
|
||||
return;
|
||||
}
|
||||
doExtractStyle((XmlTag)element, myTestConfig);
|
||||
}
|
||||
}
|
||||
|
||||
static class MyTestConfig {
|
||||
private final Module myModule;
|
||||
private final String myStyleName;
|
||||
private final String[] myAttributesToExtract;
|
||||
|
||||
MyTestConfig(@NotNull Module module,
|
||||
@NotNull String styleName,
|
||||
@NotNull String[] attributesToExtract) {
|
||||
myModule = module;
|
||||
myStyleName = styleName;
|
||||
myAttributesToExtract = attributesToExtract;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Module getModule() {
|
||||
return myModule;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getStyleName() {
|
||||
return myStyleName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String[] getAttributesToExtract() {
|
||||
return myAttributesToExtract;
|
||||
}
|
||||
|
||||
public void validate(@NotNull List<XmlAttribute> extractableAttributes) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.refactoring.ExtractStyleDialog">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="337" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="fabf3" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="6b581"/>
|
||||
<text value="&Style name:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="6b581" class="javax.swing.JTextField" binding="myStyleNameField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<grid id="8a6e9" binding="myAttributeListWrapper" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="-1" height="200"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
<component id="7f54d" class="com.intellij.ui.components.JBLabel" binding="myAttributesLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="&Attributes:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="8a393" class="com.intellij.ui.components.JBLabel" binding="myModuleLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="acdc4"/>
|
||||
<text value="&Module:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="acdc4" class="javax.swing.JComboBox" binding="myModuleCombo">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+232
-239
@@ -1,240 +1,233 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.android.designer.propertyTable.editors;
|
||||
|
||||
import com.android.resources.ResourceType;
|
||||
import com.intellij.openapi.actionSystem.ActionToolbarPosition;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.ValidationInfo;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.uiDesigner.core.GridConstraints;
|
||||
import com.intellij.uiDesigner.core.GridLayoutManager;
|
||||
import com.intellij.util.PlatformIcons;
|
||||
import com.intellij.util.containers.Convertor;
|
||||
import com.intellij.util.ui.tree.TreeUtil;
|
||||
import org.jetbrains.android.actions.CreateXmlResourceDialog;
|
||||
import org.jetbrains.android.util.AndroidResourceUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Alexander Lobas
|
||||
*/
|
||||
public class ExtractStyleDialog extends DialogWrapper {
|
||||
private final Module myModule;
|
||||
private final String myFileName;
|
||||
private final List<String> myDirNames;
|
||||
|
||||
private final JPanel myPanel;
|
||||
private final JTextField myNameText;
|
||||
private final CheckboxTree myTree;
|
||||
|
||||
private final CheckedTreeNode myRootNode;
|
||||
|
||||
public ExtractStyleDialog(Module module, String fileName, List<String> dirNames, XmlTag tag) {
|
||||
super(module.getProject());
|
||||
myModule = module;
|
||||
myFileName = fileName;
|
||||
myDirNames = dirNames;
|
||||
|
||||
myPanel = new JPanel(new GridLayoutManager(3, 2));
|
||||
|
||||
JLabel nameLabel = new JLabel("Style name:");
|
||||
nameLabel.setDisplayedMnemonic('n');
|
||||
myPanel.add(nameLabel,
|
||||
new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, 0, 0, null, null, null));
|
||||
|
||||
myNameText = new JTextField();
|
||||
myPanel.add(myNameText, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
|
||||
GridConstraints.SIZEPOLICY_CAN_GROW, 0, null, null, null));
|
||||
|
||||
nameLabel.setLabelFor(myNameText);
|
||||
|
||||
myNameText.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
checkFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
checkFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
checkFinish();
|
||||
}
|
||||
});
|
||||
|
||||
JLabel attributesLabel = new JLabel("Attributes:");
|
||||
attributesLabel.setDisplayedMnemonic('A');
|
||||
myPanel.add(attributesLabel,
|
||||
new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, 0, 0, null, null, null));
|
||||
|
||||
myRootNode = new CheckedTreeNode(null);
|
||||
for (XmlAttribute attribute : tag.getAttributes()) {
|
||||
String name = attribute.getName();
|
||||
if (!"style".equalsIgnoreCase(name) && name.startsWith("android:")) {
|
||||
CheckedTreeNode treeNode = new CheckedTreeNode(attribute);
|
||||
treeNode.setChecked(!"android:layout_width".equalsIgnoreCase(name) && !"android:layout_height".equalsIgnoreCase(name));
|
||||
myRootNode.add(treeNode);
|
||||
}
|
||||
}
|
||||
|
||||
CheckboxTree.CheckboxTreeCellRenderer renderer = new CheckboxTree.CheckboxTreeCellRenderer() {
|
||||
@Override
|
||||
public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||
if (value instanceof CheckedTreeNode) {
|
||||
XmlAttribute attribute = (XmlAttribute)((CheckedTreeNode)value).getUserObject();
|
||||
if (attribute != null) {
|
||||
getTextRenderer().append(attribute.getLocalName());
|
||||
getTextRenderer().append(" [" + attribute.getValue() + "]", SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
myTree = new CheckboxTree(renderer, myRootNode) {
|
||||
@Override
|
||||
protected void onNodeStateChanged(CheckedTreeNode node) {
|
||||
checkFinish();
|
||||
}
|
||||
|
||||
protected void installSpeedSearch() {
|
||||
new TreeSpeedSearch(this, new Convertor<TreePath, String>() {
|
||||
public String convert(TreePath path) {
|
||||
Object object = path.getLastPathComponent();
|
||||
if (object instanceof CheckedTreeNode) {
|
||||
XmlAttribute attribute = (XmlAttribute)((CheckedTreeNode)object).getUserObject();
|
||||
if (attribute != null) {
|
||||
return attribute.getLocalName();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
myTree.setRootVisible(false);
|
||||
myTree.setShowsRootHandles(false);
|
||||
TreeUtil.expandAll(myTree);
|
||||
|
||||
attributesLabel.setLabelFor(myTree);
|
||||
|
||||
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree);
|
||||
decorator.setToolbarPosition(ActionToolbarPosition.RIGHT);
|
||||
decorator.setEditAction(null);
|
||||
decorator.disableUpDownActions();
|
||||
|
||||
AnActionButton selectAll = new AnActionButton("Select All", null, PlatformIcons.SELECT_ALL_ICON) {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
setChecked(true);
|
||||
}
|
||||
};
|
||||
decorator.addExtraAction(selectAll);
|
||||
|
||||
AnActionButton unselectAll = new AnActionButton("Unselect All", null, PlatformIcons.UNSELECT_ALL_ICON) {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
setChecked(false);
|
||||
}
|
||||
};
|
||||
decorator.addExtraAction(unselectAll);
|
||||
|
||||
|
||||
myPanel.add(decorator.createPanel(),
|
||||
new GridConstraints(2, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
|
||||
GridConstraints.SIZEPOLICY_CAN_GROW, 0, null, null, null));
|
||||
|
||||
myPanel.setPreferredSize(new Dimension(400, -1));
|
||||
|
||||
setTitle("Extract Style");
|
||||
checkFinish();
|
||||
init();
|
||||
}
|
||||
|
||||
private void checkFinish() {
|
||||
if (AndroidResourceUtil.isCorrectAndroidResourceName(myNameText.getText().trim())) {
|
||||
int count = myRootNode.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
CheckedTreeNode treeNode = (CheckedTreeNode)myRootNode.getChildAt(i);
|
||||
if (treeNode.isChecked()) {
|
||||
setOKActionEnabled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
setOKActionEnabled(false);
|
||||
}
|
||||
|
||||
private void setChecked(boolean value) {
|
||||
int count = myRootNode.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
((CheckedTreeNode)myRootNode.getChildAt(i)).setChecked(value);
|
||||
}
|
||||
myTree.repaint();
|
||||
checkFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myNameText;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidationInfo doValidate() {
|
||||
return CreateXmlResourceDialog.checkIfResourceAlreadyExists(myModule, getStyleName(), ResourceType.STYLE, myDirNames, myFileName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getStyleName() {
|
||||
return myNameText.getText();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<XmlAttribute> getStyledAttributes() {
|
||||
List<XmlAttribute> attributes = new ArrayList<XmlAttribute>();
|
||||
|
||||
int count = myRootNode.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
CheckedTreeNode treeNode = (CheckedTreeNode)myRootNode.getChildAt(i);
|
||||
if (treeNode.isChecked()) {
|
||||
attributes.add((XmlAttribute)treeNode.getUserObject());
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetbrains.android.refactoring;
|
||||
|
||||
import com.android.resources.ResourceType;
|
||||
import com.intellij.openapi.actionSystem.ActionToolbarPosition;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.ValidationInfo;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.util.PlatformIcons;
|
||||
import com.intellij.util.containers.Convertor;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import com.intellij.util.ui.tree.TreeUtil;
|
||||
import org.jetbrains.android.actions.CreateXmlResourceDialog;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.util.AndroidResourceUtil;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.android.util.ModuleListCellRendererWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Alexander Lobas
|
||||
*/
|
||||
public class ExtractStyleDialog extends DialogWrapper {
|
||||
|
||||
private JPanel myPanel;
|
||||
private JTextField myStyleNameField;
|
||||
private JPanel myAttributeListWrapper;
|
||||
private JBLabel myAttributesLabel;
|
||||
private JBLabel myModuleLabel;
|
||||
private JComboBox myModuleCombo;
|
||||
|
||||
private final Module myModule;
|
||||
private final String myFileName;
|
||||
private final List<String> myDirNames;
|
||||
|
||||
private final CheckboxTree myTree;
|
||||
private final CheckedTreeNode myRootNode;
|
||||
|
||||
public ExtractStyleDialog(@NotNull Module module,
|
||||
@NotNull String fileName,
|
||||
@Nullable String parentStyleName,
|
||||
@NotNull List<String> dirNames,
|
||||
@NotNull List<XmlAttribute> attributes) {
|
||||
super(module.getProject());
|
||||
myFileName = fileName;
|
||||
myDirNames = dirNames;
|
||||
|
||||
if (parentStyleName != null && parentStyleName.length() > 0) {
|
||||
myStyleNameField.setText(parentStyleName + ".");
|
||||
myStyleNameField.selectAll();
|
||||
}
|
||||
|
||||
final Set<Module> modulesSet = new HashSet<Module>();
|
||||
modulesSet.add(module);
|
||||
|
||||
for (AndroidFacet depFacet : AndroidUtils.getAllAndroidDependencies(module, true)) {
|
||||
modulesSet.add(depFacet.getModule());
|
||||
}
|
||||
|
||||
assert modulesSet.size() > 0;
|
||||
|
||||
if (modulesSet.size() == 1) {
|
||||
myModule = module;
|
||||
myModuleLabel.setVisible(false);
|
||||
myModuleCombo.setVisible(false);
|
||||
}
|
||||
else {
|
||||
myModule = null;
|
||||
|
||||
final Module[] modules = modulesSet.toArray(new Module[modulesSet.size()]);
|
||||
Arrays.sort(modules, new Comparator<Module>() {
|
||||
@Override
|
||||
public int compare(Module m1, Module m2) {
|
||||
return m1.getName().compareTo(m2.getName());
|
||||
}
|
||||
});
|
||||
|
||||
myModuleCombo.setModel(new DefaultComboBoxModel(modules));
|
||||
myModuleCombo.setSelectedItem(module);
|
||||
myModuleCombo.setRenderer(new ModuleListCellRendererWrapper(myModuleCombo.getRenderer()));
|
||||
}
|
||||
|
||||
myRootNode = new CheckedTreeNode(null);
|
||||
|
||||
for (XmlAttribute attribute : attributes) {
|
||||
myRootNode.add(new CheckedTreeNode(attribute));
|
||||
}
|
||||
|
||||
CheckboxTree.CheckboxTreeCellRenderer renderer = new CheckboxTree.CheckboxTreeCellRenderer() {
|
||||
@Override
|
||||
public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||
if (value instanceof CheckedTreeNode) {
|
||||
XmlAttribute attribute = (XmlAttribute)((CheckedTreeNode)value).getUserObject();
|
||||
if (attribute != null) {
|
||||
getTextRenderer().append(attribute.getLocalName());
|
||||
getTextRenderer().append(" [" + attribute.getValue() + "]", SimpleTextAttributes.GRAY_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
myTree = new CheckboxTree(renderer, myRootNode) {
|
||||
protected void installSpeedSearch() {
|
||||
new TreeSpeedSearch(this, new Convertor<TreePath, String>() {
|
||||
public String convert(TreePath path) {
|
||||
Object object = path.getLastPathComponent();
|
||||
if (object instanceof CheckedTreeNode) {
|
||||
XmlAttribute attribute = (XmlAttribute)((CheckedTreeNode)object).getUserObject();
|
||||
if (attribute != null) {
|
||||
return attribute.getLocalName();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
myTree.setRootVisible(false);
|
||||
myTree.setShowsRootHandles(false);
|
||||
TreeUtil.expandAll(myTree);
|
||||
|
||||
myAttributesLabel.setLabelFor(myTree);
|
||||
|
||||
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree);
|
||||
decorator.setToolbarPosition(ActionToolbarPosition.RIGHT);
|
||||
decorator.setEditAction(null);
|
||||
decorator.disableUpDownActions();
|
||||
|
||||
AnActionButton selectAll = new AnActionButton("Select All", null, PlatformIcons.SELECT_ALL_ICON) {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
setChecked(true);
|
||||
}
|
||||
};
|
||||
decorator.addExtraAction(selectAll);
|
||||
|
||||
AnActionButton unselectAll = new AnActionButton("Unselect All", null, PlatformIcons.UNSELECT_ALL_ICON) {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
setChecked(false);
|
||||
}
|
||||
};
|
||||
decorator.addExtraAction(unselectAll);
|
||||
|
||||
myAttributeListWrapper.add(decorator.createPanel());
|
||||
init();
|
||||
}
|
||||
|
||||
private void setChecked(boolean value) {
|
||||
int count = myRootNode.getChildCount();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
((CheckedTreeNode)myRootNode.getChildAt(i)).setChecked(value);
|
||||
}
|
||||
myTree.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myStyleNameField;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidationInfo doValidate() {
|
||||
final String styleName = getStyleName();
|
||||
|
||||
if (styleName.length() == 0) {
|
||||
return new ValidationInfo("specify style name", myStyleNameField);
|
||||
}
|
||||
if (!AndroidResourceUtil.isCorrectAndroidResourceName(styleName)) {
|
||||
return new ValidationInfo("incorrect style name", myStyleNameField);
|
||||
}
|
||||
final Module module = getChosenModule();
|
||||
if (module == null) {
|
||||
return new ValidationInfo("specify module", myModuleCombo);
|
||||
}
|
||||
return CreateXmlResourceDialog.checkIfResourceAlreadyExists(module, getStyleName(), ResourceType.STYLE, myDirNames, myFileName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getStyleName() {
|
||||
return myStyleNameField.getText().trim();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<XmlAttribute> getStyledAttributes() {
|
||||
List<XmlAttribute> attributes = new ArrayList<XmlAttribute>();
|
||||
int count = myRootNode.getChildCount();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final CheckedTreeNode treeNode = (CheckedTreeNode)myRootNode.getChildAt(i);
|
||||
|
||||
if (treeNode.isChecked()) {
|
||||
attributes.add((XmlAttribute)treeNode.getUserObject());
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Module getChosenModule() {
|
||||
return myModule != null ? myModule : (Module)myModuleCombo.getSelectedItem();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ package org.jetbrains.android.util;
|
||||
import com.android.resources.ResourceFolderType;
|
||||
import com.android.resources.ResourceType;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.ide.actions.CreateElementActionBase;
|
||||
import com.intellij.ide.fileTemplates.FileTemplate;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateManager;
|
||||
@@ -32,7 +31,6 @@ import com.intellij.openapi.module.ModuleUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ModulePackageIndex;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
@@ -44,7 +42,6 @@ import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
@@ -268,6 +265,9 @@ public class AndroidResourceUtil {
|
||||
if (resourceName.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (resourceName.startsWith(".") || resourceName.endsWith(".")) {
|
||||
return false;
|
||||
}
|
||||
final String[] identifiers = resourceName.split("\\.");
|
||||
|
||||
for (String identifier : identifiers) {
|
||||
@@ -613,17 +613,17 @@ public class AndroidResourceUtil {
|
||||
}
|
||||
|
||||
public static boolean createValueResource(@NotNull Module module,
|
||||
@NotNull String resourceName,
|
||||
@NotNull ResourceType resourceType,
|
||||
@NotNull String fileName,
|
||||
@NotNull List<String> dirNames,
|
||||
@NotNull String value) {
|
||||
@NotNull String resourceName,
|
||||
@NotNull final ResourceType resourceType,
|
||||
@NotNull String fileName,
|
||||
@NotNull List<String> dirNames,
|
||||
@NotNull Processor<ResourceElement> afterAddedProcessor) {
|
||||
final Project project = module.getProject();
|
||||
final AndroidFacet facet = AndroidFacet.getInstance(module);
|
||||
assert facet != null;
|
||||
|
||||
try {
|
||||
return addValueResource(facet, resourceName, resourceType, fileName, dirNames, value);
|
||||
return addValueResource(facet, resourceName, resourceType, fileName, dirNames, afterAddedProcessor);
|
||||
}
|
||||
catch (Exception e) {
|
||||
final String message = CreateElementActionBase.filterMessage(e.getMessage());
|
||||
@@ -633,18 +633,40 @@ public class AndroidResourceUtil {
|
||||
}
|
||||
else {
|
||||
LOG.info(e);
|
||||
reportError(project, message);
|
||||
AndroidUtils.reportError(project, message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean createValueResource(@NotNull Module module,
|
||||
@NotNull String resourceName,
|
||||
@NotNull final ResourceType resourceType,
|
||||
@NotNull String fileName,
|
||||
@NotNull List<String> dirNames,
|
||||
@NotNull final String value) {
|
||||
return createValueResource(module, resourceName, resourceType, fileName, dirNames, new Processor<ResourceElement>() {
|
||||
@Override
|
||||
public boolean process(ResourceElement element) {
|
||||
if (value.length() > 0) {
|
||||
element.setStringValue(value);
|
||||
}
|
||||
else if (resourceType == ResourceType.STYLEABLE ||
|
||||
resourceType == ResourceType.STYLE) {
|
||||
element.setStringValue("value");
|
||||
element.getXmlTag().getValue().setText("");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean addValueResource(@NotNull AndroidFacet facet,
|
||||
@NotNull final String resourceName,
|
||||
@NotNull final ResourceType resourceType,
|
||||
@NotNull String fileName,
|
||||
@NotNull List<String> dirNames,
|
||||
@NotNull final String value) throws Exception {
|
||||
@NotNull final Processor<ResourceElement> afterAddedProcessor) throws Exception {
|
||||
if (dirNames.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -666,7 +688,7 @@ public class AndroidResourceUtil {
|
||||
for (int i = 0; i < resFiles.length; i++) {
|
||||
final Resources resources = AndroidUtils.loadDomElement(facet.getModule(), resFiles[i], Resources.class);
|
||||
if (resources == null) {
|
||||
reportError(facet.getModule().getProject(), AndroidBundle.message("not.resource.file.error", fileName));
|
||||
AndroidUtils.reportError(facet.getModule().getProject(), AndroidBundle.message("not.resource.file.error", fileName));
|
||||
return false;
|
||||
}
|
||||
resourcesElements[i] = resources;
|
||||
@@ -678,15 +700,7 @@ public class AndroidResourceUtil {
|
||||
for (Resources resources : resourcesElements) {
|
||||
final ResourceElement element = addValueResource(resourceType, resources);
|
||||
element.getName().setValue(resourceName);
|
||||
|
||||
if (value.length() > 0) {
|
||||
element.setStringValue(value);
|
||||
}
|
||||
else if (resourceType == ResourceType.STYLEABLE ||
|
||||
resourceType == ResourceType.STYLE) {
|
||||
element.setStringValue("value");
|
||||
element.getXmlTag().getValue().setText("");
|
||||
}
|
||||
afterAddedProcessor.process(element);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -702,14 +716,14 @@ public class AndroidResourceUtil {
|
||||
final VirtualFile resDir = facet.getLocalResourceManager().getResourceDir();
|
||||
|
||||
if (resDir == null) {
|
||||
reportError(project, AndroidBundle.message("check.resource.dir.error", module.getName()));
|
||||
AndroidUtils.reportError(project, AndroidBundle.message("check.resource.dir.error", module.getName()));
|
||||
return null;
|
||||
}
|
||||
final VirtualFile dir = AndroidUtils.createChildDirectoryIfNotExist(project, resDir, dirName);
|
||||
final String dirPath = FileUtil.toSystemDependentName(resDir.getPath() + '/' + dirName);
|
||||
|
||||
if (dir == null) {
|
||||
reportError(project, AndroidBundle.message("android.cannot.create.dir.error", dirPath));
|
||||
AndroidUtils.reportError(project, AndroidBundle.message("android.cannot.create.dir.error", dirPath));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -722,20 +736,11 @@ public class AndroidResourceUtil {
|
||||
.createFromTemplate(project, dir, AndroidFileTemplateProvider.VALUE_RESOURCE_FILE_TEMPLATE, fileName);
|
||||
final VirtualFile result = dir.findChild(fileName);
|
||||
if (result == null) {
|
||||
reportError(project, AndroidBundle.message("android.cannot.create.file.error", dirPath + File.separatorChar + fileName));
|
||||
AndroidUtils.reportError(project, AndroidBundle.message("android.cannot.create.file.error", dirPath + File.separatorChar + fileName));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void reportError(@NotNull Project project, @NotNull String message) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
throw new IncorrectOperationException(message);
|
||||
}
|
||||
else {
|
||||
Messages.showErrorDialog(project, message, CommonBundle.getErrorTitle());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Pair<String, String> getReferredResourceField(@NotNull AndroidFacet facet, @NotNull PsiReferenceExpression exp) {
|
||||
final String resFieldName = exp.getReferenceName();
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.android.ddmlib.IDevice;
|
||||
import com.android.ddmlib.ShellCommandUnresponsiveException;
|
||||
import com.android.ddmlib.TimeoutException;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.codeInsight.hint.HintUtil;
|
||||
import com.intellij.codeInsight.navigation.NavigationUtil;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
@@ -69,6 +70,7 @@ import com.intellij.psi.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.ui.content.impl.ContentImpl;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.PsiNavigateUtil;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
@@ -721,8 +723,8 @@ public class AndroidUtils {
|
||||
|
||||
public static void checkPassword(char[] password) throws CommitStepException {
|
||||
if (password.length == 0) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.password.error"));
|
||||
}
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.password.error"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkPassword(JPasswordField passwordField) throws CommitStepException {
|
||||
@@ -743,4 +745,18 @@ public class AndroidUtils {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void reportError(@NotNull Project project, @NotNull String message) {
|
||||
reportError(project, message, CommonBundle.getErrorTitle());
|
||||
}
|
||||
|
||||
public static void reportError(@NotNull Project project, @NotNull String message, @NotNull String title) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
throw new IncorrectOperationException(message);
|
||||
}
|
||||
else {
|
||||
Messages.showErrorDialog(project, message, title);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Butt<caret>on android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/black"
|
||||
android:background="@android:color/white"
|
||||
android:text="Hello"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Button android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello" style="@style/myStyle"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<style name="myStyle">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:background">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Butt<caret>on android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/black"
|
||||
android:background="@android:color/white"
|
||||
style="@style/style1"
|
||||
android:text="Hello"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Butt<caret>on android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/style1.s"
|
||||
android:text="Hello"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<style name="style1">
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
<style name="style1">
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
<style name="style1.s">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:background">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Butt<caret>on android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/black"
|
||||
android:background="@android:color/white"
|
||||
style="@style/style1"
|
||||
android:text="Hello"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Butt<caret>on android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/style2"
|
||||
android:text="Hello"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<style name="style1">
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
<style name="style1">
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
<style name="style2" parent="style1">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:background">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Butt<caret>on android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/black"
|
||||
android:background="@android:color/white"
|
||||
style="@android:style/Widget.Button"
|
||||
android:text="Hello"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Butt<caret>on android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/style1.s"
|
||||
android:text="Hello"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<style name="style1.s" parent="android:Widget.Button">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:background">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,6 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Butt<caret>on android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/style1"
|
||||
android:text="Hello"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Button android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/style1"
|
||||
android:text="Hello">
|
||||
<caret>
|
||||
</Button>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,7 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Button android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:col<caret>or/black"
|
||||
android:background="@android:color/white"
|
||||
android:text="Hello"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Button android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello" style="@style/myStyle"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<style name="myStyle">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:background">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<caret>
|
||||
</resources>
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package org.jetbrains.android.refactoring;
|
||||
|
||||
import com.intellij.openapi.actionSystem.Presentation;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.android.AndroidTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidExtractStyleTest extends AndroidTestCase {
|
||||
private static final String BASE_PATH = "refactoring/extractStyle/";
|
||||
|
||||
public void test1() {
|
||||
doTest("myStyle", new String[]{"android:textColor", "android:background"}, false);
|
||||
}
|
||||
|
||||
public void test2() {
|
||||
doTest("style1.s", new String[]{"android:textColor", "android:background"}, true);
|
||||
}
|
||||
|
||||
public void test3() {
|
||||
doTest("style2", new String[]{"android:textColor", "android:background"}, true);
|
||||
}
|
||||
|
||||
public void test4() {
|
||||
doTest("style1.s", new String[]{"android:textColor", "android:background"}, false);
|
||||
}
|
||||
|
||||
public void test5() {
|
||||
doTestDisabled();
|
||||
}
|
||||
|
||||
public void test6() {
|
||||
doTestDisabled();
|
||||
}
|
||||
|
||||
public void test7() {
|
||||
doTest("myStyle", new String[]{"android:textColor", "android:background"}, false);
|
||||
}
|
||||
|
||||
public void test8() {
|
||||
doTestHidden();
|
||||
}
|
||||
|
||||
private void doTestHidden() {
|
||||
final String testName = getTestName(true);
|
||||
final VirtualFile f =
|
||||
myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/values/test" + testName + ".xml");
|
||||
myFixture.configureFromExistingVirtualFile(f);
|
||||
final Presentation presentation = myFixture.testAction(new AndroidExtractStyleAction());
|
||||
assertFalse(presentation.isVisible());
|
||||
}
|
||||
|
||||
private void doTestDisabled() {
|
||||
final String testName = getTestName(true);
|
||||
final VirtualFile f =
|
||||
myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test" + testName + ".xml");
|
||||
myFixture.configureFromExistingVirtualFile(f);
|
||||
final Presentation presentation = myFixture.testAction(new AndroidExtractStyleAction());
|
||||
assertFalse(presentation.isEnabled());
|
||||
assertTrue(presentation.isVisible());
|
||||
}
|
||||
|
||||
private void doTest(@NotNull String styleName,
|
||||
@NotNull String[] attributes,
|
||||
boolean copyInitialStylesXml) {
|
||||
doTest(styleName, attributes, attributes, copyInitialStylesXml);
|
||||
}
|
||||
|
||||
private void doTest(@NotNull String styleName,
|
||||
@NotNull String[] attributesToExtract,
|
||||
@NotNull String[] expectedExtractableAttrs,
|
||||
boolean copyInitialStylesXml) {
|
||||
final String testName = getTestName(true);
|
||||
|
||||
if (copyInitialStylesXml) {
|
||||
myFixture.copyFileToProject(BASE_PATH + testName + "_styles.xml", "res/values/styles.xml");
|
||||
}
|
||||
final VirtualFile f =
|
||||
myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test" + testName + ".xml");
|
||||
myFixture.configureFromExistingVirtualFile(f);
|
||||
myFixture.testAction(new AndroidExtractStyleAction(new MyConfig(myModule, styleName, attributesToExtract, expectedExtractableAttrs)));
|
||||
myFixture.checkResultByFile(BASE_PATH + testName + "_after.xml");
|
||||
myFixture.checkResultByFile("res/values/styles.xml", BASE_PATH + testName + "_styles_after.xml", true);
|
||||
}
|
||||
|
||||
private static class MyConfig extends AndroidExtractStyleAction.MyTestConfig {
|
||||
private final String[] myExpectedExtractableAttributes;
|
||||
|
||||
MyConfig(@NotNull Module module,
|
||||
@NotNull String styleName,
|
||||
@NotNull String[] attributesToExtract,
|
||||
@NotNull String[] expectedExtractableAttributes) {
|
||||
super(module, styleName, attributesToExtract);
|
||||
myExpectedExtractableAttributes = expectedExtractableAttributes;
|
||||
Arrays.sort(myExpectedExtractableAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(@NotNull List<XmlAttribute> extractableAttributes) {
|
||||
final List<String> names = new ArrayList<String>(extractableAttributes.size());
|
||||
|
||||
for (XmlAttribute attribute : extractableAttributes) {
|
||||
names.add(attribute.getName());
|
||||
}
|
||||
final String[] extractableAttributeNames = ArrayUtil.toStringArray(names);
|
||||
Arrays.sort(extractableAttributeNames);
|
||||
|
||||
assertTrue(
|
||||
"Expected: " + Arrays.toString(myExpectedExtractableAttributes) + "\nActual: " + Arrays.toString(extractableAttributeNames),
|
||||
Arrays.equals(myExpectedExtractableAttributes, extractableAttributeNames)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user