| /******************************************************************************* |
| * Copyright (c) 2016, 2018 Ericsson |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Bernd Hufmann - Initial API and implementation |
| * Simon Delisle - Add time range selection support |
| *******************************************************************************/ |
| package org.eclipse.tracecompass.internal.tmf.ui.project.operations; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Queue; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.operation.ModalContext; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.tracecompass.internal.tmf.ui.Activator; |
| import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentElement; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentFolder; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceElement; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceFolder; |
| import org.eclipse.tracecompass.tmf.ui.project.model.UpdateTraceBoundsJob; |
| |
| import com.google.common.collect.Iterables; |
| |
| /** |
| * Operation to add traces to an experiment. |
| * |
| * @author Bernd Hufmann |
| */ |
| public class SelectTracesOperation implements IRunnableWithProgress { |
| |
| private final @Nullable TmfExperimentElement fExperimentElement; |
| private final @Nullable TmfTraceFolder fParentTraceFolder; |
| private final @Nullable List<TmfTraceElement> fTraceElements; |
| private final @Nullable List<IResource> fResources; |
| private final @Nullable Map<String, TmfTraceElement> fPreviousTraces; |
| private @NonNull IStatus fStatus = Status.OK_STATUS; |
| private final ITmfTimestamp fStartTimeRange; |
| private final ITmfTimestamp fEndTimeRange; |
| |
| /** |
| * Constructor |
| * |
| * @param experimentFolderElement |
| * workspace experiment folder containing the experiment |
| * @param experiment |
| * experiment folder where to add traces |
| * @param parentTraceFolder |
| * the parent trace folder containing the trace resources |
| * @param resources |
| * the trace resources to add to the experiment |
| */ |
| public SelectTracesOperation(@NonNull TmfExperimentFolder experimentFolderElement, @NonNull IFolder experiment, @NonNull TmfTraceFolder parentTraceFolder, @NonNull List<IResource> resources) { |
| this(experimentFolderElement.getExperiment(experiment), parentTraceFolder, null, resources, null, null, null); |
| } |
| |
| /** |
| * Constructor. It will add traces to given experiment and remove traces |
| * that don't exist anymore. |
| * |
| * @param experimentElement |
| * the experiment element to add the traces |
| * @param traces |
| * the trace elements |
| * @param previousTraces |
| * map of traces currently available in the experiment |
| */ |
| public SelectTracesOperation(@NonNull TmfExperimentElement experimentElement, @NonNull TmfTraceElement[] traces, @NonNull Map<String, TmfTraceElement> previousTraces) { |
| this(experimentElement, null, traces, null, previousTraces, null, null); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param experimentElement |
| * the experiment element to add the traces |
| * @param traces |
| * the trace elements |
| * @param previousTraces |
| * map of traces currently available in the experiment |
| * @param startTimeRange |
| * The start timestamp |
| * @param endTimeRange |
| * The end timestamp |
| */ |
| public SelectTracesOperation(@NonNull TmfExperimentElement experimentElement, @NonNull TmfTraceElement[] traces, @NonNull Map<String, TmfTraceElement> previousTraces, ITmfTimestamp startTimeRange, ITmfTimestamp endTimeRange) { |
| this(experimentElement, null, traces, null, previousTraces, startTimeRange, endTimeRange); |
| } |
| |
| // Full constructor for internal use only |
| private SelectTracesOperation(TmfExperimentElement experimentElement, TmfTraceFolder parentTraceFolder, TmfTraceElement[] traces, List<IResource> resources, Map<String, TmfTraceElement> previousTraces, ITmfTimestamp startTimeRange, |
| ITmfTimestamp endTimeRange) { |
| fExperimentElement = experimentElement; |
| fParentTraceFolder = parentTraceFolder; |
| if (traces == null) { |
| fTraceElements = null; |
| } else { |
| fTraceElements = new ArrayList<>(Arrays.asList(traces)); |
| } |
| fResources = resources; |
| fPreviousTraces = previousTraces; |
| if (startTimeRange != null && endTimeRange != null && startTimeRange.compareTo(endTimeRange) > 0) { |
| fStartTimeRange = endTimeRange; |
| fEndTimeRange = startTimeRange; |
| } else { |
| fStartTimeRange = startTimeRange; |
| fEndTimeRange = endTimeRange; |
| } |
| } |
| |
| @Override |
| public void run(IProgressMonitor progressMonitor) { |
| TmfExperimentElement experimentElement = fExperimentElement; |
| if (experimentElement == null) { |
| return; |
| } |
| |
| // Check if operation was cancelled. |
| boolean changed = false; |
| |
| Map<String, TmfTraceElement> previousTraces = new HashMap<>(); |
| if (fPreviousTraces != null) { |
| previousTraces = fPreviousTraces; |
| } |
| |
| List<TmfTraceElement> elements = fTraceElements; |
| if (elements == null) { |
| if ((fParentTraceFolder != null) && (fResources != null)) { |
| elements = fParentTraceFolder.getTraceElements(fResources); |
| } else { |
| return; |
| } |
| } |
| |
| Set<String> keys = previousTraces.keySet(); |
| int workLoad = elements.size() + keys.size(); |
| SubMonitor subMonitor = SubMonitor.convert(progressMonitor, workLoad); |
| |
| if (fStartTimeRange != null && fEndTimeRange != null) { |
| workLoad += elements.size(); |
| subMonitor.setWorkRemaining(workLoad); |
| elements = filterTraceElementByTimeRange(elements, subMonitor.newChild(elements.size())); |
| } |
| // We might have less elements now |
| subMonitor.setWorkRemaining(elements.size() + keys.size()); |
| |
| // Add the selected traces to the experiment |
| try { |
| for (TmfTraceElement trace : elements) { |
| ModalContext.checkCanceled(progressMonitor); |
| String name = trace.getElementPath(); |
| if (keys.contains(name)) { |
| subMonitor.setTaskName(Messages.SelectTracesWizardPage_TraceRemovalTask + " " + trace.getElementPath()); //$NON-NLS-1$ |
| keys.remove(name); |
| } else { |
| subMonitor.setTaskName(Messages.SelectTracesWizardPage_TraceSelectionTask + " " + trace.getElementPath()); //$NON-NLS-1$ |
| experimentElement.addTrace(trace, false); |
| changed = true; |
| } |
| subMonitor.worked(1); |
| } |
| |
| // Remove traces that were unchecked (thus left in fPreviousTraces) |
| for (Map.Entry<String, TmfTraceElement> entry : previousTraces.entrySet()) { |
| ModalContext.checkCanceled(progressMonitor); |
| TmfTraceElement trace = entry.getValue(); |
| subMonitor.setTaskName(Messages.SelectTracesWizardPage_TraceRemovalTask + " " + trace.getElementPath()); //$NON-NLS-1$ |
| |
| try { |
| experimentElement.removeTrace(trace); |
| } catch (CoreException e) { |
| Activator.getDefault().logError(Messages.SelectTracesWizardPage_SelectionError + " " + experimentElement.getName(), e); //$NON-NLS-1$ |
| } |
| changed = true; |
| subMonitor.worked(1); |
| } |
| if (changed) { |
| Display.getDefault().asyncExec(() -> { |
| experimentElement.closeEditors(); |
| experimentElement.deleteSupplementaryResources(); |
| }); |
| } |
| setStatus(Status.OK_STATUS); |
| } catch (InterruptedException e) { |
| setStatus(Status.CANCEL_STATUS); |
| Thread.currentThread().interrupt(); |
| } catch (Exception e) { |
| Activator.getDefault().logError(Messages.SelectTracesWizardPage_SelectionError, e); |
| setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, Messages.SelectTracesWizardPage_SelectionError, e)); |
| } |
| } |
| |
| /** |
| * Filter the given list according to the time range. |
| * |
| * @param elements |
| * List of TmfTraceElement to filter |
| * @param progressMonitor |
| * Progress monitor for the job |
| * @return A filtered list that contain traces that are in or overlap the |
| * time range |
| */ |
| private List<TmfTraceElement> filterTraceElementByTimeRange(List<TmfTraceElement> elements, IProgressMonitor progressMonitor) { |
| SubMonitor subMonitor = SubMonitor.convert(progressMonitor, elements.size()); |
| List<TmfTraceElement> filteredElements = new ArrayList<>(); |
| List<TmfTraceElement> knownBounds = new ArrayList<>(); |
| Queue<TmfTraceElement> unknownBounds = new ConcurrentLinkedQueue<>(); |
| Iterable<TmfTraceElement> traceElements = Iterables.concat(knownBounds, unknownBounds); |
| |
| for (TmfTraceElement tmfTraceElement : elements) { |
| if (tmfTraceElement.getStartTime() == null || tmfTraceElement.getEndTime() == null) { |
| unknownBounds.add(tmfTraceElement); |
| } else { |
| knownBounds.add(tmfTraceElement); |
| } |
| } |
| |
| if (!unknownBounds.isEmpty()) { |
| UpdateTraceBoundsJob job = new UpdateTraceBoundsJob(Messages.SelectTracesWizardPage_UpdateTraceBoundsJobName, new ConcurrentLinkedQueue<>(unknownBounds)); |
| job.run(subMonitor.newChild(1)); |
| } |
| |
| for (TmfTraceElement element : traceElements) { |
| if (isInTimeRange(element.getStartTime(), element.getEndTime())) { |
| filteredElements.add(element); |
| } |
| } |
| return filteredElements; |
| } |
| |
| /** |
| * Check if the time range overlap or is completely included in this |
| * SelectTracesOperation's time range. |
| * |
| * @param traceStartTime |
| * Start timestamp of the trace |
| * @param traceEndTime |
| * End timestamp of the trace |
| * @return True if the the trace is in or overlap the time range |
| */ |
| private boolean isInTimeRange(ITmfTimestamp traceStartTime, ITmfTimestamp traceEndTime) { |
| return traceStartTime.compareTo(fEndTimeRange) <= 0 && traceEndTime.compareTo(fStartTimeRange) >= 0; |
| } |
| |
| /** |
| * Set the status for this operation |
| * |
| * @param status |
| * the status |
| */ |
| protected void setStatus(@NonNull IStatus status) { |
| fStatus = status; |
| } |
| |
| /** |
| * Returns the status of the operation execution. |
| * |
| * @return status |
| */ |
| public @NonNull IStatus getStatus() { |
| return fStatus; |
| } |
| |
| } |