• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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;
18 
19 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
20 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
21 import com.android.ide.eclipse.ddms.ISourceRevealer;
22 import com.google.common.base.Predicate;
23 
24 import org.eclipse.core.resources.IFile;
25 import org.eclipse.core.resources.IMarker;
26 import org.eclipse.core.resources.IProject;
27 import org.eclipse.core.resources.IResource;
28 import org.eclipse.core.runtime.CoreException;
29 import org.eclipse.core.runtime.NullProgressMonitor;
30 import org.eclipse.jdt.core.IMethod;
31 import org.eclipse.jdt.core.search.IJavaSearchConstants;
32 import org.eclipse.jdt.core.search.SearchEngine;
33 import org.eclipse.jdt.core.search.SearchMatch;
34 import org.eclipse.jdt.core.search.SearchParticipant;
35 import org.eclipse.jdt.core.search.SearchPattern;
36 import org.eclipse.jdt.core.search.SearchRequestor;
37 import org.eclipse.jdt.ui.JavaUI;
38 import org.eclipse.jface.viewers.IStructuredContentProvider;
39 import org.eclipse.jface.viewers.LabelProvider;
40 import org.eclipse.jface.viewers.Viewer;
41 import org.eclipse.jface.window.Window;
42 import org.eclipse.ui.IPerspectiveRegistry;
43 import org.eclipse.ui.IWorkbench;
44 import org.eclipse.ui.IWorkbenchWindow;
45 import org.eclipse.ui.PlatformUI;
46 import org.eclipse.ui.WorkbenchException;
47 import org.eclipse.ui.dialogs.ListDialog;
48 import org.eclipse.ui.ide.IDE;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.Comparator;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 
57 /**
58  * Implementation of the com.android.ide.ddms.sourceRevealer extension point.
59  * Note that this code is duplicated in the PDT plugin's SourceRevealer as well.
60  */
61 public class SourceRevealer implements ISourceRevealer {
62     @Override
reveal(String applicationName, String className, int line)63     public boolean reveal(String applicationName, String className, int line) {
64         IProject project = ProjectHelper.findAndroidProjectByAppName(applicationName);
65         if (project != null) {
66             return BaseProjectHelper.revealSource(project, className, line);
67         }
68 
69         return false;
70     }
71 
72     /**
73      * Reveal the source for given fully qualified method name.<br>
74      *
75      * The method should take care of the following scenarios:<ol>
76      * <li> A search for fqmn might provide only 1 result. In such a case, just open that result. </li>
77      * <li> The search might not provide any results. e.g, the method name may be of the form
78      *    "com.x.y$1.methodName". Searches for methods within anonymous classes will fail. In
79      *    such a case, if the fileName:lineNumber argument is available, a search for that
80      *    should be made instead. </li>
81      * <li> The search might provide multiple results. In such a case, the fileName/lineNumber
82      *    values should be utilized to narrow down the results.</li>
83      * </ol>
84      *
85      * @param fqmn fully qualified method name
86      * @param fileName file name in which the method is present, null if not known
87      * @param lineNumber line number in the file which should be given focus, -1 if not known.
88      *        Line numbers begin at 1, not 0.
89      * @param perspective perspective to switch to before the source is revealed, null to not
90      *        switch perspectives
91      */
92     @Override
revealMethod(String fqmn, String fileName, int lineNumber, String perspective)93     public boolean revealMethod(String fqmn, String fileName, int lineNumber, String perspective) {
94         List<SearchMatch> matches = searchForMethod(fqmn);
95 
96         // display the unique match
97         if (matches.size() == 1) {
98             return displayMethod((IMethod) matches.get(0).getElement(), perspective);
99         }
100 
101         // no matches for search by method, so search by filename
102         if (matches.size() == 0) {
103             if (fileName != null) {
104                 return revealLineMatch(searchForFile(fileName),
105                     fileName, lineNumber, perspective);
106             } else {
107                 return false;
108             }
109         }
110 
111         // multiple matches for search by method, narrow down by filename
112         if (fileName != null) {
113             return revealLineMatch(
114                     filterMatchByFileName(matches, fileName),
115                     fileName, lineNumber, perspective);
116         }
117 
118         // prompt the user
119         SearchMatch match = getMatchToDisplay(matches, fqmn);
120         if (match == null) {
121             return false;
122         } else {
123             return displayMethod((IMethod) match.getElement(), perspective);
124         }
125     }
126 
revealLineMatch(List<SearchMatch> matches, String fileName, int lineNumber, String perspective)127     private boolean revealLineMatch(List<SearchMatch> matches, String fileName, int lineNumber,
128             String perspective) {
129         SearchMatch match = getMatchToDisplay(matches,
130                 String.format("%s:%d", fileName, lineNumber));
131         if (match == null) {
132             return false;
133         }
134 
135         if (perspective != null) {
136             SourceRevealer.switchToPerspective(perspective);
137         }
138 
139         return displayFile((IFile) match.getResource(), lineNumber);
140     }
141 
displayFile(IFile file, int lineNumber)142     private boolean displayFile(IFile file, int lineNumber) {
143         try {
144             IMarker marker = file.createMarker(IMarker.TEXT);
145             marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
146             IDE.openEditor(
147                     PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(),
148                     marker);
149             marker.delete();
150             return true;
151         } catch (CoreException e) {
152             AdtPlugin.printErrorToConsole(e.getMessage());
153             return false;
154         }
155     }
156 
displayMethod(IMethod method, String perspective)157     private boolean displayMethod(IMethod method, String perspective) {
158         if (perspective != null) {
159             SourceRevealer.switchToPerspective(perspective);
160         }
161 
162         try {
163             JavaUI.openInEditor(method);
164             return true;
165         } catch (Exception e) {
166             AdtPlugin.printErrorToConsole(e.getMessage());
167             return false;
168         }
169     }
170 
filterMatchByFileName(List<SearchMatch> matches, String fileName)171     private List<SearchMatch> filterMatchByFileName(List<SearchMatch> matches, String fileName) {
172         if (fileName == null) {
173             return matches;
174         }
175 
176         // Use a map to collapse multiple matches in a single file into just one match since
177         // we know the line number in the file.
178         Map<IResource, SearchMatch> matchesPerFile =
179                 new HashMap<IResource, SearchMatch>(matches.size());
180 
181         for (SearchMatch m: matches) {
182             if (m.getResource() instanceof IFile
183                     && m.getResource().getName().startsWith(fileName)) {
184                 matchesPerFile.put(m.getResource(), m);
185             }
186         }
187 
188         List<SearchMatch> filteredMatches = new ArrayList<SearchMatch>(matchesPerFile.values());
189 
190         // sort results, first by project name, then by file name
191         Collections.sort(filteredMatches, new Comparator<SearchMatch>() {
192             @Override
193             public int compare(SearchMatch m1, SearchMatch m2) {
194                 String p1 = m1.getResource().getProject().getName();
195                 String p2 = m2.getResource().getProject().getName();
196 
197                 if (!p1.equals(p2)) {
198                     return p1.compareTo(p2);
199                 }
200 
201                 String r1 = m1.getResource().getName();
202                 String r2 = m2.getResource().getName();
203                 return r1.compareTo(r2);
204             }
205         });
206         return filteredMatches;
207     }
208 
getMatchToDisplay(List<SearchMatch> matches, String searchTerm)209     private SearchMatch getMatchToDisplay(List<SearchMatch> matches, String searchTerm) {
210         // no matches for given search
211         if (matches.size() == 0) {
212             return null;
213         }
214 
215         // there is only 1 match, so we return that
216         if (matches.size() == 1) {
217             return matches.get(0);
218         }
219 
220         // multiple matches, prompt the user to select
221         IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
222         if (window == null) {
223             return null;
224         }
225 
226         ListDialog dlg = new ListDialog(window.getShell());
227         dlg.setMessage("Multiple files match search: " + searchTerm);
228         dlg.setTitle("Select file to open");
229         dlg.setInput(matches);
230         dlg.setContentProvider(new IStructuredContentProvider() {
231             @Override
232             public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
233             }
234 
235             @Override
236             public void dispose() {
237             }
238 
239             @Override
240             public Object[] getElements(Object inputElement) {
241                 return ((List<?>) inputElement).toArray();
242             }
243         });
244         dlg.setLabelProvider(new LabelProvider() {
245            @Override
246            public String getText(Object element) {
247                SearchMatch m = (SearchMatch) element;
248                return String.format("/%s/%s",    //$NON-NLS-1$
249                        m.getResource().getProject().getName(),
250                        m.getResource().getProjectRelativePath().toString());
251            }
252         });
253         dlg.setInitialSelections(new Object[] { matches.get(0) });
254         dlg.setHelpAvailable(false);
255 
256         if (dlg.open() == Window.OK) {
257             Object[] selectedMatches = dlg.getResult();
258             if (selectedMatches.length > 0) {
259                 return (SearchMatch) selectedMatches[0];
260             }
261         }
262 
263         return null;
264     }
265 
searchForFile(String fileName)266     private List<SearchMatch> searchForFile(String fileName) {
267         return searchForPattern(fileName, IJavaSearchConstants.CLASS, MATCH_IS_FILE_PREDICATE);
268     }
269 
searchForMethod(String fqmn)270     private List<SearchMatch> searchForMethod(String fqmn) {
271         return searchForPattern(fqmn, IJavaSearchConstants.METHOD, MATCH_IS_METHOD_PREDICATE);
272     }
273 
searchForPattern(String pattern, int searchFor, Predicate<SearchMatch> filterPredicate)274     private List<SearchMatch> searchForPattern(String pattern, int searchFor,
275             Predicate<SearchMatch> filterPredicate) {
276         SearchEngine se = new SearchEngine();
277         SearchPattern searchPattern = SearchPattern.createPattern(
278                 pattern,
279                 searchFor,
280                 IJavaSearchConstants.DECLARATIONS,
281                 SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE);
282         SearchResultAccumulator requestor = new SearchResultAccumulator(filterPredicate);
283         try {
284             se.search(searchPattern,
285                     new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()},
286                     SearchEngine.createWorkspaceScope(),
287                     requestor,
288                     new NullProgressMonitor());
289         } catch (CoreException e) {
290             AdtPlugin.printErrorToConsole(e.getMessage());
291             return Collections.emptyList();
292         }
293 
294         return requestor.getMatches();
295     }
296 
297     private static final Predicate<SearchMatch> MATCH_IS_FILE_PREDICATE =
298             new Predicate<SearchMatch>() {
299                 @Override
300                 public boolean apply(SearchMatch match) {
301                     return match.getResource() instanceof IFile;
302                 }
303             };
304 
305     private static final Predicate<SearchMatch> MATCH_IS_METHOD_PREDICATE =
306             new Predicate<SearchMatch>() {
307                 @Override
308                 public boolean apply(SearchMatch match) {
309                     return match.getResource() instanceof IFile;
310                 }
311             };
312 
313     private static class SearchResultAccumulator extends SearchRequestor {
314         private final List<SearchMatch> mSearchMatches = new ArrayList<SearchMatch>();
315         private final Predicate<SearchMatch> mPredicate;
316 
SearchResultAccumulator(Predicate<SearchMatch> filterPredicate)317         public SearchResultAccumulator(Predicate<SearchMatch> filterPredicate) {
318             mPredicate = filterPredicate;
319         }
320 
getMatches()321         public List<SearchMatch> getMatches() {
322             return mSearchMatches;
323         }
324 
325         @Override
acceptSearchMatch(SearchMatch match)326         public void acceptSearchMatch(SearchMatch match) throws CoreException {
327             if (mPredicate.apply(match)) {
328                 mSearchMatches.add(match);
329             }
330         }
331     }
332 
switchToPerspective(String perspectiveId)333     private static void switchToPerspective(String perspectiveId) {
334         IWorkbench workbench = PlatformUI.getWorkbench();
335         IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
336         IPerspectiveRegistry perspectiveRegistry = workbench.getPerspectiveRegistry();
337         if (perspectiveId != null
338                 && perspectiveId.length() > 0
339                 && perspectiveRegistry.findPerspectiveWithId(perspectiveId) != null) {
340             try {
341                 workbench.showPerspective(perspectiveId, window);
342             } catch (WorkbenchException e) {
343                 AdtPlugin.printErrorToConsole(e.getMessage());
344             }
345         }
346     }
347 }
348