tmf: Mark TmfTraceManager @NonNullByDefault
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.core / src / org / eclipse / tracecompass / tmf / core / trace / TmfTraceManager.java
1 /*******************************************************************************
2 * Copyright (c) 2013, 2015 Ericsson
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 * Alexandre Montplaisir - Initial API and implementation
11 * Patrick Tasse - Support selection range
12 * Xavier Raynaud - Support filters tracking
13 *******************************************************************************/
14
15 package org.eclipse.tracecompass.tmf.core.trace;
16
17 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.net.URISyntaxException;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.LinkedHashMap;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29
30 import org.apache.commons.io.FileUtils;
31 import org.eclipse.core.resources.IFile;
32 import org.eclipse.core.resources.IFolder;
33 import org.eclipse.core.resources.IProject;
34 import org.eclipse.core.resources.IResource;
35 import org.eclipse.core.runtime.CoreException;
36 import org.eclipse.core.runtime.URIUtil;
37 import org.eclipse.jdt.annotation.NonNull;
38 import org.eclipse.jdt.annotation.NonNullByDefault;
39 import org.eclipse.jdt.annotation.Nullable;
40 import org.eclipse.tracecompass.internal.tmf.core.Activator;
41 import org.eclipse.tracecompass.tmf.core.TmfCommonConstants;
42 import org.eclipse.tracecompass.tmf.core.signal.TmfEventFilterAppliedSignal;
43 import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
44 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
45 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
46 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
47 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceModelSignal;
48 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
49 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
50 import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal;
51 import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
52 import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
53 import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
54 import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
55
56 import com.google.common.collect.ImmutableSet;
57
58 /**
59 * Central trace manager for TMF. It tracks the currently opened traces and
60 * experiment, as well as the currently-selected time or time range and the
61 * current window time range for each one of those. It also tracks filters
62 * applied for each trace.
63 *
64 * It's a singleton class, so only one instance should exist (available via
65 * {@link #getInstance()}).
66 *
67 * @author Alexandre Montplaisir
68 */
69 @NonNullByDefault
70 public final class TmfTraceManager {
71
72 // ------------------------------------------------------------------------
73 // Attributes
74 // ------------------------------------------------------------------------
75
76 private final Map<ITmfTrace, TmfTraceContext> fTraces;
77
78 /** The currently-selected trace. Should always be part of the trace map */
79 private @Nullable ITmfTrace fCurrentTrace = null;
80
81 private static final String TEMP_DIR_NAME = ".temp"; //$NON-NLS-1$
82
83 // ------------------------------------------------------------------------
84 // Constructor
85 // ------------------------------------------------------------------------
86
87 private TmfTraceManager() {
88 fTraces = new LinkedHashMap<>();
89 TmfSignalManager.registerVIP(this);
90 }
91
92 /** Singleton instance */
93 private static @Nullable TmfTraceManager tm = null;
94
95 /**
96 * Get an instance of the trace manager.
97 *
98 * @return The trace manager
99 */
100 public static synchronized TmfTraceManager getInstance() {
101 TmfTraceManager mgr = tm;
102 if (mgr == null) {
103 mgr = new TmfTraceManager();
104 tm = mgr;
105 }
106 return mgr;
107 }
108
109 // ------------------------------------------------------------------------
110 // Accessors
111 // ------------------------------------------------------------------------
112
113 /**
114 * Get the currently selected trace (normally, the focused editor).
115 *
116 * @return The active trace, or <code>null</code> if there is no active
117 * trace
118 */
119 public synchronized @Nullable ITmfTrace getActiveTrace() {
120 return fCurrentTrace;
121 }
122
123 /**
124 * Get the trace set of the currently active trace.
125 *
126 * @return The active trace set. Empty (but non-null) if there is no
127 * currently active trace.
128 * @see #getTraceSet(ITmfTrace)
129 */
130 public synchronized Collection<ITmfTrace> getActiveTraceSet() {
131 final ITmfTrace trace = fCurrentTrace;
132 return getTraceSet(trace);
133 }
134
135 /**
136 * Get the currently-opened traces, as an unmodifiable set.
137 *
138 * @return A set containing the opened traces
139 */
140 public synchronized Set<ITmfTrace> getOpenedTraces() {
141 return Collections.unmodifiableSet(fTraces.keySet());
142 }
143
144 /**
145 * Get the editor file for an opened trace.
146 *
147 * @param trace
148 * the trace
149 * @return the editor file or null if the trace is not opened
150 */
151 public synchronized @Nullable IFile getTraceEditorFile(ITmfTrace trace) {
152 TmfTraceContext ctx = fTraces.get(trace);
153 if (ctx != null) {
154 return ctx.getEditorFile();
155 }
156 return null;
157 }
158
159 /**
160 * Get the {@link TmfTraceContext} of the current active trace. This can be
161 * used to retrieve the current active/selected time ranges and such.
162 *
163 * @return The trace's context.
164 * @since 1.0
165 */
166 public synchronized TmfTraceContext getCurrentTraceContext() {
167 TmfTraceContext curCtx = fTraces.get(fCurrentTrace);
168 if (curCtx == null) {
169 /* There are no traces opened at the moment. */
170 return TmfTraceContext.NULL_CONTEXT;
171 }
172 return curCtx;
173 }
174
175 // ------------------------------------------------------------------------
176 // Public utility methods
177 // ------------------------------------------------------------------------
178
179 /**
180 * Get the trace set of a given trace. For a standard trace, this is simply
181 * an array with only that trace in it. For experiments, this is an array of
182 * all the traces contained in this experiment.
183 *
184 * @param trace
185 * The trace or experiment. If it is null, an empty collection
186 * will be returned.
187 * @return The corresponding trace set.
188 */
189 public static Collection<ITmfTrace> getTraceSet(@Nullable ITmfTrace trace) {
190 if (trace == null) {
191 return ImmutableSet.of();
192 }
193 List<@NonNull ITmfTrace> traces = trace.getChildren(ITmfTrace.class);
194 if (traces.size() > 0) {
195 return ImmutableSet.copyOf(traces);
196 }
197 return ImmutableSet.of(trace);
198 }
199
200 /**
201 * Get the trace set of a given trace or experiment, including the
202 * experiment. For a standard trace, this is simply a set containing only
203 * that trace. For experiments, it is the set of all the traces contained in
204 * this experiment, along with the experiment.
205 *
206 * @param trace
207 * The trace or experiment. If it is null, an empty collection
208 * will be returned.
209 * @return The corresponding trace set, including the experiment.
210 */
211 public static Collection<ITmfTrace> getTraceSetWithExperiment(@Nullable ITmfTrace trace) {
212 if (trace == null) {
213 return ImmutableSet.of();
214 }
215 if (trace instanceof TmfExperiment) {
216 TmfExperiment exp = (TmfExperiment) trace;
217 List<ITmfTrace> traces = exp.getTraces();
218 Set<ITmfTrace> alltraces = new LinkedHashSet<>(traces);
219 alltraces.add(exp);
220 return ImmutableSet.copyOf(alltraces);
221 }
222 return Collections.singleton(trace);
223 }
224
225 /**
226 * Return the path (as a string) to the directory for supplementary files to
227 * use with a given trace. If no supplementary file directory has been
228 * configured, a temporary directory based on the trace's name will be
229 * provided.
230 *
231 * @param trace
232 * The trace
233 * @return The path to the supplementary file directory (trailing slash is
234 * INCLUDED!)
235 */
236 public static String getSupplementaryFileDir(ITmfTrace trace) {
237 IResource resource = trace.getResource();
238 if (resource == null) {
239 return getTemporaryDir(trace);
240 }
241
242 String supplDir = null;
243 try {
244 supplDir = resource.getPersistentProperty(TmfCommonConstants.TRACE_SUPPLEMENTARY_FOLDER);
245 } catch (CoreException e) {
246 return getTemporaryDir(trace);
247 }
248 return supplDir + File.separator;
249 }
250
251 /**
252 * Refresh the supplementary files resources for a trace, so it can pick up
253 * new files that got created.
254 *
255 * @param trace
256 * The trace for which to refresh the supplementary files
257 */
258 public static void refreshSupplementaryFiles(ITmfTrace trace) {
259 IResource resource = trace.getResource();
260 if (resource != null && resource.exists()) {
261 String supplFolderPath = getSupplementaryFileDir(trace);
262 IProject project = resource.getProject();
263 /* Remove the project's path from the supplementary path dir */
264 if (!supplFolderPath.startsWith(project.getLocation().toOSString())) {
265 Activator.logWarning(String.format("Supplementary files folder for trace %s is not within the project.", trace.getName())); //$NON-NLS-1$
266 return;
267 }
268 IFolder supplFolder = project.getFolder(supplFolderPath.substring(project.getLocationURI().getPath().length()));
269 if (supplFolder.exists()) {
270 try {
271 supplFolder.refreshLocal(IResource.DEPTH_INFINITE, null);
272 } catch (CoreException e) {
273 Activator.logError("Error refreshing resources", e); //$NON-NLS-1$
274 }
275 }
276 }
277 }
278
279 /**
280 * Delete the supplementary files of a given trace.
281 *
282 * @param trace
283 * The trace for which the supplementary files are to be deleted
284 * @since 2.2
285 */
286 public static void deleteSupplementaryFiles(ITmfTrace trace) {
287 try {
288 FileUtils.cleanDirectory(new File(TmfTraceManager.getSupplementaryFileDir(trace)));
289 } catch (IOException e) {
290 Activator.logError("Error deleting supplementary files for trace " + trace.getName(), e); //$NON-NLS-1$
291 }
292 refreshSupplementaryFiles(trace);
293 }
294
295 // ------------------------------------------------------------------------
296 // Signal handlers
297 // ------------------------------------------------------------------------
298
299 /**
300 * Signal handler for the traceOpened signal.
301 *
302 * @param signal
303 * The incoming signal
304 */
305 @TmfSignalHandler
306 public synchronized void traceOpened(final TmfTraceOpenedSignal signal) {
307 final ITmfTrace trace = signal.getTrace();
308 final IFile editorFile = signal.getEditorFile();
309 final ITmfTimestamp startTs = trace.getStartTime();
310
311 long offset = trace.getInitialRangeOffset().toNanos();
312 long endTime = startTs.toNanos() + offset;
313 final TmfTimeRange selectionRange = new TmfTimeRange(startTs, startTs);
314 final TmfTimeRange windowRange = new TmfTimeRange(startTs, TmfTimestamp.fromNanos(endTime));
315
316 final TmfTraceContext startCtx = trace.createTraceContext(selectionRange, windowRange, editorFile, null);
317
318 fTraces.put(trace, startCtx);
319
320 /* We also want to set the newly-opened trace as the active trace */
321 fCurrentTrace = trace;
322 }
323
324 /**
325 * Signal propagator
326 *
327 * @param signal
328 * any signal
329 * @since 2.0
330 */
331 @TmfSignalHandler
332 public synchronized void signalReceived(final TmfTraceModelSignal signal) {
333 fTraces.forEach((t, u) -> u.receive(signal));
334 }
335
336
337 /**
338 * Handler for the TmfTraceSelectedSignal.
339 *
340 * @param signal
341 * The incoming signal
342 */
343 @TmfSignalHandler
344 public synchronized void traceSelected(final TmfTraceSelectedSignal signal) {
345 final ITmfTrace newTrace = signal.getTrace();
346 if (!fTraces.containsKey(newTrace)) {
347 throw new RuntimeException();
348 }
349 fCurrentTrace = newTrace;
350 }
351
352 /**
353 * Signal handler for the filterApplied signal.
354 *
355 * @param signal
356 * The incoming signal
357 */
358 @TmfSignalHandler
359 public synchronized void filterApplied(TmfEventFilterAppliedSignal signal) {
360 final ITmfTrace newTrace = signal.getTrace();
361 TmfTraceContext context = fTraces.get(newTrace);
362 if (context == null) {
363 throw new RuntimeException();
364 }
365 final TmfTraceContext newContext = newTrace.createTraceContext(context.getSelectionRange(),
366 context.getWindowRange(),
367 context.getEditorFile(),
368 signal.getEventFilter());
369 newContext.setData(context.getData());
370 fTraces.put(newTrace, newContext);
371 }
372
373 /**
374 * Signal handler for the traceClosed signal.
375 *
376 * @param signal
377 * The incoming signal
378 */
379 @TmfSignalHandler
380 public synchronized void traceClosed(final TmfTraceClosedSignal signal) {
381 fTraces.remove(signal.getTrace());
382 if (fTraces.size() == 0) {
383 fCurrentTrace = null;
384 /*
385 * In other cases, we should receive a traceSelected signal that
386 * will indicate which trace is the new one.
387 */
388 }
389 }
390
391 /**
392 * Signal handler for the selection range signal.
393 *
394 * The current time of *all* traces whose range contains the requested new
395 * selection time range will be updated.
396 *
397 * @param signal
398 * The incoming signal
399 * @since 1.0
400 */
401 @TmfSignalHandler
402 public synchronized void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal) {
403 final ITmfTimestamp beginTs = signal.getBeginTime();
404 final ITmfTimestamp endTs = signal.getEndTime();
405
406 for (Map.Entry<ITmfTrace, TmfTraceContext> entry : fTraces.entrySet()) {
407 final ITmfTrace trace = entry.getKey();
408 if (beginTs.intersects(getValidTimeRange(trace)) || endTs.intersects(getValidTimeRange(trace))) {
409 TmfTraceContext prevCtx = checkNotNull(entry.getValue());
410
411 /*
412 * We want to update the selection range, but keep everything
413 * else the same as the previous trace context.
414 */
415 TmfTimeRange newSelectionRange = new TmfTimeRange(beginTs, endTs);
416 TmfTraceContext newCtx = trace.createTraceContext(newSelectionRange,
417 prevCtx.getWindowRange(),
418 prevCtx.getEditorFile(),
419 prevCtx.getFilter());
420 newCtx.setData(prevCtx.getData());
421 entry.setValue(newCtx);
422 }
423 }
424 }
425
426 /**
427 * Signal handler for the window range signal.
428 *
429 * The current window time range of *all* valid traces will be updated to
430 * the new requested times.
431 *
432 * @param signal
433 * The incoming signal
434 * @since 1.0
435 */
436 @TmfSignalHandler
437 public synchronized void windowRangeUpdated(final TmfWindowRangeUpdatedSignal signal) {
438 for (Map.Entry<ITmfTrace, TmfTraceContext> entry : fTraces.entrySet()) {
439 final ITmfTrace trace = entry.getKey();
440 final TmfTraceContext prevCtx = checkNotNull(entry.getValue());
441
442 final TmfTimeRange validTr = getValidTimeRange(trace);
443 if (validTr == null) {
444 return;
445 }
446
447 /* Determine the new time range */
448 TmfTimeRange targetTr = signal.getCurrentRange().getIntersection(validTr);
449 TmfTimeRange newWindowTr = (targetTr == null ? prevCtx.getWindowRange() : targetTr);
450
451 /* Keep the values from the old context, except for the window range */
452 TmfTraceContext newCtx = trace.createTraceContext(prevCtx.getSelectionRange(),
453 newWindowTr, prevCtx.getEditorFile(), prevCtx.getFilter());
454 newCtx.setData(prevCtx.getData());
455 entry.setValue(newCtx);
456 }
457 }
458
459 // ------------------------------------------------------------------------
460 // Private utility methods
461 // ------------------------------------------------------------------------
462
463 /**
464 * Return the valid time range of a trace (not the current window time
465 * range, but the range of all possible valid timestamps).
466 *
467 * For a real trace this is the whole range of the trace. For an experiment,
468 * it goes from the start time of the earliest trace to the end time of the
469 * latest one.
470 *
471 * @param trace
472 * The trace to check for
473 * @return The valid time span, or 'null' if the trace is not valid
474 */
475 private @Nullable TmfTimeRange getValidTimeRange(ITmfTrace trace) {
476 if (!fTraces.containsKey(trace)) {
477 /* Trace is not part of the currently opened traces */
478 return null;
479 }
480
481 List<ITmfTrace> traces = trace.getChildren(ITmfTrace.class);
482
483 if (traces.isEmpty()) {
484 /* "trace" is a single trace, return its time range directly */
485 return trace.getTimeRange();
486 }
487
488 if (traces.size() == 1) {
489 /* Trace is an experiment with only 1 trace */
490 return traces.get(0).getTimeRange();
491 }
492
493 /*
494 * Trace is an trace set with 2+ traces, so get the earliest start and
495 * the latest end.
496 */
497 ITmfTimestamp start = traces.get(0).getStartTime();
498 ITmfTimestamp end = traces.get(0).getEndTime();
499
500 for (int i = 1; i < traces.size(); i++) {
501 ITmfTrace curTrace = traces.get(i);
502 if (curTrace.getStartTime().compareTo(start) < 0) {
503 start = curTrace.getStartTime();
504 }
505 if (curTrace.getEndTime().compareTo(end) > 0) {
506 end = curTrace.getEndTime();
507 }
508 }
509 return new TmfTimeRange(start, end);
510 }
511
512 /**
513 * Get the temporary directory path. If there is an instance of Eclipse
514 * running, the temporary directory will reside under the workspace.
515 *
516 * @return the temporary directory path suitable to be passed to the
517 * java.io.File constructor without a trailing separator
518 */
519 public static String getTemporaryDirPath() {
520 // Get the workspace path from the properties
521 String property = System.getProperty("osgi.instance.area"); //$NON-NLS-1$
522 if (property != null) {
523 try {
524 File dir = URIUtil.toFile(URIUtil.fromString(property));
525 dir = new File(dir.getAbsolutePath() + File.separator + TEMP_DIR_NAME);
526 if (!dir.exists()) {
527 dir.mkdirs();
528 }
529 return dir.getAbsolutePath();
530 } catch (URISyntaxException e) {
531 Activator.logError(e.getLocalizedMessage(), e);
532 }
533 }
534 return System.getProperty("java.io.tmpdir"); //$NON-NLS-1$
535 }
536
537 /**
538 * Get a temporary directory based on a trace's name. We will create the
539 * directory if it doesn't exist, so that it's ready to be used.
540 */
541 private static String getTemporaryDir(ITmfTrace trace) {
542 String pathName = getTemporaryDirPath() +
543 File.separator +
544 trace.getName() +
545 File.separator;
546 File dir = new File(pathName);
547 if (!dir.exists()) {
548 dir.mkdirs();
549 }
550 return pathName;
551 }
552 }
This page took 0.044316 seconds and 5 git commands to generate.