diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java | 1403 |
1 files changed, 0 insertions, 1403 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java deleted file mode 100644 index 904a3a084..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java +++ /dev/null @@ -1,1403 +0,0 @@ -/* - * 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.ANDROID_WIDGET_PREFIX; -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.ID_PREFIX; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.SdkConstants.XMLNS; -import static com.android.SdkConstants.XMLNS_PREFIX; - -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.AndroidXmlEditor; -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.configuration.ConfigurationDescription; -import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; -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.gle2.GraphicalEditorPart; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; -import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; -import com.android.utils.Pair; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.ResourcesPlugin; -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.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ITextSelection; -import org.eclipse.jface.viewers.ITreeSelection; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.ltk.core.refactoring.Change; -import org.eclipse.ltk.core.refactoring.ChangeDescriptor; -import org.eclipse.ltk.core.refactoring.CompositeChange; -import org.eclipse.ltk.core.refactoring.Refactoring; -import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor; -import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; -import org.eclipse.ltk.core.refactoring.RefactoringStatus; -import org.eclipse.text.edits.DeleteEdit; -import org.eclipse.text.edits.InsertEdit; -import org.eclipse.text.edits.MalformedTreeException; -import org.eclipse.text.edits.MultiTextEdit; -import org.eclipse.text.edits.ReplaceEdit; -import org.eclipse.text.edits.TextEdit; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.ide.IDE; -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.sse.core.internal.provisional.text.IStructuredDocumentRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; -import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; -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.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -/** - * Parent class for the various visual refactoring operations; contains shared - * implementations needed by most of them - */ -@SuppressWarnings("restriction") // XML model -public abstract class VisualRefactoring extends Refactoring { - private static final String KEY_FILE = "file"; //$NON-NLS-1$ - private static final String KEY_PROJECT = "proj"; //$NON-NLS-1$ - private static final String KEY_SEL_START = "sel-start"; //$NON-NLS-1$ - private static final String KEY_SEL_END = "sel-end"; //$NON-NLS-1$ - - protected final IFile mFile; - protected final LayoutEditorDelegate mDelegate; - protected final IProject mProject; - protected int mSelectionStart = -1; - protected int mSelectionEnd = -1; - protected final List<Element> mElements; - protected final ITreeSelection mTreeSelection; - protected final ITextSelection mSelection; - /** Same as {@link #mSelectionStart} but not adjusted to element edges */ - protected int mOriginalSelectionStart = -1; - /** Same as {@link #mSelectionEnd} but not adjusted to element edges */ - protected int mOriginalSelectionEnd = -1; - - protected final Map<Element, String> mGeneratedIdMap = new HashMap<Element, String>(); - protected final Set<String> mGeneratedIds = new HashSet<String>(); - - protected List<Change> mChanges; - private String mAndroidNamespacePrefix; - - /** - * This constructor is solely used by {@link VisualRefactoringDescriptor}, - * to replay a previous refactoring. - * @param arguments argument map created by #createArgumentMap. - */ - VisualRefactoring(Map<String, String> arguments) { - IPath path = Path.fromPortableString(arguments.get(KEY_PROJECT)); - mProject = (IProject) ResourcesPlugin.getWorkspace().getRoot().findMember(path); - path = Path.fromPortableString(arguments.get(KEY_FILE)); - mFile = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(path); - mSelectionStart = Integer.parseInt(arguments.get(KEY_SEL_START)); - mSelectionEnd = Integer.parseInt(arguments.get(KEY_SEL_END)); - mOriginalSelectionStart = mSelectionStart; - mOriginalSelectionEnd = mSelectionEnd; - mDelegate = null; - mElements = null; - mSelection = null; - mTreeSelection = null; - } - - @VisibleForTesting - VisualRefactoring(List<Element> elements, LayoutEditorDelegate delegate) { - mElements = elements; - mDelegate = delegate; - - mFile = delegate != null ? delegate.getEditor().getInputFile() : null; - mProject = delegate != null ? delegate.getEditor().getProject() : null; - mSelectionStart = 0; - mSelectionEnd = 0; - mOriginalSelectionStart = 0; - mOriginalSelectionEnd = 0; - mSelection = null; - mTreeSelection = null; - - int end = Integer.MIN_VALUE; - int start = Integer.MAX_VALUE; - for (Element element : elements) { - if (element instanceof IndexedRegion) { - IndexedRegion region = (IndexedRegion) element; - start = Math.min(start, region.getStartOffset()); - end = Math.max(end, region.getEndOffset()); - } - } - if (start >= 0) { - mSelectionStart = start; - mSelectionEnd = end; - mOriginalSelectionStart = start; - mOriginalSelectionEnd = end; - } - } - - public VisualRefactoring(IFile file, LayoutEditorDelegate editor, ITextSelection selection, - ITreeSelection treeSelection) { - mFile = file; - mDelegate = editor; - mProject = file.getProject(); - mSelection = selection; - mTreeSelection = treeSelection; - - // Initialize mSelectionStart and mSelectionEnd based on the selection context, which - // is either a treeSelection (when invoked from the layout editor or the outline), or - // a selection (when invoked from an XML editor) - if (treeSelection != null) { - int end = Integer.MIN_VALUE; - int start = Integer.MAX_VALUE; - for (TreePath path : treeSelection.getPaths()) { - Object lastSegment = path.getLastSegment(); - if (lastSegment instanceof CanvasViewInfo) { - CanvasViewInfo viewInfo = (CanvasViewInfo) lastSegment; - UiViewElementNode uiNode = viewInfo.getUiViewNode(); - if (uiNode == null) { - continue; - } - Node xmlNode = uiNode.getXmlNode(); - if (xmlNode instanceof IndexedRegion) { - IndexedRegion region = (IndexedRegion) xmlNode; - - start = Math.min(start, region.getStartOffset()); - end = Math.max(end, region.getEndOffset()); - } - } - } - if (start >= 0) { - mSelectionStart = start; - mSelectionEnd = end; - mOriginalSelectionStart = mSelectionStart; - mOriginalSelectionEnd = mSelectionEnd; - } - if (selection != null) { - mOriginalSelectionStart = selection.getOffset(); - mOriginalSelectionEnd = mOriginalSelectionStart + selection.getLength(); - } - } else if (selection != null) { - // TODO: update selection to boundaries! - mSelectionStart = selection.getOffset(); - mSelectionEnd = mSelectionStart + selection.getLength(); - mOriginalSelectionStart = mSelectionStart; - mOriginalSelectionEnd = mSelectionEnd; - } - - mElements = initElements(); - } - - @NonNull - protected abstract List<Change> computeChanges(IProgressMonitor monitor); - - @Override - public RefactoringStatus checkFinalConditions(IProgressMonitor monitor) throws CoreException, - OperationCanceledException { - RefactoringStatus status = new RefactoringStatus(); - mChanges = new ArrayList<Change>(); - try { - monitor.beginTask("Checking post-conditions...", 5); - - // Reset state for each computeChanges call, in case the user goes back - // and forth in the refactoring wizard - mGeneratedIdMap.clear(); - mGeneratedIds.clear(); - List<Change> changes = computeChanges(monitor); - mChanges.addAll(changes); - - monitor.worked(1); - } finally { - monitor.done(); - } - - return status; - } - - @Override - public Change createChange(IProgressMonitor monitor) throws CoreException, - OperationCanceledException { - try { - monitor.beginTask("Applying changes...", 1); - - CompositeChange change = new CompositeChange( - getName(), - mChanges.toArray(new Change[mChanges.size()])) { - @Override - public ChangeDescriptor getDescriptor() { - VisualRefactoringDescriptor desc = createDescriptor(); - return new RefactoringChangeDescriptor(desc); - } - }; - - monitor.worked(1); - return change; - - } finally { - monitor.done(); - } - } - - protected abstract VisualRefactoringDescriptor createDescriptor(); - - protected Map<String, String> createArgumentMap() { - HashMap<String, String> args = new HashMap<String, String>(); - args.put(KEY_PROJECT, mProject.getFullPath().toPortableString()); - args.put(KEY_FILE, mFile.getFullPath().toPortableString()); - args.put(KEY_SEL_START, Integer.toString(mSelectionStart)); - args.put(KEY_SEL_END, Integer.toString(mSelectionEnd)); - - return args; - } - - IFile getFile() { - return mFile; - } - - // ---- Shared functionality ---- - - - protected void openFile(IFile file) { - GraphicalEditorPart graphicalEditor = mDelegate.getGraphicalEditor(); - IFile leavingFile = graphicalEditor.getEditedFile(); - - try { - // Duplicate the current state into the newly created file - String state = ConfigurationDescription.getDescription(leavingFile); - - // TODO: Look for a ".NoTitleBar.Fullscreen" theme version of the current - // theme to show. - - file.setSessionProperty(GraphicalEditorPart.NAME_INITIAL_STATE, state); - } catch (CoreException e) { - // pass - } - - /* TBD: "Show Included In" if supported. - * Not sure if this is a good idea. - if (graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) { - try { - Reference include = Reference.create(graphicalEditor.getEditedFile()); - file.setSessionProperty(GraphicalEditorPart.NAME_INCLUDE, include); - } catch (CoreException e) { - // pass - worst that can happen is that we don't start with inclusion - } - } - */ - - try { - IEditorPart part = - IDE.openEditor(mDelegate.getEditor().getEditorSite().getPage(), file); - if (part instanceof AndroidXmlEditor && AdtPrefs.getPrefs().getFormatGuiXml()) { - AndroidXmlEditor newEditor = (AndroidXmlEditor) part; - newEditor.reformatDocument(); - } - } catch (PartInitException e) { - AdtPlugin.log(e, "Can't open new included layout"); - } - } - - - /** Produce a list of edits to replace references to the given id with the given new id */ - protected static List<TextEdit> replaceIds(String androidNamePrefix, - IStructuredDocument doc, int skipStart, int skipEnd, - String rootId, String referenceId) { - if (rootId == null) { - return Collections.emptyList(); - } - - // We need to search for either @+id/ or @id/ - String match1 = rootId; - String match2; - if (match1.startsWith(ID_PREFIX)) { - match2 = '"' + NEW_ID_PREFIX + match1.substring(ID_PREFIX.length()) + '"'; - match1 = '"' + match1 + '"'; - } else if (match1.startsWith(NEW_ID_PREFIX)) { - match2 = '"' + ID_PREFIX + match1.substring(NEW_ID_PREFIX.length()) + '"'; - match1 = '"' + match1 + '"'; - } else { - return Collections.emptyList(); - } - - String namePrefix = androidNamePrefix + ':' + ATTR_LAYOUT_RESOURCE_PREFIX; - List<TextEdit> edits = new ArrayList<TextEdit>(); - - IStructuredDocumentRegion region = doc.getFirstStructuredDocumentRegion(); - for (; region != null; region = region.getNext()) { - ITextRegionList list = region.getRegions(); - int regionStart = region.getStart(); - - // Look at all attribute values and look for an id reference match - String attributeName = ""; //$NON-NLS-1$ - for (int j = 0; j < region.getNumberOfRegions(); j++) { - ITextRegion subRegion = list.get(j); - String type = subRegion.getType(); - if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) { - attributeName = region.getText(subRegion); - } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) { - // Only replace references in layout attributes - if (!attributeName.startsWith(namePrefix)) { - continue; - } - // Skip occurrences in the given skip range - int subRegionStart = regionStart + subRegion.getStart(); - if (subRegionStart >= skipStart && subRegionStart <= skipEnd) { - continue; - } - - String attributeValue = region.getText(subRegion); - if (attributeValue.equals(match1) || attributeValue.equals(match2)) { - int start = subRegionStart + 1; // skip quote - int end = start + rootId.length(); - - edits.add(new ReplaceEdit(start, end - start, referenceId)); - } - } - } - } - - return edits; - } - - /** Get the id of the root selected element, if any */ - protected String getRootId() { - Element primary = getPrimaryElement(); - if (primary != null) { - String oldId = primary.getAttributeNS(ANDROID_URI, ATTR_ID); - // id null check for https://bugs.eclipse.org/bugs/show_bug.cgi?id=272378 - if (oldId != null && oldId.length() > 0) { - return oldId; - } - } - - return null; - } - - protected String getAndroidNamespacePrefix() { - if (mAndroidNamespacePrefix == null) { - List<Attr> attributeNodes = findNamespaceAttributes(); - for (Node attributeNode : attributeNodes) { - String prefix = attributeNode.getPrefix(); - if (XMLNS.equals(prefix)) { - String name = attributeNode.getNodeName(); - String value = attributeNode.getNodeValue(); - if (value.equals(ANDROID_URI)) { - mAndroidNamespacePrefix = name; - if (mAndroidNamespacePrefix.startsWith(XMLNS_PREFIX)) { - mAndroidNamespacePrefix = - mAndroidNamespacePrefix.substring(XMLNS_PREFIX.length()); - } - } - } - } - - if (mAndroidNamespacePrefix == null) { - mAndroidNamespacePrefix = ANDROID_NS_NAME; - } - } - - return mAndroidNamespacePrefix; - } - - protected static String getAndroidNamespacePrefix(Document document) { - String nsPrefix = null; - List<Attr> attributeNodes = findNamespaceAttributes(document); - for (Node attributeNode : attributeNodes) { - String prefix = attributeNode.getPrefix(); - if (XMLNS.equals(prefix)) { - String name = attributeNode.getNodeName(); - String value = attributeNode.getNodeValue(); - if (value.equals(ANDROID_URI)) { - nsPrefix = name; - if (nsPrefix.startsWith(XMLNS_PREFIX)) { - nsPrefix = - nsPrefix.substring(XMLNS_PREFIX.length()); - } - } - } - } - - if (nsPrefix == null) { - nsPrefix = ANDROID_NS_NAME; - } - - return nsPrefix; - } - - protected List<Attr> findNamespaceAttributes() { - Document document = getDomDocument(); - return findNamespaceAttributes(document); - } - - protected static List<Attr> findNamespaceAttributes(Document document) { - if (document != null) { - Element root = document.getDocumentElement(); - return findNamespaceAttributes(root); - } - - return Collections.emptyList(); - } - - protected static List<Attr> findNamespaceAttributes(Node root) { - List<Attr> result = new ArrayList<Attr>(); - NamedNodeMap attributes = root.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Node attributeNode = attributes.item(i); - - String prefix = attributeNode.getPrefix(); - if (XMLNS.equals(prefix)) { - result.add((Attr) attributeNode); - } - } - - return result; - } - - protected List<Attr> findLayoutAttributes(Node root) { - List<Attr> result = new ArrayList<Attr>(); - NamedNodeMap attributes = root.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Node attributeNode = attributes.item(i); - - String name = attributeNode.getLocalName(); - if (name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX) - && ANDROID_URI.equals(attributeNode.getNamespaceURI())) { - result.add((Attr) attributeNode); - } - } - - return result; - } - - protected String insertNamespace(String xmlText, String namespaceDeclarations) { - // Insert namespace declarations into the extracted XML fragment - int firstSpace = xmlText.indexOf(' '); - int elementEnd = xmlText.indexOf('>'); - int insertAt; - if (firstSpace != -1 && firstSpace < elementEnd) { - insertAt = firstSpace; - } else { - insertAt = elementEnd; - } - xmlText = xmlText.substring(0, insertAt) + namespaceDeclarations - + xmlText.substring(insertAt); - - return xmlText; - } - - /** Remove sections of the document that correspond to top level layout attributes; - * these are placed on the include element instead */ - protected String stripTopLayoutAttributes(Element primary, int start, String xml) { - if (primary != null) { - // List of attributes to remove - List<IndexedRegion> skip = new ArrayList<IndexedRegion>(); - NamedNodeMap attributes = primary.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)) { - // These are special and are left in - continue; - } - - if (attr instanceof IndexedRegion) { - skip.add((IndexedRegion) attr); - } - } - } - if (skip.size() > 0) { - Collections.sort(skip, new Comparator<IndexedRegion>() { - // Sort in start order - @Override - public int compare(IndexedRegion r1, IndexedRegion r2) { - return r1.getStartOffset() - r2.getStartOffset(); - } - }); - - // Successively cut out the various layout attributes - // TODO remove adjacent whitespace too (but not newlines, unless they - // are newly adjacent) - StringBuilder sb = new StringBuilder(xml.length()); - int nextStart = 0; - - // Copy out all the sections except the skip sections - for (IndexedRegion r : skip) { - int regionStart = r.getStartOffset(); - // Adjust to string offsets since we've copied the string out of - // the document - regionStart -= start; - - sb.append(xml.substring(nextStart, regionStart)); - - nextStart = regionStart + r.getLength(); - } - if (nextStart < xml.length()) { - sb.append(xml.substring(nextStart)); - } - - return sb.toString(); - } - } - - return xml; - } - - protected static String getIndent(String line, int max) { - int i = 0; - int n = Math.min(max, line.length()); - for (; i < n; i++) { - char c = line.charAt(i); - if (!Character.isWhitespace(c)) { - return line.substring(0, i); - } - } - - if (n < line.length()) { - return line.substring(0, n); - } else { - return line; - } - } - - protected static String dedent(String xml) { - String[] lines = xml.split("\n"); //$NON-NLS-1$ - if (lines.length < 2) { - // The first line never has any indentation since we copy it out from the - // element start index - return xml; - } - - String indentPrefix = getIndent(lines[1], lines[1].length()); - for (int i = 2, n = lines.length; i < n; i++) { - String line = lines[i]; - - // Ignore blank lines - if (line.trim().length() == 0) { - continue; - } - - indentPrefix = getIndent(line, indentPrefix.length()); - - if (indentPrefix.length() == 0) { - return xml; - } - } - - StringBuilder sb = new StringBuilder(); - for (String line : lines) { - if (line.startsWith(indentPrefix)) { - sb.append(line.substring(indentPrefix.length())); - } else { - sb.append(line); - } - sb.append('\n'); - } - return sb.toString(); - } - - protected String getText(int start, int end) { - try { - IStructuredDocument document = mDelegate.getEditor().getStructuredDocument(); - return document.get(start, end - start); - } catch (BadLocationException e) { - // the region offset was invalid. ignore. - return null; - } - } - - protected List<Element> getElements() { - return mElements; - } - - protected List<Element> initElements() { - List<Element> nodes = new ArrayList<Element>(); - - assert mTreeSelection == null || mSelection == null : - "treeSel= " + mTreeSelection + ", sel=" + mSelection; - - // Initialize mSelectionStart and mSelectionEnd based on the selection context, which - // is either a treeSelection (when invoked from the layout editor or the outline), or - // a selection (when invoked from an XML editor) - if (mTreeSelection != null) { - int end = Integer.MIN_VALUE; - int start = Integer.MAX_VALUE; - for (TreePath path : mTreeSelection.getPaths()) { - Object lastSegment = path.getLastSegment(); - if (lastSegment instanceof CanvasViewInfo) { - CanvasViewInfo viewInfo = (CanvasViewInfo) lastSegment; - UiViewElementNode uiNode = viewInfo.getUiViewNode(); - if (uiNode == null) { - continue; - } - Node xmlNode = uiNode.getXmlNode(); - if (xmlNode instanceof Element) { - Element element = (Element) xmlNode; - nodes.add(element); - IndexedRegion region = getRegion(element); - start = Math.min(start, region.getStartOffset()); - end = Math.max(end, region.getEndOffset()); - } - } - } - if (start >= 0) { - mSelectionStart = start; - mSelectionEnd = end; - } - } else if (mSelection != null) { - mSelectionStart = mSelection.getOffset(); - mSelectionEnd = mSelectionStart + mSelection.getLength(); - mOriginalSelectionStart = mSelectionStart; - mOriginalSelectionEnd = mSelectionEnd; - - // Figure out the range of selected nodes from the document offsets - IStructuredDocument doc = mDelegate.getEditor().getStructuredDocument(); - Pair<Element, Element> range = DomUtilities.getElementRange(doc, - mSelectionStart, mSelectionEnd); - if (range != null) { - Element first = range.getFirst(); - Element last = range.getSecond(); - - // Adjust offsets to get rid of surrounding text nodes (if you happened - // to select a text range and included whitespace on either end etc) - mSelectionStart = getRegion(first).getStartOffset(); - mSelectionEnd = getRegion(last).getEndOffset(); - - if (mSelectionStart > mSelectionEnd) { - int tmp = mSelectionStart; - mSelectionStart = mSelectionEnd; - mSelectionEnd = tmp; - } - - if (first == last) { - nodes.add(first); - } else if (first.getParentNode() == last.getParentNode()) { - // Add the range - Node node = first; - while (node != null) { - if (node instanceof Element) { - nodes.add((Element) node); - } - if (node == last) { - break; - } - node = node.getNextSibling(); - } - } else { - // Different parents: this means we have an uneven selection, selecting - // elements from different levels. We can't extract ranges like that. - } - } - } else { - assert false; - } - - // Make sure that the list of elements is unique - //Set<Element> seen = new HashSet<Element>(); - //for (Element element : nodes) { - // assert !seen.contains(element) : element; - // seen.add(element); - //} - - return nodes; - } - - protected Element getPrimaryElement() { - List<Element> elements = getElements(); - if (elements != null && elements.size() == 1) { - return elements.get(0); - } - - return null; - } - - protected Document getDomDocument() { - if (mDelegate.getUiRootNode() != null) { - return mDelegate.getUiRootNode().getXmlDocument(); - } else { - return getElements().get(0).getOwnerDocument(); - } - } - - protected List<CanvasViewInfo> getSelectedViewInfos() { - List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(); - if (mTreeSelection != null) { - for (TreePath path : mTreeSelection.getPaths()) { - Object lastSegment = path.getLastSegment(); - if (lastSegment instanceof CanvasViewInfo) { - infos.add((CanvasViewInfo) lastSegment); - } - } - } - return infos; - } - - protected boolean validateNotEmpty(List<CanvasViewInfo> infos, RefactoringStatus status) { - if (infos.size() == 0) { - status.addFatalError("No selection to extract"); - return false; - } - - return true; - } - - protected boolean validateNotRoot(List<CanvasViewInfo> infos, RefactoringStatus status) { - for (CanvasViewInfo info : infos) { - if (info.isRoot()) { - status.addFatalError("Cannot refactor the root"); - return false; - } - } - - return true; - } - - protected boolean validateContiguous(List<CanvasViewInfo> infos, RefactoringStatus status) { - if (infos.size() > 1) { - // All elements must be siblings (e.g. same parent) - List<UiViewElementNode> nodes = new ArrayList<UiViewElementNode>(infos - .size()); - for (CanvasViewInfo info : infos) { - UiViewElementNode node = info.getUiViewNode(); - if (node != null) { - nodes.add(node); - } - } - if (nodes.size() == 0) { - status.addFatalError("No selected views"); - return false; - } - - UiElementNode parent = nodes.get(0).getUiParent(); - for (UiViewElementNode node : nodes) { - if (parent != node.getUiParent()) { - status.addFatalError("The selected elements must be adjacent"); - return false; - } - } - // Ensure that the siblings are contiguous; no gaps. - // If we've selected all the children of the parent then we don't need - // to look. - List<UiElementNode> siblings = parent.getUiChildren(); - if (siblings.size() != nodes.size()) { - Set<UiViewElementNode> nodeSet = new HashSet<UiViewElementNode>(nodes); - boolean inRange = false; - int remaining = nodes.size(); - for (UiElementNode node : siblings) { - boolean in = nodeSet.contains(node); - if (in) { - remaining--; - if (remaining == 0) { - break; - } - inRange = true; - } else if (inRange) { - status.addFatalError("The selected elements must be adjacent"); - return false; - } - } - } - } - - return true; - } - - /** - * Updates the given element with a new name if the current id reflects the old - * element type. If the name was changed, it will return the new name. - */ - protected String ensureIdMatchesType(Element element, String newType, MultiTextEdit rootEdit) { - String oldType = element.getTagName(); - if (oldType.indexOf('.') == -1) { - oldType = ANDROID_WIDGET_PREFIX + oldType; - } - String oldTypeBase = oldType.substring(oldType.lastIndexOf('.') + 1); - String id = getId(element); - if (id == null || id.length() == 0 - || id.toLowerCase(Locale.US).contains(oldTypeBase.toLowerCase(Locale.US))) { - String newTypeBase = newType.substring(newType.lastIndexOf('.') + 1); - return ensureHasId(rootEdit, element, newTypeBase); - } - - return null; - } - - /** - * Returns the {@link IndexedRegion} for the given node - * - * @param node the node to look up the region for - * @return the corresponding region, or null - */ - public static IndexedRegion getRegion(Node node) { - if (node instanceof IndexedRegion) { - return (IndexedRegion) node; - } - - return null; - } - - protected String ensureHasId(MultiTextEdit rootEdit, Element element, String prefix) { - return ensureHasId(rootEdit, element, prefix, true); - } - - protected String ensureHasId(MultiTextEdit rootEdit, Element element, String prefix, - boolean apply) { - String id = mGeneratedIdMap.get(element); - if (id != null) { - return NEW_ID_PREFIX + id; - } - - if (!element.hasAttributeNS(ANDROID_URI, ATTR_ID) - || (prefix != null && !getId(element).startsWith(prefix))) { - id = DomUtilities.getFreeWidgetId(element, mGeneratedIds, prefix); - // Make sure we don't use this one again - mGeneratedIds.add(id); - mGeneratedIdMap.put(element, id); - id = NEW_ID_PREFIX + id; - if (apply) { - setAttribute(rootEdit, element, - ANDROID_URI, getAndroidNamespacePrefix(), ATTR_ID, id); - } - return id; - } - - return getId(element); - } - - protected int getFirstAttributeOffset(Element element) { - IndexedRegion region = getRegion(element); - if (region != null) { - int startOffset = region.getStartOffset(); - int endOffset = region.getEndOffset(); - String text = getText(startOffset, endOffset); - String name = element.getLocalName(); - int nameOffset = text.indexOf(name); - if (nameOffset != -1) { - return startOffset + nameOffset + name.length(); - } - } - - return -1; - } - - /** - * Returns the id of the given element - * - * @param element the element to look up the id for - * @return the corresponding id, or an empty string (should not be null - * according to the DOM API, but has been observed to be null on - * some versions of Eclipse) - */ - public static String getId(Element element) { - return element.getAttributeNS(ANDROID_URI, ATTR_ID); - } - - protected String ensureNewId(String id) { - if (id != null && id.length() > 0) { - if (id.startsWith(ID_PREFIX)) { - id = NEW_ID_PREFIX + id.substring(ID_PREFIX.length()); - } else if (!id.startsWith(NEW_ID_PREFIX)) { - id = NEW_ID_PREFIX + id; - } - } else { - id = null; - } - - return id; - } - - protected String getViewClass(String fqcn) { - // Don't include android.widget. as a package prefix in layout files - if (fqcn.startsWith(ANDROID_WIDGET_PREFIX)) { - fqcn = fqcn.substring(ANDROID_WIDGET_PREFIX.length()); - } - - return fqcn; - } - - protected void setAttribute(MultiTextEdit rootEdit, Element element, - String attributeUri, - String attributePrefix, String attributeName, String attributeValue) { - int offset = getFirstAttributeOffset(element); - if (offset != -1) { - if (element.hasAttributeNS(attributeUri, attributeName)) { - replaceAttributeDeclaration(rootEdit, offset, element, attributePrefix, - attributeUri, attributeName, attributeValue); - } else { - addAttributeDeclaration(rootEdit, offset, attributePrefix, attributeName, - attributeValue); - } - } - } - - private void addAttributeDeclaration(MultiTextEdit rootEdit, int offset, - String attributePrefix, String attributeName, String attributeValue) { - StringBuilder sb = new StringBuilder(); - sb.append(' '); - - if (attributePrefix != null) { - sb.append(attributePrefix).append(':'); - } - sb.append(attributeName).append('=').append('"'); - sb.append(attributeValue).append('"'); - - InsertEdit setAttribute = new InsertEdit(offset, sb.toString()); - rootEdit.addChild(setAttribute); - } - - /** Replaces the value declaration of the given attribute */ - private void replaceAttributeDeclaration(MultiTextEdit rootEdit, int offset, - Element element, String attributePrefix, String attributeUri, - String attributeName, String attributeValue) { - // Find attribute value and replace it - IStructuredModel model = mDelegate.getEditor().getModelForRead(); - try { - IStructuredDocument doc = model.getStructuredDocument(); - - IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset); - ITextRegionList list = region.getRegions(); - int regionStart = region.getStart(); - - int valueStart = -1; - boolean useNextValue = false; - String targetName = attributePrefix != null - ? attributePrefix + ':' + attributeName : attributeName; - - // Look at all attribute values and look for an id reference match - for (int j = 0; j < region.getNumberOfRegions(); j++) { - ITextRegion subRegion = list.get(j); - String type = subRegion.getType(); - if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) { - // What about prefix? - if (targetName.equals(region.getText(subRegion))) { - useNextValue = true; - } - } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) { - if (useNextValue) { - valueStart = regionStart + subRegion.getStart(); - break; - } - } - } - - if (valueStart != -1) { - String oldValue = element.getAttributeNS(attributeUri, attributeName); - int start = valueStart + 1; // Skip opening " - ReplaceEdit setAttribute = new ReplaceEdit(start, oldValue.length(), - attributeValue); - try { - rootEdit.addChild(setAttribute); - } catch (MalformedTreeException mte) { - AdtPlugin.log(mte, "Could not replace attribute %1$s with %2$s", - attributeName, attributeValue); - throw mte; - } - } - } finally { - model.releaseFromRead(); - } - } - - /** Strips out the given attribute, if defined */ - protected void removeAttribute(MultiTextEdit rootEdit, Element element, String uri, - String attributeName) { - if (element.hasAttributeNS(uri, attributeName)) { - Attr attribute = element.getAttributeNodeNS(uri, attributeName); - removeAttribute(rootEdit, attribute); - } - } - - /** Strips out the given attribute, if defined */ - protected void removeAttribute(MultiTextEdit rootEdit, Attr attribute) { - IndexedRegion region = getRegion(attribute); - if (region != null) { - int startOffset = region.getStartOffset(); - int endOffset = region.getEndOffset(); - DeleteEdit deletion = new DeleteEdit(startOffset, endOffset - startOffset); - rootEdit.addChild(deletion); - } - } - - - /** - * Removes the given element's opening and closing tags (including all of its - * attributes) but leaves any children alone - * - * @param rootEdit the multi edit to add the removal operation to - * @param element the element to delete the open and closing tags for - * @param skip a list of elements that should not be modified (for example because they - * are targeted for deletion) - * - * TODO: Rename this to "unwrap" ? And allow for handling nested deletions. - */ - protected void removeElementTags(MultiTextEdit rootEdit, Element element, List<Element> skip, - boolean changeIndentation) { - IndexedRegion elementRegion = getRegion(element); - if (elementRegion == null) { - return; - } - - // Look for the opening tag - IStructuredModel model = mDelegate.getEditor().getModelForRead(); - try { - int startLineInclusive = -1; - int endLineInclusive = -1; - IStructuredDocument doc = model.getStructuredDocument(); - if (doc != null) { - int start = elementRegion.getStartOffset(); - IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(start); - ITextRegionList list = region.getRegions(); - int regionStart = region.getStart(); - int startOffset = regionStart; - for (int j = 0; j < region.getNumberOfRegions(); j++) { - ITextRegion subRegion = list.get(j); - String type = subRegion.getType(); - if (DOMRegionContext.XML_TAG_OPEN.equals(type)) { - startOffset = regionStart + subRegion.getStart(); - } else if (DOMRegionContext.XML_TAG_CLOSE.equals(type)) { - int endOffset = regionStart + subRegion.getStart() + subRegion.getLength(); - - DeleteEdit deletion = createDeletion(doc, startOffset, endOffset); - rootEdit.addChild(deletion); - startLineInclusive = doc.getLineOfOffset(endOffset) + 1; - break; - } - } - - // Find the close tag - // Look at all attribute values and look for an id reference match - region = doc.getRegionAtCharacterOffset(elementRegion.getEndOffset() - - element.getTagName().length() - 1); - list = region.getRegions(); - regionStart = region.getStartOffset(); - startOffset = -1; - for (int j = 0; j < region.getNumberOfRegions(); j++) { - ITextRegion subRegion = list.get(j); - String type = subRegion.getType(); - if (DOMRegionContext.XML_END_TAG_OPEN.equals(type)) { - startOffset = regionStart + subRegion.getStart(); - } else if (DOMRegionContext.XML_TAG_CLOSE.equals(type)) { - int endOffset = regionStart + subRegion.getStart() + subRegion.getLength(); - if (startOffset != -1) { - DeleteEdit deletion = createDeletion(doc, startOffset, endOffset); - rootEdit.addChild(deletion); - endLineInclusive = doc.getLineOfOffset(startOffset) - 1; - } - break; - } - } - } - - // Dedent the contents - if (changeIndentation && startLineInclusive != -1 && endLineInclusive != -1) { - String indent = AndroidXmlEditor.getIndentAtOffset(doc, getRegion(element) - .getStartOffset()); - setIndentation(rootEdit, indent, doc, startLineInclusive, endLineInclusive, - element, skip); - } - } finally { - model.releaseFromRead(); - } - } - - protected void removeIndentation(MultiTextEdit rootEdit, String removeIndent, - IStructuredDocument doc, int startLineInclusive, int endLineInclusive, - Element element, List<Element> skip) { - if (startLineInclusive > endLineInclusive) { - return; - } - int indentLength = removeIndent.length(); - if (indentLength == 0) { - return; - } - - try { - for (int line = startLineInclusive; line <= endLineInclusive; line++) { - IRegion info = doc.getLineInformation(line); - int lineStart = info.getOffset(); - int lineLength = info.getLength(); - int lineEnd = lineStart + lineLength; - if (overlaps(lineStart, lineEnd, element, skip)) { - continue; - } - String lineText = getText(lineStart, - lineStart + Math.min(lineLength, indentLength)); - if (lineText.startsWith(removeIndent)) { - rootEdit.addChild(new DeleteEdit(lineStart, indentLength)); - } - } - } catch (BadLocationException e) { - AdtPlugin.log(e, null); - } - } - - protected void setIndentation(MultiTextEdit rootEdit, String indent, - IStructuredDocument doc, int startLineInclusive, int endLineInclusive, - Element element, List<Element> skip) { - if (startLineInclusive > endLineInclusive) { - return; - } - int indentLength = indent.length(); - if (indentLength == 0) { - return; - } - - try { - for (int line = startLineInclusive; line <= endLineInclusive; line++) { - IRegion info = doc.getLineInformation(line); - int lineStart = info.getOffset(); - int lineLength = info.getLength(); - int lineEnd = lineStart + lineLength; - if (overlaps(lineStart, lineEnd, element, skip)) { - continue; - } - String lineText = getText(lineStart, lineStart + lineLength); - int indentEnd = getFirstNonSpace(lineText); - rootEdit.addChild(new ReplaceEdit(lineStart, indentEnd, indent)); - } - } catch (BadLocationException e) { - AdtPlugin.log(e, null); - } - } - - private int getFirstNonSpace(String s) { - for (int i = 0; i < s.length(); i++) { - if (!Character.isWhitespace(s.charAt(i))) { - return i; - } - } - - return s.length(); - } - - /** Returns true if the given line overlaps any of the given elements */ - private static boolean overlaps(int startOffset, int endOffset, - Element element, List<Element> overlaps) { - for (Element e : overlaps) { - if (e == element) { - continue; - } - - IndexedRegion region = getRegion(e); - if (region.getEndOffset() >= startOffset && region.getStartOffset() <= endOffset) { - return true; - } - } - return false; - } - - protected DeleteEdit createDeletion(IStructuredDocument doc, int startOffset, int endOffset) { - // Expand to delete the whole line? - try { - IRegion info = doc.getLineInformationOfOffset(startOffset); - int lineBegin = info.getOffset(); - // Is the text on the line leading up to the deletion region, - // and the text following it, all whitespace? - boolean deleteLine = true; - if (lineBegin < startOffset) { - String prefix = getText(lineBegin, startOffset); - if (prefix.trim().length() > 0) { - deleteLine = false; - } - } - info = doc.getLineInformationOfOffset(endOffset); - int lineEnd = info.getOffset() + info.getLength(); - if (lineEnd > endOffset) { - String suffix = getText(endOffset, lineEnd); - if (suffix.trim().length() > 0) { - deleteLine = false; - } - } - if (deleteLine) { - startOffset = lineBegin; - endOffset = Math.min(doc.getLength(), lineEnd + 1); - } - } catch (BadLocationException e) { - AdtPlugin.log(e, null); - } - - - return new DeleteEdit(startOffset, endOffset - startOffset); - } - - /** - * Rewrite the edits in the given {@link MultiTextEdit} such that same edits are - * applied, but the resulting range is also formatted - */ - protected MultiTextEdit reformat(MultiTextEdit edit, XmlFormatStyle style) { - String xml = mDelegate.getEditor().getStructuredDocument().get(); - return reformat(xml, edit, style); - } - - /** - * Rewrite the edits in the given {@link MultiTextEdit} such that same edits are - * applied, but the resulting range is also formatted - * - * @param oldContents the original contents that should be edited by a - * {@link MultiTextEdit} - * @param edit the {@link MultiTextEdit} to be applied to some string - * @param style the formatting style to use - * @return a new {@link MultiTextEdit} which performs the same edits as the input edit - * but also reformats the text - */ - public static MultiTextEdit reformat(String oldContents, MultiTextEdit edit, - XmlFormatStyle style) { - IDocument document = new org.eclipse.jface.text.Document(); - document.set(oldContents); - - try { - edit.apply(document); - } catch (MalformedTreeException e) { - AdtPlugin.log(e, null); - return null; // Abort formatting - } catch (BadLocationException e) { - AdtPlugin.log(e, null); - return null; // Abort formatting - } - - String actual = document.get(); - - // TODO: Try to format only the affected portion of the document. - // To do that we need to find out what the affected offsets are; we know - // the MultiTextEdit's affected range, but that is referring to offsets - // in the old document. Use that to compute offsets in the new document. - //int distanceFromEnd = actual.length() - edit.getExclusiveEnd(); - //IStructuredModel model = DomUtilities.createStructuredModel(actual); - //int start = edit.getOffset(); - //int end = actual.length() - distanceFromEnd; - //int length = end - start; - //TextEdit format = AndroidXmlFormattingStrategy.format(model, start, length); - EclipseXmlFormatPreferences formatPrefs = EclipseXmlFormatPreferences.create(); - String formatted = EclipseXmlPrettyPrinter.prettyPrint(actual, formatPrefs, style, - null /*lineSeparator*/); - - - // Figure out how much of the before and after strings are identical and narrow - // the replacement scope - boolean foundDifference = false; - int firstDifference = 0; - int lastDifference = formatted.length(); - int start = 0; - int end = oldContents.length(); - - for (int i = 0, j = start; i < formatted.length() && j < end; i++, j++) { - if (formatted.charAt(i) != oldContents.charAt(j)) { - firstDifference = i; - foundDifference = true; - break; - } - } - - if (!foundDifference) { - // No differences - the document is already formatted, nothing to do - return null; - } - - lastDifference = firstDifference + 1; - for (int i = formatted.length() - 1, j = end - 1; - i > firstDifference && j > start; - i--, j--) { - if (formatted.charAt(i) != oldContents.charAt(j)) { - lastDifference = i + 1; - break; - } - } - - start += firstDifference; - end -= (formatted.length() - lastDifference); - end = Math.max(start, end); - formatted = formatted.substring(firstDifference, lastDifference); - - ReplaceEdit format = new ReplaceEdit(start, end - start, - formatted); - - MultiTextEdit newEdit = new MultiTextEdit(); - newEdit.addChild(format); - - return newEdit; - } - - protected ViewElementDescriptor getElementDescriptor(String fqcn) { - AndroidTargetData data = mDelegate.getEditor().getTargetData(); - if (data != null) { - return data.getLayoutDescriptors().findDescriptorByClass(fqcn); - } - - return null; - } - - /** Create a wizard for this refactoring */ - abstract VisualRefactoringWizard createWizard(); - - public abstract static class VisualRefactoringDescriptor extends RefactoringDescriptor { - private final Map<String, String> mArguments; - - public VisualRefactoringDescriptor( - String id, String project, String description, String comment, - Map<String, String> arguments) { - super(id, project, description, comment, STRUCTURAL_CHANGE | MULTI_CHANGE); - mArguments = arguments; - } - - public Map<String, String> getArguments() { - return mArguments; - } - - protected abstract Refactoring createRefactoring(Map<String, String> args); - - @Override - public Refactoring createRefactoring(RefactoringStatus status) throws CoreException { - try { - return createRefactoring(mArguments); - } catch (NullPointerException e) { - status.addFatalError("Failed to recreate refactoring from descriptor"); - return null; - } - } - } -} |