1 /*******************************************************************************
2  * Copyright (c) 2018, 2019 Angelo Zerr and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  * - Angelo Zerr: initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jdt.internal.ui.javaeditor.codemining;
15 
16 import java.text.MessageFormat;
17 import java.util.concurrent.CompletableFuture;
18 import java.util.concurrent.atomic.AtomicLong;
19 import java.util.function.Consumer;
20 
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.events.MouseEvent;
23 
24 import org.eclipse.core.runtime.CoreException;
25 import org.eclipse.core.runtime.IProgressMonitor;
26 
27 import org.eclipse.core.resources.ResourcesPlugin;
28 
29 import org.eclipse.jface.text.BadLocationException;
30 import org.eclipse.jface.text.IDocument;
31 import org.eclipse.jface.text.ITextViewer;
32 import org.eclipse.jface.text.codemining.ICodeMiningProvider;
33 
34 import org.eclipse.ui.IEditorPart;
35 
36 import org.eclipse.ui.texteditor.ITextEditor;
37 
38 import org.eclipse.search.ui.NewSearchUI;
39 
40 import org.eclipse.jdt.core.IJavaElement;
41 import org.eclipse.jdt.core.IJavaProject;
42 import org.eclipse.jdt.core.JavaCore;
43 import org.eclipse.jdt.core.JavaModelException;
44 import org.eclipse.jdt.core.search.IJavaSearchConstants;
45 import org.eclipse.jdt.core.search.IJavaSearchScope;
46 import org.eclipse.jdt.core.search.SearchEngine;
47 import org.eclipse.jdt.core.search.SearchMatch;
48 import org.eclipse.jdt.core.search.SearchParticipant;
49 import org.eclipse.jdt.core.search.SearchPattern;
50 import org.eclipse.jdt.core.search.SearchRequestor;
51 
52 import org.eclipse.jdt.ui.actions.FindReferencesAction;
53 
54 import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
55 import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
56 import org.eclipse.jdt.internal.ui.search.JavaSearchScopeFactory;
57 
58 /**
59  * Java reference code mining.
60  *
61  * @since 3.16
62  */
63 public class JavaReferenceCodeMining extends AbstractJavaElementLineHeaderCodeMining {
64 
65 	private final JavaEditor editor;
66 
67 	private final boolean showReferencesAtLeastOne;
68 
69 	private Consumer<MouseEvent> action;
70 
JavaReferenceCodeMining(IJavaElement element, JavaEditor editor, IDocument document, ICodeMiningProvider provider, boolean showReferencesAtLeastOne)71 	public JavaReferenceCodeMining(IJavaElement element, JavaEditor editor, IDocument document,
72 			ICodeMiningProvider provider, boolean showReferencesAtLeastOne)
73 			throws JavaModelException, BadLocationException {
74 		super(element, document, provider, null);
75 		this.editor= editor;
76 		this.showReferencesAtLeastOne= showReferencesAtLeastOne;
77 	}
78 
79 	@SuppressWarnings("boxing")
80 	@Override
doResolve(ITextViewer viewer, IProgressMonitor monitor)81 	protected CompletableFuture<Void> doResolve(ITextViewer viewer, IProgressMonitor monitor) {
82 		return CompletableFuture.runAsync(() -> {
83 			try {
84 				monitor.isCanceled();
85 				IJavaElement element= super.getElement();
86 				long refCount= countReferences(element, monitor);
87 				monitor.isCanceled();
88 				action= refCount > 0 ? e -> {
89 					if (refCount == 1 && (e.stateMask & SWT.CTRL) == SWT.CTRL) {
90 						// Ctrl + Click is done, open the referenced element in the Java Editor
91 						try {
92 							SearchMatch match= getReferenceMatch(element, monitor);
93 							IJavaElement javaElement= (IJavaElement) match.getElement();
94 							IEditorPart part= EditorUtility.openInEditor(javaElement);
95 							if (part != null) {
96 								EditorUtility.revealInEditor(part, javaElement);
97 								if (part instanceof ITextEditor) {
98 									ITextEditor textEditor= (ITextEditor) part;
99 									textEditor.selectAndReveal(match.getOffset(), match.getLength());
100 								}
101 							}
102 						} catch (JavaModelException e1) {
103 							// Should never occur
104 						} catch (CoreException e1) {
105 							// Should never occur
106 						}
107 					} else {
108 						// Otherwise, launch references search
109 						new FindReferencesAction(editor).run(element);
110 					}
111 				} : null;
112 				if (refCount == 0 && showReferencesAtLeastOne) {
113 					super.setLabel(""); //$NON-NLS-1$
114 				} else {
115 					super.setLabel(MessageFormat.format(JavaCodeMiningMessages.JavaReferenceCodeMining_label, refCount));
116 				}
117 			} catch (JavaModelException e) {
118 				// Should never occur
119 			} catch (CoreException e) {
120 				// Should never occur
121 			}
122 		});
123 	}
124 
125 	@Override
126 	public Consumer<MouseEvent> getAction() {
127 		return action;
128 	}
129 
130 	/**
131 	 * Return the number of references for the given java element.
132 	 *
133 	 * @param element the java element.
134 	 * @param monitor the monitor
135 	 * @return he number of references for the given java element.
136 	 * @throws JavaModelException throws when java error.
137 	 * @throws CoreException throws when java error.
138 	 */
139 	private static long countReferences(IJavaElement element, IProgressMonitor monitor)
140 			throws JavaModelException, CoreException {
141 		if (element == null) {
142 			return 0;
143 		}
144 		final AtomicLong count= new AtomicLong(0);
145 		SearchPattern pattern= SearchPattern.createPattern(element, IJavaSearchConstants.REFERENCES);
146 		SearchEngine engine= new SearchEngine();
147 		final boolean ignoreInaccurate= NewSearchUI.arePotentialMatchesIgnored();
148 		engine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
149 				createSearchScope(element), new SearchRequestor() {
150 
151 					@Override
152 					public void acceptSearchMatch(SearchMatch match) throws CoreException {
153 						if (match.getAccuracy() == SearchMatch.A_INACCURATE && ignoreInaccurate) {
154 							return;
155 						}
156 						Object o= match.getElement();
157 						if (o instanceof IJavaElement) {
158 							IJavaElement e= (IJavaElement)o;
159 							if (e.getAncestor(IJavaElement.COMPILATION_UNIT) != null
160 									|| e.getAncestor(IJavaElement.CLASS_FILE) != null) {
161 								count.incrementAndGet();
162 							}
163 						}
164 					}
165 				}, monitor);
166 
167 		return count.get();
168 	}
169 
170 	/**
171 	 * Return the single search match of references for the given java element.
172 	 *
173 	 * @param element the java element.
174 	 * @param monitor the monitor
175 	 * @return he number of references for the given java element.
176 	 * @throws JavaModelException throws when java error.
177 	 * @throws CoreException throws when java error.
178 	 */
179 	private SearchMatch getReferenceMatch(IJavaElement element, IProgressMonitor monitor)
180 			throws JavaModelException, CoreException {
181 		if (element == null) {
182 			return null;
183 		}
184 		final SearchMatch[] matches= new SearchMatch[1];
185 		SearchPattern pattern= SearchPattern.createPattern(element, IJavaSearchConstants.REFERENCES);
186 		SearchEngine engine= new SearchEngine();
187 		engine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
188 				createSourceSearchScope(), new SearchRequestor() {
189 
190 					@Override
191 					public void acceptSearchMatch(final SearchMatch match) throws CoreException {
192 						Object o= match.getElement();
193 						if (o instanceof IJavaElement
194 								&& ((IJavaElement) o).getAncestor(IJavaElement.COMPILATION_UNIT) != null) {
195 							matches[0]= match;
196 						}
197 					}
198 				}, monitor);
199 
200 		return matches[0];
201 	}
202 
203 	/**
204 	 * Create Java workspace scope.
205 	 *
206 	 * @param element IJavaElement to search references for
207 	 *
208 	 * @return the Java workspace scope.
209 	 * @throws JavaModelException when java error.
210 	 */
211 	private static IJavaSearchScope createSearchScope(IJavaElement element) throws JavaModelException {
212 		JavaSearchScopeFactory factory= JavaSearchScopeFactory.getInstance();
213 		boolean isInsideJRE = factory.isInsideJRE(element);
214 		IJavaSearchScope scope= factory.createWorkspaceScope(isInsideJRE);
215 		return scope;
216 	}
217 
218 	/**
219 	 * Create Java source search scope.
220 	 *
221 	 * @return the Java workspace scope.
222 	 * @throws JavaModelException when java error.
223 	 */
224 	private static IJavaSearchScope createSourceSearchScope() throws JavaModelException {
225 		IJavaProject[] projects= JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()).getJavaProjects();
226 		return SearchEngine.createJavaSearchScope(projects, IJavaSearchScope.SOURCES);
227 	}
228 }
229