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