/* * 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.eclipse.adt.internal.editors.formatting.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); } } }