TMF: Have IAnalysisModule#setTrace return boolean instead of throw exception
[deliverable/tracecompass.git] / org.eclipse.tracecompass.tmf.core / src / org / eclipse / tracecompass / tmf / core / analysis / TmfAbstractAnalysisModule.java
1 /*******************************************************************************
2 * Copyright (c) 2013, 2014 École Polytechnique de Montréal
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Geneviève Bastien - Initial API and implementation
11 *******************************************************************************/
12
13 package org.eclipse.tracecompass.tmf.core.analysis;
14
15 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.TimeUnit;
25
26 import org.eclipse.core.runtime.IProgressMonitor;
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.core.runtime.NullProgressMonitor;
29 import org.eclipse.core.runtime.Status;
30 import org.eclipse.core.runtime.jobs.Job;
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.eclipse.osgi.util.NLS;
34 import org.eclipse.tracecompass.common.core.NonNullUtils;
35 import org.eclipse.tracecompass.internal.tmf.core.Activator;
36 import org.eclipse.tracecompass.internal.tmf.core.TmfCoreTracer;
37 import org.eclipse.tracecompass.tmf.core.analysis.TmfAnalysisRequirement.ValuePriorityLevel;
38 import org.eclipse.tracecompass.tmf.core.component.TmfComponent;
39 import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
40 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
41 import org.eclipse.tracecompass.tmf.core.signal.TmfStartAnalysisSignal;
42 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
43 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
44 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
45 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
46
47 /**
48 * Base class that analysis modules main class may extend. It provides default
49 * behavior to some methods of the analysis module
50 *
51 * @author Geneviève Bastien
52 */
53 @NonNullByDefault
54 public abstract class TmfAbstractAnalysisModule extends TmfComponent implements IAnalysisModule {
55
56 private @Nullable String fId;
57 private boolean fAutomatic = false, fStarted = false;
58 private volatile @Nullable ITmfTrace fTrace;
59 private final Map<String, Object> fParameters = new HashMap<>();
60 private final List<String> fParameterNames = new ArrayList<>();
61 private final List<IAnalysisOutput> fOutputs = new ArrayList<>();
62 private List<IAnalysisParameterProvider> fParameterProviders = new ArrayList<>();
63 private @Nullable Job fJob = null;
64
65 private final Object syncObj = new Object();
66
67 /* Latch tracking if the analysis is completed or not */
68 private CountDownLatch fFinishedLatch = new CountDownLatch(0);
69
70 private boolean fAnalysisCancelled = false;
71
72 @Override
73 public boolean isAutomatic() {
74 return fAutomatic;
75 }
76
77 @Override
78 public String getName() {
79 return super.getName();
80 }
81
82 @Override
83 public void setName(String name) {
84 super.setName(name);
85 }
86
87 @Override
88 public void setId(String id) {
89 fId = id;
90 }
91
92 @Override
93 public String getId() {
94 String id = fId;
95 if (id == null) {
96 id = this.getClass().getCanonicalName();
97 if (id == null) {
98 /*
99 * Some types, like anonymous classes, don't have a canonical
100 * name. Just use the default name instead.
101 */
102 id = checkNotNull(this.getClass().getName());
103 }
104 fId = id;
105 }
106 return id;
107 }
108
109 @Override
110 public void setAutomatic(boolean auto) {
111 fAutomatic = auto;
112 }
113
114 /**
115 * @since 1.0
116 */
117 @Override
118 public boolean setTrace(ITmfTrace trace) throws TmfAnalysisException {
119 if (fTrace != null) {
120 throw new TmfAnalysisException(NLS.bind(Messages.TmfAbstractAnalysisModule_TraceSetMoreThanOnce, getName()));
121 }
122
123 TmfCoreTracer.traceAnalysis(getId(), trace, "setting trace for analysis"); //$NON-NLS-1$
124
125 /* Check that analysis can be executed */
126 if (!canExecute(trace)) {
127 return false;
128 }
129
130 fTrace = trace;
131 /* Get the parameter providers for this trace */
132 fParameterProviders = TmfAnalysisManager.getParameterProviders(this, trace);
133 for (IAnalysisParameterProvider provider : fParameterProviders) {
134 TmfCoreTracer.traceAnalysis(getId(), trace, "registered to parameter provider " + provider.getName()); //$NON-NLS-1$
135 provider.registerModule(this);
136 }
137 resetAnalysis();
138 fStarted = false;
139 return true;
140 }
141
142 /**
143 * Gets the trace
144 *
145 * @return The trace
146 */
147 protected @Nullable ITmfTrace getTrace() {
148 return fTrace;
149 }
150
151 @Override
152 public void addParameter(String name) {
153 fParameterNames.add(name);
154 }
155
156 @Override
157 public synchronized void setParameter(String name, @Nullable Object value) {
158 if (!fParameterNames.contains(name)) {
159 throw new RuntimeException(NLS.bind(Messages.TmfAbstractAnalysisModule_InvalidParameter, name, getName()));
160 }
161 Object oldValue = fParameters.get(name);
162 fParameters.put(name, value);
163 if ((value != null) && !(value.equals(oldValue))) {
164 parameterChanged(name);
165 }
166 }
167
168 @Override
169 public synchronized void notifyParameterChanged(String name) {
170 if (!fParameterNames.contains(name)) {
171 throw new RuntimeException(NLS.bind(Messages.TmfAbstractAnalysisModule_InvalidParameter, name, getName()));
172 }
173 Object oldValue = fParameters.get(name);
174 Object value = getParameter(name);
175 if ((value != null) && !(value.equals(oldValue))) {
176 parameterChanged(name);
177 }
178 }
179
180 /**
181 * Used to indicate that a parameter value has been changed
182 *
183 * @param name
184 * The name of the modified parameter
185 */
186 protected void parameterChanged(String name) {
187
188 }
189
190 @Override
191 public @Nullable Object getParameter(String name) {
192 Object paramValue = fParameters.get(name);
193 /* The parameter is not set, maybe it can be provided by someone else */
194 if ((paramValue == null) && (fTrace != null)) {
195 for (IAnalysisParameterProvider provider : fParameterProviders) {
196 paramValue = provider.getParameter(name);
197 if (paramValue != null) {
198 break;
199 }
200 }
201 }
202 return paramValue;
203 }
204
205 @Override
206 public boolean canExecute(ITmfTrace trace) {
207 for (TmfAnalysisRequirement requirement : getAnalysisRequirements()) {
208 if (!requirement.isFulfilled(trace)) {
209 return false;
210 }
211 }
212 return true;
213 }
214
215 /**
216 * Set the countdown latch back to 1 so the analysis can be executed again
217 */
218 protected void resetAnalysis() {
219 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "reset: ready for execution"); //$NON-NLS-1$
220 fFinishedLatch.countDown();
221 fFinishedLatch = new CountDownLatch(1);
222 }
223
224 /**
225 * Actually executes the analysis itself
226 *
227 * @param monitor
228 * Progress monitor
229 * @return Whether the analysis was completed successfully or not
230 * @throws TmfAnalysisException
231 * Method may throw an analysis exception
232 */
233 protected abstract boolean executeAnalysis(final IProgressMonitor monitor) throws TmfAnalysisException;
234
235 /**
236 * Indicate the analysis has been canceled. It is abstract to force
237 * implementing class to cleanup what they are running. This is called by
238 * the job's canceling. It does not need to be called directly.
239 */
240 protected abstract void canceling();
241
242 /**
243 * To be called when the analysis is completed, whether normally or because
244 * it was cancelled or for any other reason.
245 *
246 * It has to be called inside a synchronized block
247 */
248 private void setAnalysisCompleted() {
249 synchronized (syncObj) {
250 fStarted = false;
251 fJob = null;
252 fFinishedLatch.countDown();
253 }
254 }
255
256 /**
257 * Cancels the analysis if it is executing
258 */
259 @Override
260 public final void cancel() {
261 synchronized (syncObj) {
262 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "cancelled by application"); //$NON-NLS-1$
263 if (fJob != null) {
264 if (fJob.cancel()) {
265 fAnalysisCancelled = true;
266 setAnalysisCompleted();
267 }
268 }
269 fStarted = false;
270 }
271 }
272
273 @Override
274 public void dispose() {
275 super.dispose();
276 cancel();
277 }
278
279 /**
280 * Get an iterable list of all analyzes this analysis depends on. These
281 * analyzes will be scheduled before this analysis starts and the current
282 * analysis will not be considered completed until all the dependent
283 * analyzes are finished.
284 *
285 * @return An iterable list of analysis this analyzes depends on.
286 */
287 protected Iterable<IAnalysisModule> getDependentAnalyses() {
288 return checkNotNull(Collections.EMPTY_LIST);
289 }
290
291 private void execute(final ITmfTrace trace) {
292 /*
293 * TODO: The analysis in a job should be done at the analysis manager
294 * level instead of depending on this abstract class implementation,
295 * otherwise another analysis implementation may block the main thread
296 */
297
298 /* Do not execute if analysis has already run */
299 if (fFinishedLatch.getCount() == 0) {
300 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "already executed"); //$NON-NLS-1$
301 return;
302 }
303
304 /* Do not execute if analysis already running */
305 synchronized (syncObj) {
306 if (fStarted) {
307 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "already started, not starting again"); //$NON-NLS-1$
308 return;
309 }
310 fStarted = true;
311 }
312
313 /* Execute dependent analyses before creating the job for this one */
314 final Iterable<IAnalysisModule> dependentAnalyses = getDependentAnalyses();
315 for (IAnalysisModule module : dependentAnalyses) {
316 module.schedule();
317 }
318
319 /*
320 * Actual analysis will be run on a separate thread
321 */
322 String jobName = checkNotNull(NLS.bind(Messages.TmfAbstractAnalysisModule_RunningAnalysis, getName()));
323 fJob = new Job(jobName) {
324 @Override
325 protected @Nullable IStatus run(final @Nullable IProgressMonitor monitor) {
326 IProgressMonitor mon = monitor;
327 if (mon == null) {
328 mon = new NullProgressMonitor();
329 }
330 try {
331 mon.beginTask("", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
332 broadcast(new TmfStartAnalysisSignal(TmfAbstractAnalysisModule.this, TmfAbstractAnalysisModule.this));
333 TmfCoreTracer.traceAnalysis(TmfAbstractAnalysisModule.this.getId(), TmfAbstractAnalysisModule.this.getTrace(), "started"); //$NON-NLS-1$
334 fAnalysisCancelled = !executeAnalysis(mon);
335 for (IAnalysisModule module : dependentAnalyses) {
336 module.waitForCompletion(mon);
337 }
338 TmfCoreTracer.traceAnalysis(TmfAbstractAnalysisModule.this.getId(), TmfAbstractAnalysisModule.this.getTrace(), "finished"); //$NON-NLS-1$
339 } catch (TmfAnalysisException e) {
340 Activator.logError("Error executing analysis with trace " + trace.getName(), e); //$NON-NLS-1$
341 } finally {
342 synchronized (syncObj) {
343 mon.done();
344 setAnalysisCompleted();
345 }
346 TmfTraceManager.refreshSupplementaryFiles(trace);
347 }
348 if (!fAnalysisCancelled) {
349 return Status.OK_STATUS;
350 }
351 // Reset analysis so that it can be executed again.
352 resetAnalysis();
353 return Status.CANCEL_STATUS;
354 }
355
356 @Override
357 protected void canceling() {
358 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "job cancelled"); //$NON-NLS-1$
359 TmfAbstractAnalysisModule.this.canceling();
360 }
361
362 };
363 fJob.schedule();
364 }
365
366 @Override
367 public IStatus schedule() {
368 synchronized (syncObj) {
369 final ITmfTrace trace = getTrace();
370 if (trace == null) {
371 return new Status(IStatus.ERROR, Activator.PLUGIN_ID, String.format("No trace specified for analysis %s", getName())); //$NON-NLS-1$
372 }
373 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "scheduled"); //$NON-NLS-1$
374 execute(trace);
375 }
376
377 return checkNotNull(Status.OK_STATUS);
378 }
379
380 @Override
381 public Iterable<IAnalysisOutput> getOutputs() {
382 return fOutputs;
383 }
384
385 @Override
386 public void registerOutput(IAnalysisOutput output) {
387 if (!fOutputs.contains(output)) {
388 fOutputs.add(output);
389 }
390 }
391
392 @Override
393 public boolean waitForCompletion() {
394 try {
395 fFinishedLatch.await();
396 } catch (InterruptedException e) {
397 Activator.logError("Error while waiting for module completion", e); //$NON-NLS-1$
398 }
399 return !fAnalysisCancelled;
400 }
401
402 @Override
403 public boolean waitForCompletion(IProgressMonitor monitor) {
404 try {
405 while (!fFinishedLatch.await(500, TimeUnit.MILLISECONDS)) {
406 if (fAnalysisCancelled || monitor.isCanceled()) {
407 fAnalysisCancelled = true;
408 return false;
409 }
410 }
411 } catch (InterruptedException e) {
412 Activator.logError("Error while waiting for module completion", e); //$NON-NLS-1$
413 }
414 return !fAnalysisCancelled;
415 }
416
417 /**
418 * Signal handler for trace closing
419 *
420 * @param signal
421 * Trace closed signal
422 */
423 @TmfSignalHandler
424 public void traceClosed(TmfTraceClosedSignal signal) {
425 /* Is the closing trace the one that was requested? */
426 synchronized (syncObj) {
427 if (signal.getTrace() == fTrace) {
428 cancel();
429 fTrace = null;
430 }
431 }
432 }
433
434 /**
435 * Signal handler for when the trace becomes active
436 *
437 * @param signal
438 * Trace selected signal
439 */
440 @TmfSignalHandler
441 public void traceSelected(TmfTraceSelectedSignal signal) {
442 /*
443 * Since some parameter providers may handle many traces, we need to
444 * register the current trace to it
445 */
446 if (signal.getTrace() == fTrace) {
447 for (IAnalysisParameterProvider provider : fParameterProviders) {
448 provider.registerModule(this);
449 }
450 }
451 }
452
453 /**
454 * Returns a full help text to display
455 *
456 * @return Full help text for the module
457 */
458 protected String getFullHelpText() {
459 return NonNullUtils.nullToEmptyString(NLS.bind(
460 Messages.TmfAbstractAnalysisModule_AnalysisModule,
461 getName()));
462 }
463
464 /**
465 * Gets a short help text, to display as header to other help text
466 *
467 * @param trace
468 * The trace to show help for
469 *
470 * @return Short help text describing the module
471 */
472 protected String getShortHelpText(ITmfTrace trace) {
473 return NonNullUtils.nullToEmptyString(NLS.bind(
474 Messages.TmfAbstractAnalysisModule_AnalysisForTrace,
475 getName(), trace.getName()));
476 }
477
478 /**
479 * Gets the help text specific for a trace who does not have required
480 * characteristics for module to execute. The default implementation uses
481 * the analysis requirements.
482 *
483 * @param trace
484 * The trace to apply the analysis to
485 * @return Help text
486 */
487 protected String getTraceCannotExecuteHelpText(ITmfTrace trace) {
488 StringBuilder builder = new StringBuilder();
489 builder.append(NLS.bind(Messages.TmfAbstractAnalysisModule_AnalysisCannotExecute, getName()));
490 for (TmfAnalysisRequirement requirement : getAnalysisRequirements()) {
491 if (!requirement.isFulfilled(trace)) {
492 builder.append("\n\n"); //$NON-NLS-1$
493 builder.append(NLS.bind(Messages.TmfAnalysis_RequirementNotFulfilled, requirement.getType()));
494 builder.append("\n"); //$NON-NLS-1$
495 builder.append(NLS.bind(Messages.TmfAnalysis_RequirementMandatoryValues, requirement.getValues(ValuePriorityLevel.MANDATORY)));
496 Set<String> information = requirement.getInformation();
497 if (!information.isEmpty()) {
498 builder.append("\n"); //$NON-NLS-1$
499 builder.append(NLS.bind(Messages.TmfAnalysis_RequirementInformation, information));
500 }
501 }
502 }
503 return checkNotNull(builder.toString());
504 }
505
506 @Override
507 public String getHelpText() {
508 return getFullHelpText();
509 }
510
511 @Override
512 public String getHelpText(ITmfTrace trace) {
513 String text = getShortHelpText(trace);
514 if (!canExecute(trace)) {
515 text = text + "\n\n" + getTraceCannotExecuteHelpText(trace); //$NON-NLS-1$
516 }
517 return text;
518 }
519
520 @Override
521 public Iterable<TmfAnalysisRequirement> getAnalysisRequirements() {
522 return checkNotNull(Collections.EMPTY_SET);
523 }
524 }
This page took 0.043538 seconds and 5 git commands to generate.