aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java
diff options
context:
space:
mode:
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.java410
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;
+ }
+}