From 886436743d5f723318b56be9942a76b24ce14bc7 Mon Sep 17 00:00:00 2001 From: Tor Norbye Date: Mon, 28 Mar 2011 09:16:41 -0700 Subject: [PATCH] Extract Style Refactoring Adds a new refactoring, "Extract Style", which will show the user the attributes for the current selected elements (or if invoked from an editor context, the attributes overlapping the current caret or editor selection). The user can select which attributes to extract, and these are then added as a new style in the styles.xml file in the project (which is created if necessary). The user can optionally replace the attributes that were extracted, and the user can also optionally set the style attribute on the elements to the new style. (Both are on by default.) This is integrated with the refactoring quick assistant as well. Change-Id: I0504e86a824b00730482607150a879ff28233618 --- .../plugins/com.android.ide.eclipse.adt/plugin.xml | 19 + .../android/ide/common/layout/LayoutConstants.java | 1 + .../adt/internal/editors/layout/LayoutEditor.java | 2 +- .../editors/layout/gle2/DynamicContextMenu.java | 2 + .../editors/layout/gle2/GraphicalEditorPart.java | 2 +- .../layout/refactoring/ExtractStyleAction.java | 47 ++ .../refactoring/ExtractStyleContribution.java | 40 ++ .../refactoring/ExtractStyleRefactoring.java | 538 +++++++++++++++++++++ .../layout/refactoring/ExtractStyleWizard.java | 431 +++++++++++++++++ .../layout/refactoring/RefactoringAssistant.java | 39 +- .../layout/refactoring/VisualRefactoring.java | 49 +- .../descriptors/ResourcesDescriptors.java | 1 + .../wizards/newxmlfile/NewXmlFileWizard.java | 3 +- .../adt/internal/build/AaptQuickFixTest.java | 3 +- .../editors/layout/refactoring/AdtProjectTest.java | 27 +- .../refactoring/ExtractStyleRefactoringTest.java | 229 +++++++++ .../refactoring/RefactoringAssistantTest.java | 4 +- .../layout/refactoring/RefactoringTest.java | 22 +- .../testdata/extractstyle1-expected-extract1b.diff | 3 + .../testdata/extractstyle1-expected-extract1c.diff | 4 + .../testdata/extractstyle1-expected-extract1d.diff | 6 + .../testdata/extractstyle1-expected-extract2.diff | 6 + .../testdata/extractstyle1-expected-extract3.diff | 15 + .../testdata/extractstyle1-expected-extract4.diff | 7 + .../testdata/extractstyle1-expected-extract5.diff | 8 + .../testdata/extractstyle1-expected-extract6.diff | 6 + .../testdata/extractstyle1-expected-extract8.diff | 4 + .../layout/refactoring/testdata/extractstyle1.info | 3 + .../layout/refactoring/testdata/extractstyle1.xml | 11 + .../testdata/extractstyle2-expected-extract7.diff | 13 + .../layout/refactoring/testdata/extractstyle2.info | 3 + .../layout/refactoring/testdata/extractstyle2.xml | 11 + .../navigationstyles-expected-extract2.diff | 6 + .../testdata/newstyles-expected-extract1.diff | 9 + .../testdata/newstyles2-expected-extract1b.diff | 9 + .../testdata/newstyles3-expected-extract1c.diff | 9 + .../testdata/newstyles3-expected-extract8.diff | 9 + .../testdata/newstyles4-expected-extract1d.diff | 9 + .../testdata/newstyles4-expected-extract3.diff | 9 + .../testdata/newstyles5-expected-extract4.diff | 8 + .../testdata/newstyles6-expected-extract5.diff | 8 + .../testdata/newstyles7-expected-extract6.diff | 13 + .../testdata/newstyles8-expected-extract7.diff | 9 + .../testdata/sample1a-expected-assistant1.txt | 1 + .../testdata/sample1a-expected-assistant2.txt | 1 + .../testdata/sample1a-expected-assistant3.txt | 2 +- .../ide/common/resources/ResourceResolver.java | 2 +- 47 files changed, 1640 insertions(+), 23 deletions(-) create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoringTest.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1b.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1c.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1d.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract2.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract3.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract4.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract5.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract6.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract8.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.info create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.xml create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2-expected-extract7.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.info create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.xml create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-extract2.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles-expected-extract1.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index 9c3f20886..82f4e0574 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -686,6 +686,15 @@ tooltip="Extracts Views as Included Layout"> + + + + @@ -857,6 +872,10 @@ id="com.android.ide.eclipse.adt.refactoring.extract.include"> + + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java index 092b0a520..73cb2298a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java @@ -51,6 +51,7 @@ public class LayoutConstants { public static final String GESTURE_OVERLAY_VIEW = "GestureOverlayView";//$NON-NLS-1$ public static final String ATTR_TEXT = "text"; //$NON-NLS-1$ + public static final String ATTR_HINT = "hint"; //$NON-NLS-1$ public static final String ATTR_ID = "id"; //$NON-NLS-1$ public static final String ATTR_STYLE = "style"; //$NON-NLS-1$ public static final String ATTR_HANDLE = "handle"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java index b862ada63..bccc7d0ec 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout; -import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java index a0db1fbac..1857602b0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java @@ -28,6 +28,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeLayoutAction; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeViewAction; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractIncludeAction; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractStyleAction; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.WrapInAction; import org.eclipse.jface.action.Action; @@ -219,6 +220,7 @@ import java.util.regex.Pattern; // or on an included view, or on a non-contiguous selection mMenuManager.insertBefore(endId, new Separator()); mMenuManager.insertBefore(endId, ExtractIncludeAction.create(mEditor)); + mMenuManager.insertBefore(endId, ExtractStyleAction.create(mEditor)); mMenuManager.insertBefore(endId, WrapInAction.create(mEditor)); if (selection.size() == 1 && (selection.get(0).isLayout() || selection.get(0).getViewInfo().getName().equals(FQCN_GESTURE_OVERLAY_VIEW))) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index 47d4f2266..ad3292e9a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -1587,7 +1587,7 @@ public class GraphicalEditorPart extends EditorPart return null; } - ResourceResolver createResolver() { + public ResourceResolver createResolver() { String theme = mConfigComposite.getTheme(); boolean isProjectTheme = mConfigComposite.isProjectTheme(); Map> configuredProjectRes = diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java new file mode 100644 index 000000000..4eef6b896 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; + +/** + * Action executed when the "Extract Style" menu item is invoked. + */ +public class ExtractStyleAction extends VisualRefactoringAction { + @Override + public void run(IAction action) { + if ((mTextSelection != null || mTreeSelection != null) && mFile != null) { + ExtractStyleRefactoring ref = new ExtractStyleRefactoring(mFile, mEditor, + mTextSelection, mTreeSelection); + RefactoringWizard wizard = new ExtractStyleWizard(ref, mEditor); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + op.run(mWindow.getShell(), wizard.getDefaultPageTitle()); + } catch (InterruptedException e) { + // Interrupted. Pass. + } + } + } + + public static IAction create(LayoutEditor editor) { + return create("Extract Style...", editor, ExtractStyleAction.class); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java new file mode 100644 index 000000000..95fbdbc43 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import org.eclipse.ltk.core.refactoring.RefactoringContribution; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; + +import java.util.Map; + +public class ExtractStyleContribution extends RefactoringContribution { + + @SuppressWarnings("unchecked") + @Override + public RefactoringDescriptor createDescriptor(String id, String project, String description, + String comment, Map arguments, int flags) throws IllegalArgumentException { + return new ExtractStyleRefactoring.Descriptor(project, description, comment, arguments); + } + + @SuppressWarnings("unchecked") + @Override + public Map retrieveArgumentMap(RefactoringDescriptor descriptor) { + if (descriptor instanceof ExtractStyleRefactoring.Descriptor) { + return ((ExtractStyleRefactoring.Descriptor) descriptor).getArguments(); + } + return super.retrieveArgumentMap(descriptor); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java new file mode 100644 index 000000000..3e18fcaff --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.AndroidConstants.FD_RES_VALUES; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_HINT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC; +import static com.android.ide.common.layout.LayoutConstants.ATTR_STYLE; +import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; +import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_COLON; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.ITEM_TAG; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.PARENT_ATTR; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.ROOT_ELEMENT; +import static com.android.sdklib.SdkConstants.FD_RESOURCES; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; +import com.android.util.Pair; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.text.edits.InsertEdit; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * Extracts the selection and writes it out as a separate layout file, then adds an + * include to that new layout file. Interactively asks the user for a new name for the + * layout. + *

+ * Remaining work to do / Possible enhancements: + *

    + *
  • Optionally look in other files in the project and attempt to set style attributes + * in other cases where the style attributes match? + *
  • If the elements we are extracting from already contain a style attribute, set that + * style as the parent style of the current style? + *
  • Add a parent-style picker to the wizard (initialized with the above if applicable) + *
  • Pick up indentation settings from the XML module + *
  • Integrate with themes somehow -- make an option to have the extracted style go into + * the theme instead + *
+ */ +@SuppressWarnings("restriction") // XML model +public class ExtractStyleRefactoring extends VisualRefactoring { + private static final String KEY_NAME = "name"; //$NON-NLS-1$ + private static final String KEY_REMOVE_EXTRACTED = "removeextracted"; //$NON-NLS-1$ + private static final String KEY_REMOVE_ALL = "removeall"; //$NON-NLS-1$ + private static final String KEY_APPLY_STYLE = "applystyle"; //$NON-NLS-1$ + private static final String KEY_PARENT = "parent"; //$NON-NLS-1$ + private String mStyleName; + /** The name of the file in res/values/ that the style will be added to. Normally + * res/values/styles.xml - but unit tests pick other names */ + private String mStyleFileName = "styles.xml"; + /** Set a style reference on the extracted elements? */ + private boolean mApplyStyle; + /** Remove the attributes that were extracted? */ + private boolean mRemoveExtracted; + /** List of attributes chosen by the user to be extracted */ + private List mChosenAttributes = new ArrayList(); + /** Remove all attributes that match the extracted attributes names, regardless of value */ + private boolean mRemoveAll; + /** The parent style to extend */ + private String mParent; + /** The full list of available attributes in the refactoring */ + private Map> mAvailableAttributes; + + /** + * This constructor is solely used by {@link Descriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + ExtractStyleRefactoring(Map arguments) { + super(arguments); + mStyleName = arguments.get(KEY_NAME); + mRemoveExtracted = Boolean.parseBoolean(arguments.get(KEY_REMOVE_EXTRACTED)); + mRemoveAll = Boolean.parseBoolean(arguments.get(KEY_REMOVE_ALL)); + mApplyStyle = Boolean.parseBoolean(arguments.get(KEY_APPLY_STYLE)); + mParent = arguments.get(KEY_PARENT); + } + + public ExtractStyleRefactoring(IFile file, LayoutEditor editor, ITextSelection selection, + ITreeSelection treeSelection) { + super(file, editor, selection, treeSelection); + } + + @VisibleForTesting + ExtractStyleRefactoring(List selectedElements, LayoutEditor editor) { + super(selectedElements, editor); + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, + OperationCanceledException { + RefactoringStatus status = new RefactoringStatus(); + + try { + pm.beginTask("Checking preconditions...", 6); + + if (mSelectionStart == -1 || mSelectionEnd == -1) { + status.addFatalError("No selection to extract"); + return status; + } + + // This also ensures that we have a valid DOM model: + if (mElements.size() == 0) { + status.addFatalError("Nothing to extract"); + return status; + } + + pm.worked(1); + return status; + + } finally { + pm.done(); + } + } + + @Override + protected VisualRefactoringDescriptor createDescriptor() { + String comment = getName(); + return new Descriptor( + mProject.getName(), //project + comment, //description + comment, //comment + createArgumentMap()); + } + + @Override + protected Map createArgumentMap() { + Map args = super.createArgumentMap(); + args.put(KEY_NAME, mStyleName); + args.put(KEY_REMOVE_EXTRACTED, Boolean.toString(mRemoveExtracted)); + args.put(KEY_REMOVE_ALL, Boolean.toString(mRemoveAll)); + args.put(KEY_APPLY_STYLE, Boolean.toString(mApplyStyle)); + args.put(KEY_PARENT, mParent); + + return args; + } + + @Override + public String getName() { + return "Extract Style"; + } + + void setStyleName(String styleName) { + mStyleName = styleName; + } + + void setStyleFileName(String styleFileName) { + mStyleFileName = styleFileName; + } + + void setChosenAttributes(List attributes) { + mChosenAttributes = attributes; + } + + void setRemoveExtracted(boolean removeExtracted) { + mRemoveExtracted = removeExtracted; + } + + void setApplyStyle(boolean applyStyle) { + mApplyStyle = applyStyle; + } + + void setRemoveAll(boolean removeAll) { + mRemoveAll = removeAll; + } + + void setParent(String parent) { + mParent = parent; + } + + // ---- Actual implementation of Extract Style modification computation ---- + + /** + * Returns two items: a map from attribute name to a list of attribute nodes of that + * name, and a subset of these attributes that fall within the text selection + * (used to drive initial selection in the wizard) + */ + Pair>, Set> getAvailableAttributes() { + mAvailableAttributes = new TreeMap>(); + Set withinSelection = new HashSet(); + for (Element element : getElements()) { + IndexedRegion elementRegion = getRegion(element); + boolean allIncluded = + (mOriginalSelectionStart <= elementRegion.getStartOffset() && + mOriginalSelectionEnd >= elementRegion.getEndOffset()); + + NamedNodeMap attributeMap = element.getAttributes(); + for (int i = 0, n = attributeMap.getLength(); i < n; i++) { + Attr attribute = (Attr) attributeMap.item(i); + + String name = attribute.getLocalName(); + if (name == null || name.equals(ATTR_ID) || name.startsWith(ATTR_STYLE) + || name.startsWith(ATTR_LAYOUT_PREFIX) || name.equals(ATTR_TEXT) + || name.equals(ATTR_HINT) || name.equals(ATTR_SRC)) { + // Don't offer to extract attributes that don't make sense in + // styles (like "id" or "style"), or attributes that the user + // probably does not want to define in styles (like layout + // attributes such as layout_width, or the label of a button etc). + // This makes the options offered listed in the wizard simpler. + // In special cases where the user *does* want to set one of these + // attributes, they can always do it manually so optimize for + // the common case here. + continue; + } + + // Skip attributes that are in a namespace other than the Android one + String namespace = attribute.getNamespaceURI(); + if (namespace != null && !ANDROID_URI.equals(namespace)) { + continue; + } + + if (!allIncluded) { + IndexedRegion region = getRegion(attribute); + boolean attributeIncluded = mOriginalSelectionStart < region.getEndOffset() && + mOriginalSelectionEnd >= region.getStartOffset(); + if (attributeIncluded) { + withinSelection.add(attribute); + } + } else { + withinSelection.add(attribute); + } + + List list = mAvailableAttributes.get(name); + if (list == null) { + list = new ArrayList(); + mAvailableAttributes.put(name, list); + } + list.add(attribute); + } + } + + return Pair.of(mAvailableAttributes, withinSelection); + } + + IFile getStyleFile(IProject project) { + return project.getFile(new Path(FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP + + mStyleFileName)); + } + + @Override + protected List computeChanges() { + List changes = new ArrayList(); + if (mChosenAttributes.size() == 0) { + return changes; + } + + IFile file = getStyleFile(mEditor.getProject()); + boolean createFile = !file.exists(); + int insertAtIndex; + String initialIndent = null; + if (!createFile) { + Pair context = computeInsertContext(file); + insertAtIndex = context.getFirst(); + initialIndent = context.getSecond(); + } else { + insertAtIndex = 0; + } + + TextFileChange addFile = new TextFileChange("Create new separate style declaration", file); + addFile.setTextType(EXT_XML); + changes.add(addFile); + String styleString = computeStyleDeclaration(createFile, initialIndent); + addFile.setEdit(new InsertEdit(insertAtIndex, styleString)); + + // Remove extracted attributes? + MultiTextEdit rootEdit = new MultiTextEdit(); + if (mRemoveExtracted || mRemoveAll) { + for (Attr attribute : mChosenAttributes) { + List list = mAvailableAttributes.get(attribute.getLocalName()); + for (Attr attr : list) { + if (mRemoveAll || attr.getValue().equals(attribute.getValue())) { + removeAttribute(rootEdit, attr); + } + } + } + } + + // Set the style attribute? + if (mApplyStyle) { + for (Element element : getElements()) { + String value = ResourceResolver.PREFIX_RESOURCE_REF + + ResourceResolver.REFERENCE_STYLE + mStyleName; + setAttribute(rootEdit, element, null, null, ATTR_STYLE, value); + } + } + + if (rootEdit.hasChildren()) { + IFile sourceFile = mEditor.getInputFile(); + TextFileChange change = new TextFileChange(sourceFile.getName(), sourceFile); + change.setEdit(rootEdit); + change.setTextType(EXT_XML); + changes.add(change); + } + + return changes; + } + + private String computeStyleDeclaration(boolean createFile, String initialIndent) { + StringBuilder sb = new StringBuilder(); + if (createFile) { + sb.append(NewXmlFileWizard.XML_HEADER_LINE); + sb.append('<').append(ROOT_ELEMENT).append(' '); + sb.append(XMLNS_COLON).append(ANDROID_NS_NAME).append('=').append('"'); + sb.append(ANDROID_URI); + sb.append('"').append('>').append('\n'); + } + + // Indent. Use the existing indent found for previous +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff new file mode 100644 index 000000000..d83eb49d3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff @@ -0,0 +1,9 @@ +< +--- +> +> +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff new file mode 100644 index 000000000..d83eb49d3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff @@ -0,0 +1,9 @@ +< +--- +> +> +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff new file mode 100644 index 000000000..3b4d930ce --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff @@ -0,0 +1,9 @@ +< +--- +> +> +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff new file mode 100644 index 000000000..d83eb49d3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff @@ -0,0 +1,9 @@ +< +--- +> +> +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff new file mode 100644 index 000000000..0685d947f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff @@ -0,0 +1,9 @@ +< +--- +> +> +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff new file mode 100644 index 000000000..f052485cb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff @@ -0,0 +1,8 @@ +< +--- +> +> +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff new file mode 100644 index 000000000..ce1d4aaea --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff @@ -0,0 +1,8 @@ +< +--- +> +> +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff new file mode 100644 index 000000000..51f0812c9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff @@ -0,0 +1,13 @@ +< +--- +> +> +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff new file mode 100644 index 000000000..8f7ad98de --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff @@ -0,0 +1,9 @@ +< +--- +> +> +> +> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt index 853dbaa86..457239f9c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt @@ -1,2 +1,3 @@ Quick assistant in sample1a.xml for