analysis: combine nested ifs in TmfAbstractAnalysisModule
[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 && fJob.cancel()) {
264 fAnalysisCancelled = true;
265 setAnalysisCompleted();
266 }
267 fStarted = false;
268 }
269 }
270
271 @Override
272 public void dispose() {
273 super.dispose();
274 cancel();
275 }
276
277 /**
278 * Get an iterable list of all analyzes this analysis depends on. These
279 * analyzes will be scheduled before this analysis starts and the current
280 * analysis will not be considered completed until all the dependent
281 * analyzes are finished.
282 *
283 * @return An iterable list of analysis this analyzes depends on.
284 */
285 protected Iterable<IAnalysisModule> getDependentAnalyses() {
286 return checkNotNull(Collections.EMPTY_LIST);
287 }
288
289 private void execute(final ITmfTrace trace) {
290 /*
291 * TODO: The analysis in a job should be done at the analysis manager
292 * level instead of depending on this abstract class implementation,
293 * otherwise another analysis implementation may block the main thread
294 */
295
296 /* Do not execute if analysis has already run */
297 if (fFinishedLatch.getCount() == 0) {
298 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "already executed"); //$NON-NLS-1$
299 return;
300 }
301
302 /* Do not execute if analysis already running */
303 synchronized (syncObj) {
304 if (fStarted) {
305 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "already started, not starting again"); //$NON-NLS-1$
306 return;
307 }
308 fStarted = true;
309 }
310
311 /* Execute dependent analyses before creating the job for this one */
312 final Iterable<IAnalysisModule> dependentAnalyses = getDependentAnalyses();
313 for (IAnalysisModule module : dependentAnalyses) {
314 module.schedule();
315 }
316
317 /*
318 * Actual analysis will be run on a separate thread
319 */
320 String jobName = checkNotNull(NLS.bind(Messages.TmfAbstractAnalysisModule_RunningAnalysis, getName()));
321 fJob = new Job(jobName) {
322 @Override
323 protected @Nullable IStatus run(final @Nullable IProgressMonitor monitor) {
324 IProgressMonitor mon = monitor;
325 if (mon == null) {
326 mon = new NullProgressMonitor();
327 }
328 try {
329 mon.beginTask("", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
330 broadcast(new TmfStartAnalysisSignal(TmfAbstractAnalysisModule.this, TmfAbstractAnalysisModule.this));
331 TmfCoreTracer.traceAnalysis(TmfAbstractAnalysisModule.this.getId(), TmfAbstractAnalysisModule.this.getTrace(), "started"); //$NON-NLS-1$
332 fAnalysisCancelled = !executeAnalysis(mon);
333 for (IAnalysisModule module : dependentAnalyses) {
334 module.waitForCompletion(mon);
335 }
336 TmfCoreTracer.traceAnalysis(TmfAbstractAnalysisModule.this.getId(), TmfAbstractAnalysisModule.this.getTrace(), "finished"); //$NON-NLS-1$
337 } catch (TmfAnalysisException e) {
338 Activator.logError("Error executing analysis with trace " + trace.getName(), e); //$NON-NLS-1$
339 } finally {
340 synchronized (syncObj) {
341 mon.done();
342 setAnalysisCompleted();
343 }
344 TmfTraceManager.refreshSupplementaryFiles(trace);
345 }
346 if (!fAnalysisCancelled) {
347 return Status.OK_STATUS;
348 }
349 // Reset analysis so that it can be executed again.
350 resetAnalysis();
351 return Status.CANCEL_STATUS;
352 }
353
354 @Override
355 protected void canceling() {
356 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "job cancelled"); //$NON-NLS-1$
357 TmfAbstractAnalysisModule.this.canceling();
358 }
359
360 };
361 fJob.schedule();
362 }
363
364 @Override
365 public IStatus schedule() {
366 synchronized (syncObj) {
367 final ITmfTrace trace = getTrace();
368 if (trace == null) {
369 return new Status(IStatus.ERROR, Activator.PLUGIN_ID, String.format("No trace specified for analysis %s", getName())); //$NON-NLS-1$
370 }
371 TmfCoreTracer.traceAnalysis(getId(), getTrace(), "scheduled"); //$NON-NLS-1$
372 execute(trace);
373 }
374
375 return checkNotNull(Status.OK_STATUS);
376 }
377
378 @Override
379 public Iterable<IAnalysisOutput> getOutputs() {
380 return fOutputs;
381 }
382
383 @Override
384 public void registerOutput(IAnalysisOutput output) {
385 if (!fOutputs.contains(output)) {
386 fOutputs.add(output);
387 }
388 }
389
390 @Override
391 public boolean waitForCompletion() {
392 try {
393 fFinishedLatch.await();
394 } catch (InterruptedException e) {
395 Activator.logError("Error while waiting for module completion", e); //$NON-NLS-1$
396 }
397 return !fAnalysisCancelled;
398 }
399
400 @Override
401 public boolean waitForCompletion(IProgressMonitor monitor) {
402 try {
403 while (!fFinishedLatch.await(500, TimeUnit.MILLISECONDS)) {
404 if (fAnalysisCancelled || monitor.isCanceled()) {
405 fAnalysisCancelled = true;
406 return false;
407 }
408 }
409 } catch (InterruptedException e) {
410 Activator.logError("Error while waiting for module completion", e); //$NON-NLS-1$
411 }
412 return !fAnalysisCancelled;
413 }
414
415 /**
416 * Signal handler for trace closing
417 *
418 * @param signal
419 * Trace closed signal
420 */
421 @TmfSignalHandler
422 public void traceClosed(TmfTraceClosedSignal signal) {
423 /* Is the closing trace the one that was requested? */
424 synchronized (syncObj) {
425 if (signal.getTrace() == fTrace) {
426 cancel();
427 fTrace = null;
428 }
429 }
430 }
431
432 /**
433 * Signal handler for when the trace becomes active
434 *
435 * @param signal
436 * Trace selected signal
437 */
438 @TmfSignalHandler
439 public void traceSelected(TmfTraceSelectedSignal signal) {
440 /*
441 * Since some parameter providers may handle many traces, we need to
442 * register the current trace to it
443 */
444 if (signal.getTrace() == fTrace) {
445 for (IAnalysisParameterProvider provider : fParameterProviders) {
446 provider.registerModule(this);
447 }
448 }
449 }
450
451 /**
452 * Returns a full help text to display
453 *
454 * @return Full help text for the module
455 */
456 protected String getFullHelpText() {
457 return NonNullUtils.nullToEmptyString(NLS.bind(
458 Messages.TmfAbstractAnalysisModule_AnalysisModule,
459 getName()));
460 }
461
462 /**
463 * Gets a short help text, to display as header to other help text
464 *
465 * @param trace
466 * The trace to show help for
467 *
468 * @return Short help text describing the module
469 */
470 protected String getShortHelpText(ITmfTrace trace) {
471 return NonNullUtils.nullToEmptyString(NLS.bind(
472 Messages.TmfAbstractAnalysisModule_AnalysisForTrace,
473 getName(), trace.getName()));
474 }
475
476 /**
477 * Gets the help text specific for a trace who does not have required
478 * characteristics for module to execute. The default implementation uses
479 * the analysis requirements.
480 *
481 * @param trace
482 * The trace to apply the analysis to
483 * @return Help text
484 */
485 protected String getTraceCannotExecuteHelpText(ITmfTrace trace) {
486 StringBuilder builder = new StringBuilder();
487 builder.append(NLS.bind(Messages.TmfAbstractAnalysisModule_AnalysisCannotExecute, getName()));
488 for (TmfAnalysisRequirement requirement : getAnalysisRequirements()) {
489 if (!requirement.isFulfilled(trace)) {
490 builder.append("\n\n"); //$NON-NLS-1$
491 builder.append(NLS.bind(Messages.TmfAnalysis_RequirementNotFulfilled, requirement.getType()));
492 builder.append("\n"); //$NON-NLS-1$
493 builder.append(NLS.bind(Messages.TmfAnalysis_RequirementMandatoryValues, requirement.getValues(ValuePriorityLevel.MANDATORY)));
494 Set<String> information = requirement.getInformation();
495 if (!information.isEmpty()) {
496 builder.append("\n"); //$NON-NLS-1$
497 builder.append(NLS.bind(Messages.TmfAnalysis_RequirementInformation, information));
498 }
499 }
500 }
501 return checkNotNull(builder.toString());
502 }
503
504 @Override
505 public String getHelpText() {
506 return getFullHelpText();
507 }
508
509 @Override
510 public String getHelpText(ITmfTrace trace) {
511 String text = getShortHelpText(trace);
512 if (!canExecute(trace)) {
513 text = text + "\n\n" + getTraceCannotExecuteHelpText(trace); //$NON-NLS-1$
514 }
515 return text;
516 }
517
518 @Override
519 public Iterable<TmfAnalysisRequirement> getAnalysisRequirements() {
520 return checkNotNull(Collections.EMPTY_SET);
521 }
522 }
This page took 0.068958 seconds and 6 git commands to generate.