diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java new file mode 100644 index 000000000..8ecb08836 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2012 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.refactorings.core; + +import static com.android.SdkConstants.ANDROID_MANIFEST_XML; +import static com.android.SdkConstants.ANDROID_PREFIX; +import static com.android.SdkConstants.ANDROID_THEME_PREFIX; +import static com.android.SdkConstants.ATTR_NAME; +import static com.android.SdkConstants.ATTR_TYPE; +import static com.android.SdkConstants.TAG_ITEM; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.resources.ResourceUrl; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; +import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.resources.ResourceType; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor; +import org.eclipse.jdt.internal.ui.refactoring.reorg.RenameTypeWizard; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.ITextEditorExtension; +import org.eclipse.ui.texteditor.ITextEditorExtension2; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.List; + +/** + * Text action for XML files to invoke renaming + * <p> + * TODO: Handle other types of renaming: invoking class renaming when editing + * class names in layout files and manifest files, renaming attribute names when + * editing a styleable attribute, etc. + */ +@SuppressWarnings("restriction") // Java rename refactoring +public final class RenameResourceXmlTextAction extends Action { + private final ITextEditor mEditor; + + /** + * Creates a new {@linkplain RenameResourceXmlTextAction} + * + * @param editor the associated editor + */ + public RenameResourceXmlTextAction(@NonNull ITextEditor editor) { + super("Rename"); + mEditor = editor; + } + + @Override + public void run() { + if (!validateEditorInputState()) { + return; + } + IFile file = getFile(); + if (file == null) { + return; + } + IProject project = file.getProject(); + if (project == null) { + return; + } + IDocument document = getDocument(); + if (document == null) { + return; + } + ITextSelection selection = getSelection(); + if (selection == null) { + return; + } + + ResourceUrl resource = findResource(document, selection.getOffset()); + + if (resource == null) { + resource = findItemDefinition(document, selection.getOffset()); + } + + if (resource != null) { + ResourceType type = resource.type; + String name = resource.name; + Shell shell = mEditor.getSite().getShell(); + boolean canClear = false; + + RenameResourceWizard.renameResource(shell, project, type, name, null, canClear); + return; + } + + String className = findClassName(document, file, selection.getOffset()); + if (className != null) { + assert className.equals(className.trim()); + IType type = findType(className, project); + if (type != null) { + RenameTypeProcessor processor = new RenameTypeProcessor(type); + //processor.setNewElementName(className); + processor.setUpdateQualifiedNames(true); + processor.setUpdateSimilarDeclarations(false); + //processor.setMatchStrategy(?); + //processor.setFilePatterns(patterns); + processor.setUpdateReferences(true); + + RenameRefactoring refactoring = new RenameRefactoring(processor); + RenameTypeWizard wizard = new RenameTypeWizard(refactoring); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + op.run(window.getShell(), wizard.getDefaultPageTitle()); + } catch (InterruptedException e) { + } + } + + return; + } + + // Fallback: tell user the cursor isn't in the right place + MessageDialog.openInformation(mEditor.getSite().getShell(), + "Rename", + "Operation unavailable on the current selection.\n" + + "Select an Android resource name or class."); + } + + private boolean validateEditorInputState() { + if (mEditor instanceof ITextEditorExtension2) + return ((ITextEditorExtension2) mEditor).validateEditorInputState(); + else if (mEditor instanceof ITextEditorExtension) + return !((ITextEditorExtension) mEditor).isEditorInputReadOnly(); + else if (mEditor != null) + return mEditor.isEditable(); + else + return false; + } + + /** + * Searches for a resource URL around the caret, such as {@code @string/foo} + * + * @param document the document to search in + * @param offset the offset to search at + * @return a resource pair, or null if not found + */ + @Nullable + public static ResourceUrl findResource(@NonNull IDocument document, int offset) { + try { + int max = document.getLength(); + if (offset >= max) { + offset = max - 1; + } else if (offset < 0) { + offset = 0; + } else if (offset > 0) { + // If the caret is right after a resource name (meaning getChar(offset) points + // to the following character), back up + char c = document.getChar(offset); + if (!isValidResourceNameChar(c)) { + offset--; + } + } + + int start = offset; + boolean valid = true; + for (; start >= 0; start--) { + char c = document.getChar(start); + if (c == '@' || c == '?') { + break; + } else if (!isValidResourceNameChar(c)) { + valid = false; + break; + } + } + if (valid) { + // Search forwards for the end + int end = start + 1; + for (; end < max; end++) { + char c = document.getChar(end); + if (!isValidResourceNameChar(c)) { + break; + } + } + if (end > start + 1) { + String url = document.get(start, end - start); + + // Don't allow renaming framework resources -- @android:string/ok etc + if (url.startsWith(ANDROID_PREFIX) || url.startsWith(ANDROID_THEME_PREFIX)) { + return null; + } + + return ResourceUrl.parse(url); + } + } + } catch (BadLocationException e) { + AdtPlugin.log(e, null); + } + + return null; + } + + private static boolean isValidResourceNameChar(char c) { + return c == '@' || c == '?' || c == '/' || c == '+' || Character.isJavaIdentifierPart(c); + } + + /** + * Searches for an item definition around the caret, such as + * {@code <string name="foo">My String</string>} + */ + private ResourceUrl findItemDefinition(IDocument document, int offset) { + Node node = DomUtilities.getNode(document, offset); + if (node == null) { + return null; + } + if (node.getNodeType() == Node.TEXT_NODE) { + node = node.getParentNode(); + } + if (node == null || node.getNodeType() != Node.ELEMENT_NODE) { + return null; + } + + Element element = (Element) node; + String name = element.getAttribute(ATTR_NAME); + if (name == null || name.isEmpty()) { + return null; + } + String typeString = element.getTagName(); + if (TAG_ITEM.equals(typeString)) { + typeString = element.getAttribute(ATTR_TYPE); + if (typeString == null || typeString.isEmpty()) { + return null; + } + } + ResourceType type = ResourceType.getEnum(typeString); + if (type != null) { + return ResourceUrl.create(type, name, false, false); + } + + return null; + } + + /** + * Searches for a fully qualified class name around the caret, such as {@code foo.bar.MyClass} + * + * @param document the document to search in + * @param file the file, if known + * @param offset the offset to search at + * @return a resource pair, or null if not found + */ + @Nullable + public static String findClassName( + @NonNull IDocument document, + @Nullable IFile file, + int offset) { + try { + int max = document.getLength(); + if (offset >= max) { + offset = max - 1; + } else if (offset < 0) { + offset = 0; + } else if (offset > 0) { + // If the caret is right after a resource name (meaning getChar(offset) points + // to the following character), back up + char c = document.getChar(offset); + if (Character.isJavaIdentifierPart(c)) { + offset--; + } + } + + int start = offset; + for (; start >= 0; start--) { + char c = document.getChar(start); + if (c == '"' || c == '<' || c == '/') { + start++; + break; + } else if (c != '.' && !Character.isJavaIdentifierPart(c)) { + return null; + } + } + // Search forwards for the end + int end = start + 1; + for (; end < max; end++) { + char c = document.getChar(end); + if (c != '.' && !Character.isJavaIdentifierPart(c)) { + if (c != '"' && c != '>' && !Character.isWhitespace(c)) { + return null; + } + break; + } + } + if (end > start + 1) { + String fqcn = document.get(start, end - start); + int dot = fqcn.indexOf('.'); + if (dot == -1) { // Only support fully qualified names + return null; + } + if (dot == 0) { // Special case for manifests: prepend package + if (file != null && file.getName().equals(ANDROID_MANIFEST_XML)) { + ManifestInfo info = ManifestInfo.get(file.getProject()); + return info.getPackage() + fqcn; + } + return null; + } + + return fqcn; + } + } catch (BadLocationException e) { + AdtPlugin.log(e, null); + } + + return null; + } + + @Nullable + private IType findType(@NonNull String className, @NonNull IProject project) { + IType type = null; + try { + IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); + type = javaProject.findType(className); + if (type == null || !type.exists()) { + return null; + } + if (!type.isBinary()) { + return type; + } + // See if this class is coming through a library project jar file and + // if so locate the real class + ProjectState projectState = Sdk.getProjectState(project); + if (projectState != null) { + List<IProject> libraries = projectState.getFullLibraryProjects(); + for (IProject library : libraries) { + javaProject = BaseProjectHelper.getJavaProject(library); + type = javaProject.findType(className); + if (type != null && type.exists() && !type.isBinary()) { + return type; + } + } + } + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + + return null; + } + + private ITextSelection getSelection() { + ISelectionProvider selectionProvider = mEditor.getSelectionProvider(); + if (selectionProvider == null) { + return null; + } + ISelection selection = selectionProvider.getSelection(); + if (!(selection instanceof ITextSelection)) { + return null; + } + return (ITextSelection) selection; + } + + private IDocument getDocument() { + IDocumentProvider documentProvider = mEditor.getDocumentProvider(); + if (documentProvider == null) { + return null; + } + IDocument document = documentProvider.getDocument(mEditor.getEditorInput()); + if (document == null) { + return null; + } + return document; + } + + @Nullable + private IFile getFile() { + IEditorInput input = mEditor.getEditorInput(); + if (input instanceof IFileEditorInput) { + IFileEditorInput fileInput = (IFileEditorInput) input; + return fileInput.getFile(); + } + + return null; + } +} |