| /* |
| * 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); |
| } |
| } |
| } |