blob: 4eff2cde53838ba058582ca727fe2daad7bed0b9 [file] [log] [blame]
/*
* 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);
}
}
}