aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java670
1 files changed, 670 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java
new file mode 100644
index 000000000..f58ac5501
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java
@@ -0,0 +1,670 @@
+/*
+ * 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_NS_NAME;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.EXT_XML;
+import static com.android.SdkConstants.FD_RES;
+import static com.android.SdkConstants.FD_RESOURCES;
+import static com.android.SdkConstants.FD_RES_LAYOUT;
+import static com.android.SdkConstants.ID_PREFIX;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
+import static com.android.SdkConstants.VIEW_INCLUDE;
+import static com.android.SdkConstants.XMLNS;
+import static com.android.SdkConstants.XMLNS_PREFIX;
+import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
+import static com.android.resources.ResourceType.LAYOUT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
+import com.android.utils.XmlUtils;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.dialogs.IInputValidator;
+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.NullChange;
+import org.eclipse.ltk.core.refactoring.Refactoring;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.text.edits.InsertEdit;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.ui.IWorkbenchPage;
+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.Document;
+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.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * 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.
+ */
+@SuppressWarnings("restriction") // XML model
+public class ExtractIncludeRefactoring extends VisualRefactoring {
+ private static final String KEY_NAME = "name"; //$NON-NLS-1$
+ private static final String KEY_OCCURRENCES = "all-occurrences"; //$NON-NLS-1$
+ private String mLayoutName;
+ private boolean mReplaceOccurrences;
+
+ /**
+ * This constructor is solely used by {@link Descriptor},
+ * to replay a previous refactoring.
+ * @param arguments argument map created by #createArgumentMap.
+ */
+ ExtractIncludeRefactoring(Map<String, String> arguments) {
+ super(arguments);
+ mLayoutName = arguments.get(KEY_NAME);
+ mReplaceOccurrences = Boolean.parseBoolean(arguments.get(KEY_OCCURRENCES));
+ }
+
+ public ExtractIncludeRefactoring(
+ IFile file,
+ LayoutEditorDelegate delegate,
+ ITextSelection selection,
+ ITreeSelection treeSelection) {
+ super(file, delegate, selection, treeSelection);
+ }
+
+ @VisibleForTesting
+ ExtractIncludeRefactoring(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 extract");
+ return status;
+ }
+
+ // Make sure the selection is contiguous
+ if (mTreeSelection != null) {
+ // TODO - don't do this if we based the selection on text. In this case,
+ // make sure we're -balanced-.
+ List<CanvasViewInfo> infos = getSelectedViewInfos();
+ if (!validateNotEmpty(infos, status)) {
+ return status;
+ }
+
+ if (!validateNotRoot(infos, status)) {
+ return status;
+ }
+
+ // Disable if you've selected a single include tag
+ if (infos.size() == 1) {
+ UiViewElementNode uiNode = infos.get(0).getUiViewNode();
+ if (uiNode != null) {
+ Node xmlNode = uiNode.getXmlNode();
+ if (xmlNode.getLocalName().equals(VIEW_INCLUDE)) {
+ status.addWarning("No point in refactoring a single include tag");
+ }
+ }
+ }
+
+ // Enforce that the selection is -contiguous-
+ if (!validateContiguous(infos, status)) {
+ 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<String, String> createArgumentMap() {
+ Map<String, String> args = super.createArgumentMap();
+ args.put(KEY_NAME, mLayoutName);
+ args.put(KEY_OCCURRENCES, Boolean.toString(mReplaceOccurrences));
+
+ return args;
+ }
+
+ @Override
+ public String getName() {
+ return "Extract as Include";
+ }
+
+ void setLayoutName(String layoutName) {
+ mLayoutName = layoutName;
+ }
+
+ void setReplaceOccurrences(boolean selection) {
+ mReplaceOccurrences = selection;
+ }
+
+ // ---- Actual implementation of Extract as Include modification computation ----
+
+ @Override
+ protected @NonNull List<Change> computeChanges(IProgressMonitor monitor) {
+ String extractedText = getExtractedText();
+
+ String namespaceDeclarations = computeNamespaceDeclarations();
+
+ // Insert namespace:
+ extractedText = insertNamespace(extractedText, namespaceDeclarations);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); //$NON-NLS-1$
+ sb.append(extractedText);
+ sb.append('\n');
+
+ List<Change> changes = new ArrayList<Change>();
+
+ String newFileName = mLayoutName + DOT_XML;
+ IProject project = mDelegate.getEditor().getProject();
+ IFile sourceFile = mDelegate.getEditor().getInputFile();
+ if (sourceFile == null) {
+ return changes;
+ }
+
+ // Replace extracted elements by <include> tag
+ handleIncludingFile(changes, sourceFile, mSelectionStart, mSelectionEnd,
+ getDomDocument(), getPrimaryElement());
+
+ // Also extract in other variations of the same file (landscape/portrait, etc)
+ boolean haveVariations = false;
+ if (mReplaceOccurrences) {
+ List<IFile> layouts = getOtherLayouts(sourceFile);
+ for (IFile file : layouts) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ IStructuredModel model = null;
+ // We could enhance this with a SubMonitor to make the progress bar move as
+ // well.
+ monitor.subTask(String.format("Looking for duplicates in %1$s",
+ file.getProjectRelativePath()));
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+
+ try {
+ model = modelManager.getModelForRead(file);
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ IDOMDocument otherDocument = domModel.getDocument();
+ List<Element> otherElements = new ArrayList<Element>();
+ Element otherPrimary = null;
+
+ for (Element element : getElements()) {
+ Element other = DomUtilities.findCorresponding(element,
+ otherDocument);
+ if (other != null) {
+ // See if the structure is similar to what we have in this
+ // document
+ if (DomUtilities.isEquivalent(element, other)) {
+ otherElements.add(other);
+ if (element == getPrimaryElement()) {
+ otherPrimary = other;
+ }
+ }
+ }
+ }
+
+ // Only perform extract in the other file if we find a match for
+ // ALL of elements being extracted, and if they too are contiguous
+ if (otherElements.size() == getElements().size() &&
+ DomUtilities.isContiguous(otherElements)) {
+ // Find the range
+ int begin = Integer.MAX_VALUE;
+ int end = Integer.MIN_VALUE;
+ for (Element element : otherElements) {
+ // Yes!! Extract this one as well!
+ IndexedRegion region = getRegion(element);
+ end = Math.max(end, region.getEndOffset());
+ begin = Math.min(begin, region.getStartOffset());
+ }
+ handleIncludingFile(changes, file, begin,
+ end, otherDocument, otherPrimary);
+ haveVariations = true;
+ }
+ }
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ if (model != null) {
+ model.releaseFromRead();
+ }
+ }
+ }
+ }
+
+ // Add change to create the new file
+ IContainer parent = sourceFile.getParent();
+ if (haveVariations) {
+ // If we're extracting from multiple configuration folders, then we need to
+ // place the extracted include in the base layout folder (if not it goes next to
+ // the including file)
+ parent = mProject.getFolder(FD_RES).getFolder(FD_RES_LAYOUT);
+ }
+ IPath parentPath = parent.getProjectRelativePath();
+ final IFile file = project.getFile(new Path(parentPath + WS_SEP + newFileName));
+ TextFileChange addFile = new TextFileChange("Create new separate layout", file);
+ addFile.setTextType(EXT_XML);
+ changes.add(addFile);
+
+ String newFile = sb.toString();
+ if (AdtPrefs.getPrefs().getFormatGuiXml()) {
+ newFile = EclipseXmlPrettyPrinter.prettyPrint(newFile,
+ EclipseXmlFormatPreferences.create(), XmlFormatStyle.LAYOUT,
+ null /*lineSeparator*/);
+ }
+ addFile.setEdit(new InsertEdit(0, newFile));
+
+ Change finishHook = createFinishHook(file);
+ changes.add(finishHook);
+
+ return changes;
+ }
+
+ private void handleIncludingFile(List<Change> changes,
+ IFile sourceFile, int begin, int end, Document document, Element primary) {
+ TextFileChange change = new TextFileChange(sourceFile.getName(), sourceFile);
+ MultiTextEdit rootEdit = new MultiTextEdit();
+ change.setTextType(EXT_XML);
+ changes.add(change);
+
+ String referenceId = getReferenceId();
+ // Replace existing elements in the source file and insert <include>
+ String androidNsPrefix = getAndroidNamespacePrefix(document);
+ String include = computeIncludeString(primary, mLayoutName, androidNsPrefix, referenceId);
+ int length = end - begin;
+ ReplaceEdit replace = new ReplaceEdit(begin, length, include);
+ rootEdit.addChild(replace);
+
+ // Update any layout references to the old id with the new id
+ if (referenceId != null && primary != null) {
+ String rootId = getId(primary);
+ IStructuredModel model = null;
+ try {
+ model = StructuredModelManager.getModelManager().getModelForRead(sourceFile);
+ IStructuredDocument doc = model.getStructuredDocument();
+ if (doc != null && rootId != null) {
+ List<TextEdit> replaceIds = replaceIds(androidNsPrefix, doc, begin,
+ end, rootId, referenceId);
+ for (TextEdit edit : replaceIds) {
+ rootEdit.addChild(edit);
+ }
+
+ if (AdtPrefs.getPrefs().getFormatGuiXml()) {
+ MultiTextEdit formatted = reformat(doc.get(), rootEdit,
+ XmlFormatStyle.LAYOUT);
+ if (formatted != null) {
+ rootEdit = formatted;
+ }
+ }
+ }
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ if (model != null) {
+ model.releaseFromRead();
+ }
+ }
+ }
+
+ change.setEdit(rootEdit);
+ }
+
+ /**
+ * Returns a list of all the other layouts (in all configurations) in the project other
+ * than the given source layout where the refactoring was initiated. Never null.
+ */
+ private List<IFile> getOtherLayouts(IFile sourceFile) {
+ List<IFile> layouts = new ArrayList<IFile>(100);
+ IPath sourcePath = sourceFile.getProjectRelativePath();
+ IFolder resources = mProject.getFolder(FD_RESOURCES);
+ try {
+ for (IResource folder : resources.members()) {
+ if (folder.getName().startsWith(FD_RES_LAYOUT) &&
+ folder instanceof IFolder) {
+ IFolder layoutFolder = (IFolder) folder;
+ for (IResource file : layoutFolder.members()) {
+ if (file.getName().endsWith(EXT_XML)
+ && file instanceof IFile) {
+ if (!file.getProjectRelativePath().equals(sourcePath)) {
+ layouts.add((IFile) file);
+ }
+ }
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return layouts;
+ }
+
+ String getInitialName() {
+ String defaultName = ""; //$NON-NLS-1$
+ Element primary = getPrimaryElement();
+ if (primary != null) {
+ String id = primary.getAttributeNS(ANDROID_URI, ATTR_ID);
+ // id null check for https://bugs.eclipse.org/bugs/show_bug.cgi?id=272378
+ if (id != null && (id.startsWith(ID_PREFIX) || id.startsWith(NEW_ID_PREFIX))) {
+ // Use everything following the id/, and make it lowercase since that is
+ // the convention for layouts (and use Locale.US to ensure that "Image" becomes
+ // "image" etc)
+ defaultName = id.substring(id.indexOf('/') + 1).toLowerCase(Locale.US);
+
+ IInputValidator validator = ResourceNameValidator.create(true, mProject, LAYOUT);
+
+ if (validator.isValid(defaultName) != null) { // Already exists?
+ defaultName = ""; //$NON-NLS-1$
+ }
+ }
+ }
+
+ return defaultName;
+ }
+
+ IFile getSourceFile() {
+ return mFile;
+ }
+
+ private Change createFinishHook(final IFile file) {
+ return new NullChange("Open extracted layout and refresh resources") {
+ @Override
+ public Change perform(IProgressMonitor pm) throws CoreException {
+ Display display = AdtPlugin.getDisplay();
+ display.asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ openFile(file);
+ mDelegate.getGraphicalEditor().refreshProjectResources();
+ // Save file to trigger include finder scanning (as well as making
+ // the
+ // actual show-include feature work since it relies on reading
+ // files from
+ // disk, not a live buffer)
+ IWorkbenchPage page = mDelegate.getEditor().getEditorSite().getPage();
+ page.saveEditor(mDelegate.getEditor(), false);
+ }
+ });
+
+ // Not undoable: just return null instead of an undo-change.
+ return null;
+ }
+ };
+ }
+
+ private String computeNamespaceDeclarations() {
+ String androidNsPrefix = null;
+ String namespaceDeclarations = null;
+
+ StringBuilder sb = new StringBuilder();
+ List<Attr> attributeNodes = findNamespaceAttributes();
+ for (Node attributeNode : attributeNodes) {
+ String prefix = attributeNode.getPrefix();
+ if (XMLNS.equals(prefix)) {
+ sb.append(' ');
+ String name = attributeNode.getNodeName();
+ sb.append(name);
+ sb.append('=').append('"');
+
+ String value = attributeNode.getNodeValue();
+ if (value.equals(ANDROID_URI)) {
+ androidNsPrefix = name;
+ if (androidNsPrefix.startsWith(XMLNS_PREFIX)) {
+ androidNsPrefix = androidNsPrefix.substring(XMLNS_PREFIX.length());
+ }
+ }
+ sb.append(XmlUtils.toXmlAttributeValue(value));
+ sb.append('"');
+ }
+ }
+ namespaceDeclarations = sb.toString();
+
+ if (androidNsPrefix == null) {
+ androidNsPrefix = ANDROID_NS_NAME;
+ }
+
+ if (namespaceDeclarations.length() == 0) {
+ sb.setLength(0);
+ sb.append(' ');
+ sb.append(XMLNS_PREFIX);
+ sb.append(androidNsPrefix);
+ sb.append('=').append('"');
+ sb.append(ANDROID_URI);
+ sb.append('"');
+ namespaceDeclarations = sb.toString();
+ }
+
+ return namespaceDeclarations;
+ }
+
+ /** Returns the id to be used for the include tag itself (may be null) */
+ private String getReferenceId() {
+ String rootId = getRootId();
+ if (rootId != null) {
+ return rootId + "_ref";
+ }
+
+ return null;
+ }
+
+ /**
+ * Compute the actual {@code <include>} string to be inserted in place of the old
+ * selection
+ */
+ private static String computeIncludeString(Element primaryNode, String newName,
+ String androidNsPrefix, String referenceId) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<include layout=\"@layout/"); //$NON-NLS-1$
+ sb.append(newName);
+ sb.append('"');
+ sb.append(' ');
+
+ // Create new id for the include itself
+ if (referenceId != null) {
+ sb.append(androidNsPrefix);
+ sb.append(':');
+ sb.append(ATTR_ID);
+ sb.append('=').append('"');
+ sb.append(referenceId);
+ sb.append('"').append(' ');
+ }
+
+ // Add id string, unless it's a <merge>, since we may need to adjust any layout
+ // references to apply to the <include> tag instead
+
+ // I should move all the layout_ attributes as well
+ // I also need to duplicate and modify the id and then replace
+ // everything else in the file with this new id...
+
+ // HACK: see issue 13494: We must duplicate the width/height attributes on the
+ // <include> statement for designtime rendering only
+ String width = null;
+ String height = null;
+ if (primaryNode == null) {
+ // Multiple selection - in that case we will be creating an outer <merge>
+ // so we need to set our own width/height on it
+ width = height = VALUE_WRAP_CONTENT;
+ } else {
+ if (!primaryNode.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH)) {
+ width = VALUE_WRAP_CONTENT;
+ } else {
+ width = primaryNode.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
+ }
+ if (!primaryNode.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT)) {
+ height = VALUE_WRAP_CONTENT;
+ } else {
+ height = primaryNode.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT);
+ }
+ }
+ if (width != null) {
+ sb.append(' ');
+ sb.append(androidNsPrefix);
+ sb.append(':');
+ sb.append(ATTR_LAYOUT_WIDTH);
+ sb.append('=').append('"');
+ sb.append(XmlUtils.toXmlAttributeValue(width));
+ sb.append('"');
+ }
+ if (height != null) {
+ sb.append(' ');
+ sb.append(androidNsPrefix);
+ sb.append(':');
+ sb.append(ATTR_LAYOUT_HEIGHT);
+ sb.append('=').append('"');
+ sb.append(XmlUtils.toXmlAttributeValue(height));
+ sb.append('"');
+ }
+
+ // Duplicate all the other layout attributes as well
+ if (primaryNode != null) {
+ NamedNodeMap attributes = primaryNode.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Node attr = attributes.item(i);
+ String name = attr.getLocalName();
+ if (name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
+ && ANDROID_URI.equals(attr.getNamespaceURI())) {
+ if (name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT)) {
+ // Already handled
+ continue;
+ }
+
+ sb.append(' ');
+ sb.append(androidNsPrefix);
+ sb.append(':');
+ sb.append(name);
+ sb.append('=').append('"');
+ sb.append(XmlUtils.toXmlAttributeValue(attr.getNodeValue()));
+ sb.append('"');
+ }
+ }
+ }
+
+ sb.append("/>");
+ return sb.toString();
+ }
+
+ /** Return the text in the document in the range start to end */
+ private String getExtractedText() {
+ String xml = getText(mSelectionStart, mSelectionEnd);
+ Element primaryNode = getPrimaryElement();
+ xml = stripTopLayoutAttributes(primaryNode, mSelectionStart, xml);
+ xml = dedent(xml);
+
+ // Wrap siblings in <merge>?
+ if (primaryNode == null) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<merge>\n"); //$NON-NLS-1$
+ // indent an extra level
+ for (String line : xml.split("\n")) { //$NON-NLS-1$
+ sb.append(" "); //$NON-NLS-1$
+ sb.append(line).append('\n');
+ }
+ sb.append("</merge>\n"); //$NON-NLS-1$
+ xml = sb.toString();
+ }
+
+ return xml;
+ }
+
+ @Override
+ VisualRefactoringWizard createWizard() {
+ return new ExtractIncludeWizard(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.extract.include", //$NON-NLS-1$
+ project, description, comment, arguments);
+ }
+
+ @Override
+ protected Refactoring createRefactoring(Map<String, String> args) {
+ return new ExtractIncludeRefactoring(args);
+ }
+ }
+}