Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 4b950c468d6107650a67d90d67c8c031e9cb71be (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
/**
 *  Copyright (c) 2017 Angelo ZERR.
 *
 *  This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License 2.0
 *  which accompanies this distribution, and is available at
 *  https://www.eclipse.org/legal/epl-2.0/
 *
 *  SPDX-License-Identifier: EPL-2.0
 *
 *  Contributors:
 *  Angelo Zerr <angelo.zerr@gmail.com> - [CodeMining] Provide CodeMining support with CodeMiningManager - Bug 527720
 */
package org.eclipse.jface.internal.text.codemining;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.osgi.framework.Bundle;

import org.eclipse.swt.graphics.Rectangle;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.codemining.ICodeMining;
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
import org.eclipse.jface.text.codemining.LineHeaderCodeMining;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.inlined.AbstractInlinedAnnotation;
import org.eclipse.jface.text.source.inlined.InlinedAnnotationSupport;

/**
 * Code Mining manager implementation.
 *
 * @since 3.13
 */
public class CodeMiningManager implements Runnable {

	/**
	 * The source viewer
	 */
	private final ISourceViewer fViewer;

	/**
	 * The inlined annotation support used to draw CodeMining in the line spacing.
	 */
	private final InlinedAnnotationSupport fInlinedAnnotationSupport;

	/**
	 * The list of codemining providers.
	 */
	private List<ICodeMiningProvider> fCodeMiningProviders;

	/**
	 * The current progress monitor.
	 */
	private IProgressMonitor fMonitor;

	/**
	 * Constructor of codemining manager with the given arguments.
	 *
	 * @param viewer                   the source viewer
	 * @param inlinedAnnotationSupport the inlined annotation support used to draw code minings
	 * @param codeMiningProviders      the array of codemining providers, must not be empty
	 */
	public CodeMiningManager(ISourceViewer viewer, InlinedAnnotationSupport inlinedAnnotationSupport,
			ICodeMiningProvider[] codeMiningProviders) {
		Assert.isNotNull(viewer);
		Assert.isNotNull(inlinedAnnotationSupport);
		Assert.isNotNull(codeMiningProviders);
		fViewer= viewer;
		fInlinedAnnotationSupport= inlinedAnnotationSupport;
		setCodeMiningProviders(codeMiningProviders);
	}

	/**
	 * Set the codemining providers.
	 *
	 * @param codeMiningProviders the codemining providers.
	 */
	public void setCodeMiningProviders(ICodeMiningProvider[] codeMiningProviders) {
		cancel();
		if (fCodeMiningProviders != null) {
			fCodeMiningProviders.stream().forEach(ICodeMiningProvider::dispose);
		}
		fCodeMiningProviders= Arrays.asList(codeMiningProviders);
	}

	/**
	 * Uninstalls this codemining manager.
	 */
	public void uninstall() {
		cancel();
		if (fInlinedAnnotationSupport != null) {
			fInlinedAnnotationSupport.updateAnnotations(Collections.emptySet());
		}
	}

	/**
	 * Collect, resolve and render the code minings of the viewer.
	 */
	@Override
	public void run() {
		if (fViewer == null || fInlinedAnnotationSupport == null || fCodeMiningProviders == null
				|| fCodeMiningProviders.isEmpty() || fViewer.getAnnotationModel() == null) {
			return;
		}
		// Cancel the last progress monitor to cancel last resolve and render of code
		// minings
		cancel();
		// Update the code minings
		updateCodeMinings();
	}

	/**
	 * Update the code minings.
	 */
	private void updateCodeMinings() {
		// Refresh the code minings by using the new progress monitor.
		fMonitor= new CancellationExceptionMonitor();
		IProgressMonitor monitor= fMonitor;
		// Collect the code minings for the viewer
		getCodeMinings(fViewer, fCodeMiningProviders, monitor).thenAccept(symbols -> {
			// check if request was canceled.
			monitor.isCanceled();
			// then group code minings by lines position
			Map<Position, List<ICodeMining>> groups= groupByLines(symbols, fCodeMiningProviders);
			// resolve and render code minings
			renderCodeMinings(groups, fViewer, monitor);
		});
	}

	/**
	 * Cancel the codemining process.
	 */
	private void cancel() {
		// Cancel the last progress monitor.
		if (fMonitor != null) {
			fMonitor.setCanceled(true);
		}
	}

	private static void logCodeMiningProviderException(Throwable e) {
		if (e != null && (e instanceof CancellationException || (e.getCause() != null && e.getCause() instanceof CancellationException))) {
			return;
		}
		String PLUGIN_ID= "org.eclipse.jface.text"; //$NON-NLS-1$
		Bundle plugin= Platform.getBundle(PLUGIN_ID);
		if (plugin != null) {
			// In OSGi context, uses Platform Text log
			ILog log= Platform.getLog(plugin);
			log.log(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, e.getMessage(), e));
		} else {
			// In java main context, print stack trace
			System.err.println("Error while code mining process: " + e.getMessage()); //$NON-NLS-1$
		}
	}

	/**
	 * Return the list of {@link CompletableFuture} which provides the list of {@link ICodeMining}
	 * for the given <code>viewer</code> by using the given providers.
	 *
	 * @param viewer    the text viewer.
	 * @param providers the CodeMining list providers.
	 * @param monitor   the progress monitor.
	 * @return the list of {@link CompletableFuture} which provides the list of {@link ICodeMining}
	 *         for the given <code>viewer</code> by using the given providers.
	 */
	private static CompletableFuture<List<? extends ICodeMining>> getCodeMinings(ITextViewer viewer,
			List<ICodeMiningProvider> providers, IProgressMonitor monitor) {
		List<CompletableFuture<List<? extends ICodeMining>>> com= providers.stream()
				.map(provider -> provider.provideCodeMinings(viewer, monitor))
				.filter(c -> c != null)
				.map(future -> future.exceptionally(e -> {
					logCodeMiningProviderException(e);
					return Collections.emptyList();
				}))
				.collect(Collectors.toList());
		return CompletableFuture.allOf(com.toArray(new CompletableFuture[com.size()])).thenApply(
				v -> com.stream().map(CompletableFuture::join).flatMap(java.util.Collection::stream).collect(Collectors.toList()));
	}

	/**
	 * Returns a sorted Map which groups the given code minings by same position line.
	 *
	 * @param codeMinings list of code minings to group.
	 * @param providers   CodeMining providers used to retrieve code minings.
	 * @return a sorted Map which groups the given code minings by same position line.
	 */
	private static Map<Position, List<ICodeMining>> groupByLines(List<? extends ICodeMining> codeMinings,
			List<ICodeMiningProvider> providers) {
		// sort code minings by lineNumber and provider-rank if
		Collections.sort(codeMinings, (a, b) -> {
			if (a.getPosition().offset < b.getPosition().offset) {
				return -1;
			} else if (a.getPosition().offset > b.getPosition().offset) {
				return 1;
			} else if (providers.indexOf(a.getProvider()) < providers.indexOf(b.getProvider())) {
				return -1;
			} else if (providers.indexOf(a.getProvider()) > providers.indexOf(b.getProvider())) {
				return 1;
			} else {
				return 0;
			}
		});
		return codeMinings.stream().collect(Collectors.groupingBy(ICodeMining::getPosition, LinkedHashMap::new,
				Collectors.mapping(Function.identity(), Collectors.toList())));
	}

	/**
	 * Render the codemining grouped by line position.
	 *
	 * @param groups  code minings grouped by lines position
	 * @param viewer  the viewer
	 * @param monitor the progress monitor
	 */
	private void renderCodeMinings(Map<Position, List<ICodeMining>> groups, ISourceViewer viewer,
			IProgressMonitor monitor) {
		// check if request was canceled.
		monitor.isCanceled();
		IDocument document= viewer != null ? viewer.getDocument() : null;
		if (document == null) {
			// this case comes from when editor is closed before codemining rendered is
			// done.
			return;
		}
		Set<ICodeMiningAnnotation> annotationsToRedraw= new HashSet<>();
		Set<AbstractInlinedAnnotation> currentAnnotations= new HashSet<>();
		// Loop for grouped code minings
		groups.entrySet().stream().forEach(g -> {
			// check if request was canceled.
			monitor.isCanceled();

			Position pos= new Position(g.getKey().offset, g.getKey().length);
			List<ICodeMining> minings= g.getValue();
			boolean inLineHeader= !minings.isEmpty() ? (minings.get(0) instanceof LineHeaderCodeMining) : true;
			// Try to find existing annotation
			AbstractInlinedAnnotation ann= fInlinedAnnotationSupport.findExistingAnnotation(pos);
			if (ann == null) {
				// The annotation doesn't exists, create it.
				ann= inLineHeader ? new CodeMiningLineHeaderAnnotation(pos, viewer) : new CodeMiningLineContentAnnotation(pos, viewer);
			} else if (ann instanceof ICodeMiningAnnotation && ((ICodeMiningAnnotation) ann).isInVisibleLines()) {
				// annotation is in visible lines
				annotationsToRedraw.add((ICodeMiningAnnotation) ann);
			}
			((ICodeMiningAnnotation) ann).update(minings, monitor);
			currentAnnotations.add(ann);
		});
		// check if request was canceled.
		monitor.isCanceled();
		fInlinedAnnotationSupport.updateAnnotations(currentAnnotations);
		// redraw the existing codemining annotations since their content can change
		annotationsToRedraw.stream().forEach(ICodeMiningAnnotation::redraw);
	}

	/**
	 * Returns <code>true</code> if the given mining has a non empty label and <code>false</code>
	 * otherwise.
	 *
	 * @param mining the mining to check
	 * @return <code>true</code> if the given mining has a non empty label and <code>false</code>
	 *         otherwise.
	 */
	static boolean isValidMining(ICodeMining mining) {
		return mining != null && mining.getLabel() != null && !mining.getLabel().isEmpty();
	}

	/**
	 * Returns the valid code mining at the given location by using the bounds of codemining
	 * annotations which stores only the valid code mining.
	 *
	 * @param minings the list of mining of the codemining annotation.
	 * @param bounds  the bounds of the valid minings of the codemining annotation.
	 * @param x       the x location
	 * @param y       the y location
	 * @return the valid code mining at the given location by using the bounds of codemining
	 *         annotations which stores only the valid code mining.
	 */
	static ICodeMining getValidCodeMiningAtLocation(ICodeMining[] minings, List<Rectangle> bounds, int x, int y) {
		for (int i= 0; i < bounds.size(); i++) {
			Rectangle bound= bounds.get(i);
			if (bound.contains(x, y)) {
				return getCodeValidMiningAtIndex(minings, i);
			}
		}
		return null;
	}

	/**
	 * Returns the valid code mining at the given index.
	 *
	 * @param minings the list of minings
	 * @param index   the index
	 * @return the valid code mining at the given index.
	 */
	private static ICodeMining getCodeValidMiningAtIndex(ICodeMining[] minings, int index) {
		int validIndex= 0;
		for (ICodeMining mining : minings) {
			if (isValidMining(mining)) {
				if (validIndex == index) {
					return mining;
				}
				validIndex++;
			}
		}
		return null;
	}
}

Back to the top