diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java new file mode 100644 index 000000000..4eff2cde5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java @@ -0,0 +1,246 @@ +/* + * 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.SdkConstants.ANDROID_URI; +import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; +import static com.android.SdkConstants.EXT_XML; + +import com.android.annotations.NonNull; +import com.android.annotations.VisibleForTesting; +import com.android.ide.common.xml.XmlFormatStyle; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +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.MultiTextEdit; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Removes the layout surrounding the current selection (or if the current selection has + * children, removes the current layout), and migrates namespace and layout attributes. + */ +@SuppressWarnings("restriction") // XML model +public class UnwrapRefactoring extends VisualRefactoring { + private Element mContainer; + + /** + * This constructor is solely used by {@link Descriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + UnwrapRefactoring(Map<String, String> arguments) { + super(arguments); + } + + public UnwrapRefactoring( + IFile file, + LayoutEditorDelegate delegate, + ITextSelection selection, + ITreeSelection treeSelection) { + super(file, delegate, selection, treeSelection); + } + + @VisibleForTesting + UnwrapRefactoring(List<Element> selectedElements, LayoutEditorDelegate 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 wrap"); + return status; + } + + // Make sure that the selection all has the same parent? + if (mElements.size() == 0) { + status.addFatalError("Nothing to unwrap"); + return status; + } + + Element first = mElements.get(0); + + // Determine the element of the container to be removed. + // If you've selected a non-container, or you've selected multiple + // elements, then it's the parent which should be removed. Otherwise, + // it's the selection itself which represents the container. + boolean useParent = mElements.size() > 1; + if (!useParent) { + if (DomUtilities.getChildren(first).size() == 0) { + useParent = true; + } + } + Node parent = first.getParentNode(); + if (parent instanceof Document) { + mContainer = first; + List<Element> elements = DomUtilities.getChildren(mContainer); + if (elements.size() == 0) { + status.addFatalError( + "Cannot remove container when it has no children"); + return status; + } + } else if (useParent && (parent instanceof Element)) { + mContainer = (Element) parent; + } else { + mContainer = first; + } + + for (Element element : mElements) { + if (element.getParentNode() != parent) { + status.addFatalError( + "All unwrapped elements must share the same parent element"); + return status; + } + } + + // Ensure that if we are removing the root, that it has only one child + // such that there is a new single root + if (mContainer.getParentNode() instanceof Document) { + if (DomUtilities.getChildren(mContainer).size() > 1) { + status.addFatalError( + "Cannot remove root: it has more than one child " + + "which would result in multiple new roots"); + 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 + public String getName() { + return "Remove Container"; + } + + @Override + protected @NonNull List<Change> computeChanges(IProgressMonitor monitor) { + // (1) If the removed parent is the root container, transfer its + // namespace declarations + // (2) Remove the root element completely + // (3) Transfer layout attributes? + // (4) Check for Java R.file usages? + + IFile file = mDelegate.getEditor().getInputFile(); + List<Change> changes = new ArrayList<Change>(); + if (file == null) { + return changes; + } + MultiTextEdit rootEdit = new MultiTextEdit(); + + // Transfer namespace elements? + if (mContainer.getParentNode() instanceof Document) { + List<Element> elements = DomUtilities.getChildren(mContainer); + assert elements.size() == 1; + Element newRoot = elements.get(0); + + List<Attr> declarations = findNamespaceAttributes(mContainer); + for (Attr attribute : declarations) { + if (attribute instanceof IndexedRegion) { + setAttribute(rootEdit, newRoot, attribute.getNamespaceURI(), + attribute.getPrefix(), attribute.getLocalName(), attribute.getValue()); + } + } + } + + // Transfer layout_ attributes (other than width and height) + List<Element> children = DomUtilities.getChildren(mContainer); + if (children.size() == 1) { + List<Attr> layoutAttributes = findLayoutAttributes(mContainer); + for (Attr attribute : layoutAttributes) { + String name = attribute.getLocalName(); + if ((name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT)) + && ANDROID_URI.equals(attribute.getNamespaceURI())) { + // Already handled specially + continue; + } + } + } + + // Remove the root + removeElementTags(rootEdit, mContainer, Collections.<Element>emptyList() /* skip */, + false /*changeIndentation*/); + + MultiTextEdit formatted = reformat(rootEdit, XmlFormatStyle.LAYOUT); + if (formatted != null) { + rootEdit = formatted; + } + + TextFileChange change = new TextFileChange(file.getName(), file); + change.setEdit(rootEdit); + change.setTextType(EXT_XML); + changes.add(change); + return changes; + } + + @Override + public VisualRefactoringWizard createWizard() { + return new UnwrapWizard(this, mDelegate); + } + + public static class Descriptor extends VisualRefactoringDescriptor { + public Descriptor(String project, String description, String comment, + Map<String, String> arguments) { + super("com.android.ide.eclipse.adt.refactoring.unwrap", //$NON-NLS-1$ + project, description, comment, arguments); + } + + @Override + protected Refactoring createRefactoring(Map<String, String> args) { + return new UnwrapRefactoring(args); + } + } +} |