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