1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.refactorings.extractstring;
18 
19 import com.android.ide.eclipse.adt.AdtConstants;
20 
21 import org.eclipse.core.resources.IFile;
22 import org.eclipse.core.resources.IProject;
23 import org.eclipse.core.runtime.CoreException;
24 import org.eclipse.jdt.core.ICompilationUnit;
25 import org.eclipse.jdt.core.JavaCore;
26 import org.eclipse.jface.action.IAction;
27 import org.eclipse.jface.text.ITextSelection;
28 import org.eclipse.jface.viewers.ISelection;
29 import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
30 import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
31 import org.eclipse.ui.IEditorInput;
32 import org.eclipse.ui.IEditorPart;
33 import org.eclipse.ui.IWorkbenchPage;
34 import org.eclipse.ui.IWorkbenchWindow;
35 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
36 import org.eclipse.ui.PlatformUI;
37 import org.eclipse.ui.part.FileEditorInput;
38 
39 /*
40  * Quick Reference Link:
41  * http://www.eclipse.org/articles/article.php?file=Article-Unleashing-the-Power-of-Refactoring/index.html
42  * and
43  * http://www.ibm.com/developerworks/opensource/library/os-ecjdt/
44  */
45 
46 /**
47  * Action executed when the "Extract String" menu item is invoked.
48  * <p/>
49  * The intent of the action is to start a refactoring that extracts a source string and
50  * replaces it by an Android string resource ID.
51  * <p/>
52  * Workflow:
53  * <ul>
54  * <li> The action is currently located in the Refactoring menu in the main menu.
55  * <li> TODO: extend the popup refactoring menu in a Java or Android XML file.
56  * <li> The action is only enabled if the selection is 1 character or more. That is at least part
57  *     of the string must be selected, it's not enough to just move the insertion point. This is
58  *     a limitation due to {@link #selectionChanged(IAction, ISelection)} not being called when
59  *     the insertion point is merely moved. TODO: address this limitation.
60  * <ul> The action gets the current {@link ISelection}. It also knows the current
61  *     {@link IWorkbenchWindow}. However for the refactoring we are also interested in having the
62  *     actual resource file. By looking at the Active Window > Active Page > Active Editor we
63  *     can get the {@link IEditorInput} and find the {@link ICompilationUnit} (aka Java file)
64  *     that is being edited.
65  * <ul> TODO: change this to find the {@link IFile} being manipulated. The {@link ICompilationUnit}
66  *     can be inferred using {@link JavaCore#createCompilationUnitFrom(IFile)}. This will allow
67  *     us to be able to work with a selection from an Android XML file later.
68  * <li> The action creates a new {@link ExtractStringRefactoring} and make it run on in a new
69  *     {@link ExtractStringWizard}.
70  * <ul>
71  */
72 public class ExtractStringAction implements IWorkbenchWindowActionDelegate {
73 
74     /** Keep track of the current workbench window. */
75     private IWorkbenchWindow mWindow;
76     private ITextSelection mSelection;
77     private IEditorPart mEditor;
78     private IFile mFile;
79 
80     /**
81      * Keep track of the current workbench window.
82      */
83     @Override
init(IWorkbenchWindow window)84     public void init(IWorkbenchWindow window) {
85         mWindow = window;
86     }
87 
88     @Override
dispose()89     public void dispose() {
90         // Nothing to do
91     }
92 
93     /**
94      * Examine the selection to determine if the action should be enabled or not.
95      * <p/>
96      * Keep a link to the relevant selection structure (i.e. a part of the Java AST).
97      */
98     @Override
selectionChanged(IAction action, ISelection selection)99     public void selectionChanged(IAction action, ISelection selection) {
100 
101         // Note, two kinds of selections are returned here:
102         // ITextSelection on a Java source window
103         // IStructuredSelection in the outline or navigator
104         // This simply deals with the refactoring based on a non-empty selection.
105         // At that point, just enable the action and later decide if it's valid when it actually
106         // runs since we don't have access to the AST yet.
107 
108         mSelection = null;
109         mFile = null;
110 
111         if (selection instanceof ITextSelection) {
112             mSelection = (ITextSelection) selection;
113             if (mSelection.getLength() > 0) {
114                 mEditor = getActiveEditor();
115                 mFile = getSelectedFile(mEditor);
116             }
117         }
118 
119         action.setEnabled(mSelection != null && mFile != null);
120     }
121 
122     /**
123      * Create a new instance of our refactoring and a wizard to configure it.
124      */
125     @Override
run(IAction action)126     public void run(IAction action) {
127         if (mSelection != null && mFile != null) {
128             ExtractStringRefactoring ref = new ExtractStringRefactoring(mFile, mEditor, mSelection);
129             RefactoringWizard wizard = new ExtractStringWizard(ref, mFile.getProject());
130             RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
131             try {
132                 op.run(mWindow.getShell(), wizard.getDefaultPageTitle());
133             } catch (InterruptedException e) {
134                 // Interrupted. Pass.
135             }
136         }
137     }
138 
139     /**
140      * Returns the active editor (hopefully matching our selection) or null.
141      */
getActiveEditor()142     private IEditorPart getActiveEditor() {
143         IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
144         if (wwin != null) {
145             IWorkbenchPage page = wwin.getActivePage();
146             if (page != null) {
147                 return page.getActiveEditor();
148             }
149         }
150 
151         return null;
152     }
153 
154     /**
155      * Returns the active {@link IFile} (hopefully matching our selection) or null.
156      * The file is only returned if it's a file from a project with an Android nature.
157      * <p/>
158      * At that point we do not try to analyze if the selection nor the file is suitable
159      * for the refactoring. This check is performed when the refactoring is invoked since
160      * it can then produce meaningful error messages as needed.
161      */
getSelectedFile(IEditorPart editor)162     private IFile getSelectedFile(IEditorPart editor) {
163         if (editor != null) {
164             IEditorInput input = editor.getEditorInput();
165 
166             if (input instanceof FileEditorInput) {
167                 FileEditorInput fi = (FileEditorInput) input;
168                 IFile file = fi.getFile();
169                 if (file.exists()) {
170                     IProject proj = file.getProject();
171                     try {
172                         if (proj != null && proj.hasNature(AdtConstants.NATURE_DEFAULT)) {
173                             return file;
174                         }
175                     } catch (CoreException e) {
176                         // ignore
177                     }
178                 }
179             }
180         }
181 
182         return null;
183     }
184 }
185