TMF: Add extension point for parameter providers
[deliverable/tracecompass.git] / tmf / 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.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.TimeUnit;
26
27 import org.eclipse.core.runtime.IProgressMonitor;
28 import org.eclipse.core.runtime.IStatus;
29 import org.eclipse.core.runtime.NullProgressMonitor;
30 import org.eclipse.core.runtime.Status;
31 import org.eclipse.core.runtime.jobs.Job;
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.eclipse.osgi.util.NLS;
35 import org.eclipse.tracecompass.common.core.NonNullUtils;
36 import org.eclipse.tracecompass.internal.tmf.core.Activator;
37 import org.eclipse.tracecompass.internal.tmf.core.TmfCoreTracer;
38 import org.eclipse.tracecompass.tmf.core.analysis.TmfAnalysisRequirement.ValuePriorityLevel;
39 import org.eclipse.tracecompass.tmf.core.component.TmfComponent;
40 import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
41 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
42 import org.eclipse.tracecompass.tmf.core.signal.TmfStartAnalysisSignal;
43 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
44 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
45 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
46 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
47
48 /**
49 * Base class that analysis modules main class may extend. It provides default
50 * behavior to some methods of the analysis module
51 *
52 * @author Geneviève Bastien
53 */
54 @NonNullByDefault
55 public abstract class TmfAbstractAnalysisModule extends TmfComponent implements IAnalysisModule {
56
57 private @Nullable String fId;
58 private boolean fAutomatic = false, fStarted = false;
59 private volatile @Nullable ITmfTrace fTrace;
60 private final Map<String, Object> fParameters = new HashMap<>();
61 private final List<String> fParameterNames = new ArrayList<>();
62 private final List<IAnalysisOutput> fOutputs = new ArrayList<>();
63 private Set<IAnalysisParameterProvider> fParameterProviders = new HashSet<>();
64 private @Nullable Job fJob = null;
65
66 private final Object syncObj = new Object();
67
68 /* Latch tracking if the analysis is completed or not */
69 private CountDownLatch fFinishedLatch = new CountDownLatch(0);
70
71 private boolean fAnalysisCancelled = false;
72
73 @Override
74 public boolean isAutomatic() {
75 return fAutomatic;
76 }
77
78 @Override
79 public String getName() {
80 return super.getName();
81 }
82
83 @Override
84 public void setName(String name) {
85 super.setName(name);
86 }
87
88 @Override
89 public void setId(String id) {
90 fId = id;
91 }
92
93 @Override
94 public String getId() {
95 String id = fId;
96 if (id == null) {
97 id = this.getClass().getCanonicalName();
98 if (id == null) {
99 /*
100 * Some types, like anonymous classes, don't have a canonical
101 * name. Just use the default name instead.
102 */
103 id = checkNotNull(this.getClass().getName());
104 }
105 fId = id;
106 }
107 return id;
108 }
109
110 @Override
111 public void setAutomatic(boolean auto) {
112 fAutomatic = auto;
113 }
114
115 /**
116 * @since 1.0
117 */
118 @Override
119 public boolean setTrace(ITmfTrace trace) throws TmfAnalysisException {
120 if (fTrace != null) {
121 throw new TmfAnalysisException(NLS.bind(Messages.TmfAbstractAnalysisModule_TraceSetMoreThanOnce, getName()));
122 }
123
124 TmfCoreTracer.traceAnalysis(getId(), trace, "setting trace for analysis"); //$NON-NLS-1$
125
126 /* Check that analysis can be executed */
127 fTrace = trace;
128 if (!canExecute(trace)) {
129 fTrace = null;
130 return false;
131 }
132
133 /* Get the parameter providers for this trace */
134 fParameterProviders = TmfAnalysisManager.getParameterProvidersForModule(this, trace);
135 for (IAnalysisParameterProvider provider : fParameterProviders) {
136 TmfCoreTracer.traceAnalysis(getId(), trace, "registered to parameter provider " + provider.getName()); //$NON-NLS-1$
137 provider.registerModule(this);
138 }
139 resetAnalysis();
140 fStarted = false;
141 return true;
142 }
143
144 /**
145 * Gets the trace
146 *
147 * @return The trace
148 */
149 protected @Nullable ITmfTrace getTrace() {
150 return fTrace;
151 }
152
153 @Override
154 public void addParameter(String name) {
155 fParameterNames.add(name);
156 }
157
158 @Override
159 public synchronized void setParameter(String name, @Nullable Object value) {
160 if (!fParameterNames.contains(name)) {
161 throw new RuntimeException(NLS.bind(Messages.TmfAbstractAnalysisModule_InvalidParameter, name, getName()));
162 }
163 Object oldValue = fParameters.get(name);
164 fParameters.put(name, value);
165 if ((value != null) && !(value.equals(oldValue))) {
166 parameterChanged(name);
167 }
168 }
169
170 @Override
171 public synchronized void notifyParameterChanged(String name) {
172 if (!fParameterNames.contains(name)) {
173 throw new RuntimeException(NLS.bind(Messages.TmfAbstractAnalysisModule_InvalidParameter, name, getName()));
174 }
175 Object oldValue = fParameters.get(name);
176 Object value = getParameter(name);
177 if ((value != null) && !(value.equals(oldValue))) {
178 parameterChanged(name);
179 }
180 }
181
182 /**
183 * Used to indicate that a parameter value has been changed
184 *
185 * @param name
186 * The name of the modified parameter
187 */
188 protected void parameterChanged(String name) {
189
190 }
191
192 @Override
193 public @Nullable Object getParameter(String name) {
194 Object paramValue = fParameters.get(name);
195 /* The parameter is not set, maybe it can be provided by someone else */
196 if ((paramValue == null) && (fTrace != null)) {
197 for (IAnalysisParameterProvider provider : fParameterProviders) {
198 paramValue = provider.getParameter(name);
199 if (paramValue != null) {
200 break;
201 }
202 }
203 }
204 return paramValue;
205 }
206
207 @Override
208 public boolean canExecute(ITmfTrace trace) {
209 for (TmfAnalysisRequirement requirement : getAnalysisRequirements()) {
210 if (!requirement.isFulfilled(trace)) {
211 return false;
212 }
213 }
214 return true;
215 }
216
217 /**
218 * Set the countdown latch back to 1 so the analysis can be executed again
219 */
220 protected void resetAnalysis() {
221 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "reset: ready for execution"); //$NON-NLS-1$
222 fFinishedLatch.countDown();
223 fFinishedLatch = new CountDownLatch(1);
224 }
225
226 /**
227 * Actually executes the analysis itself
228 *
229 * @param monitor
230 * Progress monitor
231 * @return Whether the analysis was completed successfully or not
232 * @throws TmfAnalysisException
233 * Method may throw an analysis exception
234 */
235 protected abstract boolean executeAnalysis(final IProgressMonitor monitor) throws TmfAnalysisException;
236
237 /**
238 * Indicate the analysis has been canceled. It is abstract to force
239 * implementing class to cleanup what they are running. This is called by
240 * the job's canceling. It does not need to be called directly.
241 */
242 protected abstract void canceling();
243
244 /**
245 * To be called when the analysis is completed, whether normally or because
246 * it was cancelled or for any other reason.
247 *
248 * It has to be called inside a synchronized block
249 */
250 private void setAnalysisCompleted() {
251 synchronized (syncObj) {
252 fStarted = false;
253 fJob = null;
254 fFinishedLatch.countDown();
255 }
256 }
257
258 /**
259 * Cancels the analysis if it is executing
260 */
261 @Override
262 public final void cancel() {
263 synchronized (syncObj) {
264 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "cancelled by application"); //$NON-NLS-1$
265 if (fJob != null && fJob.cancel()) {
266 fAnalysisCancelled = true;
267 setAnalysisCompleted();
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.043938 seconds and 6 git commands to generate.