diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoring.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoring.java | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoring.java new file mode 100644 index 000000000..73f5eb149 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoring.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.SdkConstants.ANDROID_URI; +import static com.android.SdkConstants.ANDROID_WIDGET_PREFIX; +import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; +import static com.android.SdkConstants.ATTR_TEXT; +import static com.android.SdkConstants.EXT_XML; +import static com.android.SdkConstants.VIEW_FRAGMENT; +import static com.android.SdkConstants.VIEW_INCLUDE; + +import com.android.annotations.NonNull; +import com.android.annotations.VisibleForTesting; +import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.text.edits.TextEdit; +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.document.ElementImpl; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Changes the type of the given widgets to the given target type + * and updates the attributes if necessary + */ +@SuppressWarnings("restriction") // XML model +public class ChangeViewRefactoring extends VisualRefactoring { + private static final String KEY_TYPE = "type"; //$NON-NLS-1$ + private String mTypeFqcn; + + /** + * This constructor is solely used by {@link Descriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + ChangeViewRefactoring(Map<String, String> arguments) { + super(arguments); + mTypeFqcn = arguments.get(KEY_TYPE); + } + + public ChangeViewRefactoring( + IFile file, + LayoutEditorDelegate delegate, + ITextSelection selection, + ITreeSelection treeSelection) { + super(file, delegate, selection, treeSelection); + } + + @VisibleForTesting + ChangeViewRefactoring(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 convert"); + return status; + } + + // Make sure the selection is contiguous + if (mTreeSelection != null) { + List<CanvasViewInfo> infos = getSelectedViewInfos(); + if (!validateNotEmpty(infos, status)) { + return status; + } + } + + // Ensures that we have a valid DOM model: + if (mElements.size() == 0) { + status.addFatalError("Nothing to convert"); + 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_TYPE, mTypeFqcn); + + return args; + } + + @Override + public String getName() { + return "Change Widget Type"; + } + + void setType(String typeFqcn) { + mTypeFqcn = typeFqcn; + } + + @Override + protected @NonNull List<Change> computeChanges(IProgressMonitor monitor) { + String name = getViewClass(mTypeFqcn); + + IFile file = mDelegate.getEditor().getInputFile(); + List<Change> changes = new ArrayList<Change>(); + if (file == null) { + return changes; + } + TextFileChange change = new TextFileChange(file.getName(), file); + MultiTextEdit rootEdit = new MultiTextEdit(); + change.setEdit(rootEdit); + change.setTextType(EXT_XML); + changes.add(change); + + for (Element element : getElements()) { + IndexedRegion region = getRegion(element); + String text = getText(region.getStartOffset(), region.getEndOffset()); + String oldName = element.getNodeName(); + int open = text.indexOf(oldName); + int close = text.lastIndexOf(oldName); + if (element instanceof ElementImpl && ((ElementImpl) element).isEmptyTag()) { + close = -1; + } + + if (open != -1) { + int oldLength = oldName.length(); + rootEdit.addChild(new ReplaceEdit(region.getStartOffset() + open, + oldLength, name)); + } + if (close != -1 && close != open) { + int oldLength = oldName.length(); + rootEdit.addChild(new ReplaceEdit(region.getStartOffset() + close, oldLength, + name)); + } + + // Change tag type + String oldId = getId(element); + String newId = ensureIdMatchesType(element, mTypeFqcn, rootEdit); + // Update any layout references to the old id with the new id + if (oldId != null && newId != null) { + IStructuredModel model = mDelegate.getEditor().getModelForRead(); + try { + IStructuredDocument doc = model.getStructuredDocument(); + if (doc != null) { + IndexedRegion range = getRegion(element); + int skipStart = range.getStartOffset(); + int skipEnd = range.getEndOffset(); + List<TextEdit> replaceIds = replaceIds(getAndroidNamespacePrefix(), doc, + skipStart, skipEnd, + oldId, newId); + for (TextEdit edit : replaceIds) { + rootEdit.addChild(edit); + } + } + } finally { + model.releaseFromRead(); + } + } + + // Strip out attributes that no longer make sense + removeUndefinedAttrs(rootEdit, element); + } + + return changes; + } + + /** Removes all the unused attributes after a conversion */ + private void removeUndefinedAttrs(MultiTextEdit rootEdit, Element element) { + ViewElementDescriptor descriptor = getElementDescriptor(mTypeFqcn); + if (descriptor == null) { + return; + } + + Set<String> defined = new HashSet<String>(); + AttributeDescriptor[] layoutAttributes = descriptor.getAttributes(); + for (AttributeDescriptor attribute : layoutAttributes) { + defined.add(attribute.getXmlLocalName()); + } + + List<Attr> attributes = findAttributes(element); + for (Attr attribute : attributes) { + String name = attribute.getLocalName(); + if (!defined.contains(name)) { + // Remove it + removeAttribute(rootEdit, element, attribute.getNamespaceURI(), name); + } + } + + // Set text attribute if it's defined + if (defined.contains(ATTR_TEXT) && !element.hasAttributeNS(ANDROID_URI, ATTR_TEXT)) { + setAttribute(rootEdit, element, ANDROID_URI, getAndroidNamespacePrefix(), + ATTR_TEXT, descriptor.getUiName()); + } + } + + protected List<Attr> findAttributes(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; + } + + List<String> getOldTypes() { + List<String> types = new ArrayList<String>(); + for (Element primary : getElements()) { + String oldType = primary.getTagName(); + if (oldType.indexOf('.') == -1 + && !oldType.equals(VIEW_INCLUDE) && !oldType.equals(VIEW_FRAGMENT)) { + oldType = ANDROID_WIDGET_PREFIX + oldType; + } + types.add(oldType); + } + + return types; + } + + @Override + VisualRefactoringWizard createWizard() { + return new ChangeViewWizard(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.changeview", //$NON-NLS-1$ + project, description, comment, arguments); + } + + @Override + protected Refactoring createRefactoring(Map<String, String> args) { + return new ChangeViewRefactoring(args); + } + } +} |