blob: b638322aa2668c46432345aeb0e800f201b7f49c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2018 EfficiOS Inc., Alexandre Montplaisir and others
*
* 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
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.ui.project.handlers;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.tracecompass.internal.tmf.ui.project.operations.NewExperimentOperation;
import org.eclipse.tracecompass.internal.tmf.ui.project.operations.TmfWorkspaceModifyOperation;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.snapshot.StateSnapshot;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
import org.eclipse.tracecompass.tmf.core.component.ITmfEventProvider;
import org.eclipse.tracecompass.tmf.core.project.model.TmfTraceType;
import org.eclipse.tracecompass.tmf.core.project.model.TraceTypeHelper;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfAnalysisModuleWithStateSystems;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.core.trace.trim.ITmfTrimmableTrace;
import org.eclipse.tracecompass.tmf.ui.project.model.ITmfProjectModelElement;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfCommonProjectElement;
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.TmfOpenTraceHelper;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectElement;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectRegistry;
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.TmfTraceTypeUIUtils;
import org.eclipse.tracecompass.tmf.ui.project.model.TraceUtils;
import org.eclipse.tracecompass.tmf.ui.project.wizards.TrimTraceDialog;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.handlers.IHandlerService;
import com.google.common.collect.Lists;
/**
* Handler for the Trace Trim operation.
*
* @author Alexandre Montplaisir
*/
@NonNullByDefault
public class TrimTraceHandler extends AbstractHandler {
private static final class TraceToTrim {
private ITmfTrace fTrace;
private @Nullable ITmfTrimmableTrace fTrimmable;
private Path fDestinationPath;
private Collection<TraceToTrim> fChildren = new ArrayList<>();
private TmfCommonProjectElement fElement;
private @Nullable IFolder fFolder = null;
private @Nullable ITmfProjectModelElement fDestElement = null;
private TmfCommonProjectElement fSourceElement;
public static @Nullable TraceToTrim create(TmfCommonProjectElement element, Path destination) {
ITmfTrace trace = element.getTrace();
if (trace != null) {
return new TraceToTrim(trace, element, element, destination);
}
if (element instanceof TmfTraceElement) {
TmfTraceElement traceElement = (TmfTraceElement) element;
trace = traceElement.getTrace();
if (trace != null) {
return new TraceToTrim(trace, element, element, destination);
}
}
return null;
}
private static final @Nullable TraceToTrim create(TraceToTrim parent, TmfTraceElement traceElement) {
TmfTraceElement elementUnderTraceFolder = traceElement.getElementUnderTraceFolder();
ITmfTrace trace = traceElement.getTrace();
if (trace != null) {
List<String> elems = new ArrayList<>();
ITmfProjectModelElement tempTraceElement = elementUnderTraceFolder;
while (tempTraceElement != null) {
elems.add(Objects.requireNonNull(tempTraceElement.getName()));
tempTraceElement = tempTraceElement.getParent();
}
TraceTypeHelper traceType = TmfTraceType.getTraceType(elementUnderTraceFolder.getTraceType());
String end = elems.remove(0);
elems = Lists.reverse(elems);
TmfTraceFolder tracesFolder = elementUnderTraceFolder.getProject().getTracesFolder();
if (tracesFolder == null) {
return null;
}
IFolder folder = tracesFolder.getResource();
// remove project name
elems.remove(0);
// remove "Traces"
elems.set(0, String.valueOf(parent.fDestinationPath.getFileName()));
for (String elem : elems) {
folder = folder.getFolder(elem);
if (!folder.exists()) {
try {
folder.create(true, true, new NullProgressMonitor());
} catch (CoreException e) {
return null;
}
}
}
folder = folder.getFolder(end);
if (!traceType.isDirectoryTraceType() && !folder.exists()) {
try {
folder.create(true, true, new NullProgressMonitor());
folder.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
} catch (CoreException e) {
return null;
}
}
TmfTraceElement te = new TmfTraceElement(elementUnderTraceFolder.getName(), folder, tracesFolder) {
@Override
public String getTraceType() {
return String.valueOf(traceElement.getTraceType());
}
@Override
public void refreshTraceType() {
// Do nothing, we have the tracetype above
}
@Override
public IResource[] getSupplementaryResources() {
return traceElement.getSupplementaryResources();
}
};
return new TraceToTrim(trace, elementUnderTraceFolder, te, (new File(te.getName())).toPath());
}
return null;
}
public TraceToTrim(ITmfTrace trace, TmfCommonProjectElement sourceElement, TmfCommonProjectElement element, Path destination) {
fTrace = trace;
fSourceElement = sourceElement;
fElement = element;
if (trace instanceof ITmfTrimmableTrace) {
fTrimmable = (ITmfTrimmableTrace) trace;
} else {
fTrimmable = null;
}
fFolder = (IFolder) element.getResource();
// Note: getParent.getLocation is almost never a link. The
// folder.getLocation may be a link.
fDestinationPath = fFolder != null ? fFolder.getParent().getLocation().append(destination.toString()).toFile().toPath() : destination;
// go through children
for (TmfTraceElement childElement : element.getTraces()) {
TraceToTrim create = TraceToTrim.create(this, childElement);
if (create != null) {
fChildren.add(create);
}
}
}
public IStatus trim(TmfTimeRange tr, IProgressMonitor mon) {
if (mon.isCanceled()) {
return Status.CANCEL_STATUS;
}
SubMonitor monitor = SubMonitor.convert(mon, 3);
List<@NonNull ITmfAnalysisModuleWithStateSystems> statesystemModules = new ArrayList<>();
for (IAnalysisModule module : fTrace.getAnalysisModules()) {
if (module instanceof ITmfAnalysisModuleWithStateSystems) {
statesystemModules.add((ITmfAnalysisModuleWithStateSystems) module);
}
}
monitor.worked(1);
long snapshotTime = tr.getStartTime().toNanos();
/*
* Perform the trace-specific trim operation. This should create the
* trace file(s) in the destination path.
*/
Path returnPath = fDestinationPath;
if (fTrimmable != null) {
try {
returnPath = fTrimmable.trim(tr, returnPath, monitor);
} catch (CoreException e) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "An error occurred writing the state systems snapshots", e); //$NON-NLS-1$
}
}
if (returnPath == null) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to trim trace. " + fTrimmable); //$NON-NLS-1$
}
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
monitor.worked(1);
SubMonitor ssMon = SubMonitor.convert(mon, statesystemModules.size());
/* Write the snapshot files in the new trace's location. */
try {
for (ITmfAnalysisModuleWithStateSystems module : statesystemModules) {
ssMon.split(1);
Map<String, Integer> versions = module.getProviderVersions();
Iterable<ITmfStateSystem> sss = module.getStateSystems();
for (ITmfStateSystem ss : sss) {
Integer version = versions.get(ss.getSSID());
long currentEndTime = ss.getCurrentEndTime();
if (snapshotTime <= currentEndTime && version != null) {
StateSnapshot snapshot = new StateSnapshot(ss, Math.max(snapshotTime, ss.getStartTime()), Math.min(currentEndTime, tr.getEndTime().toNanos()), version);
snapshot.write(returnPath);
}
}
}
} catch (IOException e) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "An error occurred writing the state systems snapshots", e); //$NON-NLS-1$
}
for (TraceToTrim child : fChildren) {
IStatus status = child.trim(tr, monitor);
if (!status.isOK()) {
return status;
}
}
return Status.OK_STATUS;
}
public IStatus importTrace(IProgressMonitor monitor) {
try {
IFolder folder = fFolder;
if (folder == null) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Folder is null"); //$NON-NLS-1$
}
ITmfProjectModelElement destElement;
for (TraceToTrim child : fChildren) {
IStatus status = child.importTrace(monitor);
if (!status.isOK()) {
return status;
}
}
TraceTypeHelper traceTypeHelper = TmfTraceType.getTraceType(fElement.getTraceType());
if (fElement instanceof TmfExperimentElement) {
TmfExperimentElement experimentElement = (TmfExperimentElement) fElement;
// create an experiment
TmfExperimentFolder experimentsFolder = experimentElement.getProject().getExperimentsFolder();
if (experimentsFolder == null) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error getting experiment folder"); //$NON-NLS-1$
}
List<TmfTraceElement> children = new ArrayList<>();
for (TraceToTrim child : fChildren) {
ITmfProjectModelElement childDest = child.fDestElement;
if (childDest instanceof TmfTraceElement) {
children.add((TmfTraceElement) childDest);
}
}
String name = fDestinationPath.getFileName().toString();
if (name == null) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "could not create element"); //$NON-NLS-1$
}
NewExperimentOperation newExperimentOperation = new NewExperimentOperation(experimentsFolder, name, null, children);
newExperimentOperation.run(monitor);
experimentsFolder.refresh();
destElement = experimentsFolder.getChild(name);
if (destElement == null) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "could not create element"); //$NON-NLS-1$
}
TmfTraceTypeUIUtils.setTraceType(destElement.getResource(), traceTypeHelper);
if (destElement instanceof TmfExperimentElement) {
TmfExperimentElement expElement = (TmfExperimentElement) destElement;
for (TraceToTrim child : fChildren) {
ITmfProjectModelElement childDest = child.fDestElement;
if (childDest instanceof TmfTraceElement && expElement.getChild(childDest.getName()) == null) {
expElement.addTrace((TmfTraceElement) childDest, false);
}
}
fSourceElement.copySupplementaryFolder(destElement.getName());
destElement.refresh();
fDestElement = destElement;
}
} else if (fElement instanceof TmfTraceElement) {
// create a trace
String path = (traceTypeHelper.isDirectoryTraceType() ? fDestinationPath : fDestinationPath.getParent()).toAbsolutePath().toString();
IPath destinationPath = org.eclipse.core.runtime.Path.fromOSString(path);
IResource traceResource = null;
String lastSegment = destinationPath.lastSegment();
IContainer tempFolder = folder.getParent();
tempFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
// Look for a common parent. In case the folders are oddly
// configured.
while (tempFolder != null && traceResource == null) {
traceResource = tempFolder.findMember(lastSegment);
tempFolder = tempFolder.getParent();
}
if (traceResource == null) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error finding common parent"); //$NON-NLS-1$
}
IStatus ret = TmfTraceTypeUIUtils.setTraceType(traceResource, traceTypeHelper);
if (!ret.isOK()) {
return ret;
}
ITmfProjectModelElement findElement = TmfProjectRegistry.findElement(traceResource, true);
if (findElement instanceof TmfCommonProjectElement) {
fSourceElement.copySupplementaryFolder(((TmfCommonProjectElement) findElement).getElementPath());
}
fDestElement = findElement;
}
} catch (CoreException e) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error creating link", e); //$NON-NLS-1$
}
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
public void open() {
if (fDestElement instanceof TmfCommonProjectElement) {
TmfOpenTraceHelper.openTraceFromElement((TmfCommonProjectElement) fDestElement);
}
}
public void createDir() throws IOException {
if (fElement instanceof TmfExperimentElement) {
for (TraceToTrim child : fChildren) {
child.createDir();
}
} else {
fDestinationPath.toAbsolutePath().toFile().mkdirs();
}
}
}
/**
* Can this trace be trimmed?
*
* @param element
* the element to test
* @return <code>true</code> if the trace or all its children can be
* trimmed.
*/
private static boolean isValid(@Nullable Object element) {
if (element instanceof ITmfTrimmableTrace) {
return true;
}
if (!(element instanceof ITmfTrace)) {
return false;
}
ITmfTrace trace = (ITmfTrace) element;
if (trace.getChildren().isEmpty()) {
return false;
}
for (ITmfEventProvider child : trace.getChildren()) {
if (!isValid(child)) {
return false;
}
}
return true;
}
@Override
public boolean isEnabled() {
IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench == null) {
return false;
}
@SuppressWarnings("null")
IHandlerService service = workbench.getService(IHandlerService.class);
// we need the current state, and the map, but the command and the
// trigger are
// not necessary for getCurrentSelection
ExecutionEvent executionEvent = new ExecutionEvent(null, Collections.emptyMap(), null, service.getCurrentState());
final Object element = HandlerUtil.getCurrentSelection(executionEvent);
if (!(element instanceof TreeSelection)) {
return false;
}
/*
* plugin.xml should have done type/count verification already
*/
Object firstElement = ((TreeSelection) element).getFirstElement();
ITmfTrace trace = null;
if (firstElement instanceof TmfCommonProjectElement) {
TmfCommonProjectElement traceElem = (TmfCommonProjectElement) firstElement;
if (traceElem instanceof TmfTraceElement) {
traceElem = ((TmfTraceElement) traceElem).getElementUnderTraceFolder();
}
trace = traceElem.getTrace();
}
if (trace == null || !isValid(trace)) {
return false;
}
/* Only enable the action if a time range is currently selected */
TmfTraceManager tm = TmfTraceManager.getInstance();
TmfTimeRange selectionRange = tm.getTraceContext(trace).getSelectionRange();
return !(selectionRange.getStartTime().equals(selectionRange.getEndTime()));
}
@Override
public @Nullable Object execute(@Nullable ExecutionEvent event) throws ExecutionException {
ISelection selection = HandlerUtil.getCurrentSelectionChecked(event);
Object firstElement = ((IStructuredSelection) selection).getFirstElement();
final TmfCommonProjectElement traceElem = (firstElement instanceof TmfTraceElement) ?
((TmfTraceElement) firstElement).getElementUnderTraceFolder() : (TmfCommonProjectElement) firstElement;
ITmfTrace trace = traceElem.getTrace();
if (trace == null || !isValid(trace)) {
/* That trace is not currently opened */
return null;
}
/* Retrieve the current time range */
final Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
TmfTraceManager tm = TmfTraceManager.getInstance();
TmfTimeRange timeRange = tm.getTraceContext(trace).getSelectionRange();
if (Objects.equals(timeRange.getStartTime(), timeRange.getEndTime())) {
MessageDialog.openError(shell, Messages.TrimTraces_InvalidTimeRange_DialogTitle, Messages.TrimTraces_InvalidTimeRange_DialogText);
return null;
}
/* Ensure the time range is in the right direction */
final TmfTimeRange tr = ((timeRange.getStartTime().compareTo(timeRange.getEndTime()) > 0) ? new TmfTimeRange(timeRange.getEndTime(), timeRange.getStartTime()) : timeRange);
/*
* Pop a dialog asking the user to select a parent directory for the new
* trace.
*/
TrimTraceDialog dialog = new TrimTraceDialog(shell, traceElem);
if (dialog.open() != Window.OK) {
return null;
}
Object result = dialog.getFirstResult();
if (result == null) {
/* Dialog was cancelled, take no further action. */
return null;
}
/* Verify that the selected path is valid and writeable */
final Path destinationPath = checkNotNull(Paths.get(result.toString()));
if (destinationPath.toFile().exists()) {
MessageDialog.openError(shell, Messages.TrimTraces_InvalidDirectory_DialogTitle, Messages.TrimTraces_InvalidDirectory_DialogText);
return null;
}
TraceToTrim toTrim = TraceToTrim.create(traceElem, destinationPath);
if (toTrim == null) {
return null;
}
try {
toTrim.createDir();
} catch (IOException e) {
/* Should not happen since we have checked permissions, etc. */
throw new ExecutionException(e.getMessage(), e);
}
TmfWorkspaceModifyOperation trimOperation = new TmfWorkspaceModifyOperation() {
@Override
public void execute(@Nullable IProgressMonitor monitor) throws CoreException {
SubMonitor mon = SubMonitor.convert(monitor, 2);
toTrim.trim(tr, mon);
/*
* Import the new trace into the current project, at the
* top-level.
*/
TmfProjectElement currentProjectElement = traceElem.getProject();
TmfTraceFolder traceFolder = currentProjectElement.getTracesFolder();
toTrim.importTrace(mon);
if (mon.isCanceled()) {
return;
}
if (traceFolder != null) {
Display.getDefault().asyncExec(toTrim::open);
} else {
Activator.getDefault().logWarning("Trace folder does not exist: " + toTrim.fDestinationPath); //$NON-NLS-1$
}
}
};
try {
PlatformUI.getWorkbench().getProgressService().run(true, true, trimOperation);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (InvocationTargetException e) {
TraceUtils.displayErrorMsg(Messages.TrimTraceHandler_failMsg, e.getMessage(), e);
}
return null;
}
}