/*******************************************************************************
* Copyright (c) 2014 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexandre Montplaisir - Initial API and implementation
* Patrick Tasse - Add support for folder elements
*******************************************************************************/
package fr.inria.linuxtools.tmf.ui.project.model;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import fr.inria.linuxtools.tmf.core.TmfCommonConstants;
import fr.inria.linuxtools.tmf.core.project.model.TmfTraceImportException;
import fr.inria.linuxtools.tmf.core.project.model.TmfTraceType;
import fr.inria.linuxtools.tmf.core.project.model.TraceTypeHelper;
import fr.inria.linuxtools.tmf.core.project.model.TmfTraceType.TraceElementType;
import fr.inria.linuxtools.tmf.core.trace.ITmfTrace;
import fr.inria.linuxtools.tmf.core.util.Pair;
/**
* Utils class for the UI-specific parts of @link {@link TmfTraceType}.
*
* @author Alexandre Montplaisir
* @since 3.0
*/
public final class TmfTraceTypeUIUtils {
/** Extension point ID */
public static final String TMF_TRACE_TYPE_UI_ID = "fr.inria.linuxtools.tmf.ui.tracetypeui"; //$NON-NLS-1$
/** Extension point element 'type' (should match the type in TmfTraceType) */
public static final String TYPE_ELEM = "type"; //$NON-NLS-1$
/**
* Extension point element 'experiment' (should match the type in
* TmfTraceType)
*/
public static final String EXPERIMENT_ELEM = "experiment"; //$NON-NLS-1$
/** Extension point element 'Default editor' */
public static final String DEFAULT_EDITOR_ELEM = "defaultEditor"; //$NON-NLS-1$
/** Extension point element 'Events table type' */
public static final String EVENTS_TABLE_TYPE_ELEM = "eventsTableType"; //$NON-NLS-1$
/** Extension point attribute 'tracetype' */
public static final String TRACETYPE_ATTR = "tracetype"; //$NON-NLS-1$
/** Extension point attribute 'icon' */
public static final String ICON_ATTR = "icon"; //$NON-NLS-1$
/** Extension point attribute 'class' (attribute of eventsTableType) */
public static final String CLASS_ATTR = "class"; //$NON-NLS-1$
private static final char SEPARATOR = ':';
private TmfTraceTypeUIUtils() {
}
private static List<Pair<Integer, TraceTypeHelper>> reduce(List<Pair<Integer, TraceTypeHelper>> candidates) {
List<Pair<Integer, TraceTypeHelper>> retVal = new ArrayList<>();
// get all the tracetypes that are unique in that stage
for (Pair<Integer, TraceTypeHelper> candidatePair : candidates) {
TraceTypeHelper candidate = candidatePair.getSecond();
if (isUnique(candidate, candidates)) {
retVal.add(candidatePair);
}
}
return retVal;
}
/*
* Only return the leaves of the trace types. Ignore custom trace types.
*/
private static boolean isUnique(TraceTypeHelper trace, List<Pair<Integer, TraceTypeHelper>> set) {
if (isCustomTraceId(trace.getCanonicalName())) {
return true;
}
// check if the trace type is the leaf. we make an instance of the trace
// type and if it is only an instance of itself, it is a leaf
final ITmfTrace tmfTrace = trace.getTrace();
int count = -1;
for (Pair<Integer, TraceTypeHelper> child : set) {
final ITmfTrace traceCandidate = child.getSecond().getTrace();
if (tmfTrace.getClass().isInstance(traceCandidate)) {
count++;
}
}
return count == 0;
}
/**
* Is the trace type id a custom (user-defined) trace type. These are the
* traces like : text and xml defined by the custom trace wizard.
*
* @param traceTypeId
* the trace type id
* @return true if the trace is a custom type
*/
private static boolean isCustomTraceId(String traceTypeId) {
TraceTypeHelper traceType = TmfTraceType.getTraceType(traceTypeId);
if (traceType != null) {
return TmfTraceType.isCustomTrace(traceType.getCategoryName() + SEPARATOR + traceType.getName());
}
return false;
}
private static TraceTypeHelper getTraceTypeToSet(List<Pair<Integer, TraceTypeHelper>> candidates, Shell shell) {
final Map<String, String> names = new HashMap<>();
Shell shellToShow = new Shell(shell);
shellToShow.setText(Messages.TmfTraceType_SelectTraceType);
final String candidatesToSet[] = new String[1];
for (Pair<Integer, TraceTypeHelper> candidatePair : candidates) {
TraceTypeHelper candidate = candidatePair.getSecond();
Button b = new Button(shellToShow, SWT.RADIO);
final String displayName = candidate.getCategoryName() + ':' + candidate.getName();
b.setText(displayName);
names.put(displayName, candidate.getCanonicalName());
b.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
final Button source = (Button) e.getSource();
candidatesToSet[0] = (names.get(source.getText()));
source.getParent().dispose();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
}
shellToShow.setLayout(new RowLayout(SWT.VERTICAL));
shellToShow.pack();
shellToShow.open();
Display display = shellToShow.getDisplay();
while (!shellToShow.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
return TmfTraceType.getTraceTypeHelper(candidatesToSet[0]);
}
/**
* This member figures out the trace type of a given file. It will prompt
* the user if it needs more information to properly pick the trace type.
*
* @param path
* The path of file to import
* @param shell
* a shell to display the message to. If it is null, it is
* assumed to be cancelled.
* @param traceTypeHint
* the ID of a trace (like "o.e.l.specifictrace" )
* @return null if the request is cancelled or a TraceTypeHelper if it
* passes.
* @throws TmfTraceImportException
* if the traces don't match or there are errors in the trace
* file
*/
public static TraceTypeHelper selectTraceType(String path, Shell shell, String traceTypeHint) throws TmfTraceImportException {
Comparator<Pair<Integer, TraceTypeHelper>> comparator = new Comparator<Pair<Integer, TraceTypeHelper>>() {
@Override
public int compare(Pair<Integer, TraceTypeHelper> o1, Pair<Integer, TraceTypeHelper> o2) {
int res = -o1.getFirst().compareTo(o2.getFirst()); // invert so that highest confidence is first
if (res == 0) {
res = o1.getSecond().getName().compareTo(o2.getSecond().getName());
}
return res;
}
};
TreeSet<Pair<Integer, TraceTypeHelper>> validCandidates = new TreeSet<>(comparator);
final Iterable<TraceTypeHelper> traceTypeHelpers = TmfTraceType.getTraceTypeHelpers();
for (TraceTypeHelper traceTypeHelper : traceTypeHelpers) {
if (traceTypeHelper.isExperimentType()) {
continue;
}
int confidence = traceTypeHelper.validateWithConfidence(path);
if (confidence >= 0) {
// insert in the tree map, ordered by confidence (highest confidence first) then name
Pair<Integer, TraceTypeHelper> element = new Pair<>(confidence, traceTypeHelper);
validCandidates.add(element);
}
}
TraceTypeHelper traceTypeToSet = null;
if (validCandidates.isEmpty()) {
final String errorMsg = NLS.bind(Messages.TmfOpenTraceHelper_NoTraceTypeMatch, path);
throw new TmfTraceImportException(errorMsg);
} else if (validCandidates.size() != 1) {
List<Pair<Integer, TraceTypeHelper>> candidates = new ArrayList<>(validCandidates);
List<Pair<Integer, TraceTypeHelper>> reducedCandidates = reduce(candidates);
for (Pair<Integer, TraceTypeHelper> candidatePair : reducedCandidates) {
TraceTypeHelper candidate = candidatePair.getSecond();
if (candidate.getCanonicalName().equals(traceTypeHint)) {
traceTypeToSet = candidate;
break;
}
}
if (traceTypeToSet == null) {
if (reducedCandidates.size() == 0) {
throw new TmfTraceImportException(Messages.TmfOpenTraceHelper_ReduceError);
} else if (reducedCandidates.size() == 1) {
traceTypeToSet = reducedCandidates.get(0).getSecond();
} else if (shell == null) {
Pair<Integer, TraceTypeHelper> candidate = reducedCandidates.get(0);
// if the best match has lowest confidence, don't select it
if (candidate.getFirst() > 0) {
traceTypeToSet = candidate.getSecond();
}
} else {
traceTypeToSet = getTraceTypeToSet(reducedCandidates, shell);
}
}
} else {
traceTypeToSet = validCandidates.first().getSecond();
}
return traceTypeToSet;
}
/**
* Set the trace type of a {@Link TraceTypeHelper}. Should only be
* used internally by this project.
*
* @param resource
* the resource to set
* @param traceType
* the {@link TraceTypeHelper} to set the trace type to.
* @return Status.OK_Status if successful, error is otherwise.
* @throws CoreException
* An exception caused by accessing eclipse project items.
*/
public static IStatus setTraceType(IResource resource, TraceTypeHelper traceType) throws CoreException {
String traceTypeId = traceType.getCanonicalName();
resource.setPersistentProperty(TmfCommonConstants.TRACETYPE, traceTypeId);
TmfProjectElement tmfProject = TmfProjectRegistry.getProject(resource.getProject(), true);
if (tmfProject.getTracesFolder().getPath().isPrefixOf(resource.getFullPath())) {
String elementPath = resource.getFullPath().makeRelativeTo(tmfProject.getTracesFolder().getPath()).toString();
refreshTraceElement(tmfProject.getTracesFolder().getTraces(), elementPath);
} else if (resource.getParent().equals(tmfProject.getExperimentsFolder().getResource())) {
/* The trace type to set is for an experiment */
for (TmfExperimentElement experimentElement : tmfProject.getExperimentsFolder().getExperiments()) {
if (resource.equals(experimentElement.getResource())) {
experimentElement.refreshTraceType();
break;
}
}
} else {
for (TmfExperimentElement experimentElement : tmfProject.getExperimentsFolder().getExperiments()) {
if (experimentElement.getPath().isPrefixOf(resource.getFullPath())) {
String elementPath = resource.getFullPath().makeRelativeTo(experimentElement.getPath()).toString();
refreshTraceElement(experimentElement.getTraces(), elementPath);
break;
}
}
}
tmfProject.refresh();
return Status.OK_STATUS;
}
private static void refreshTraceElement(List<TmfTraceElement> traceElements, String elementPath) {
for (TmfTraceElement traceElement : traceElements) {
if (traceElement.getElementPath().equals(elementPath)) {
traceElement.refreshTraceType();
break;
}
}
}
/**
* Retrieves all configuration elements from the platform extension registry
* for the trace type UI extension.
*
* @param elType
* The type of trace type requested, either TRACE or EXPERIMENT
* @return An array of trace type configuration elements
*/
public static IConfigurationElement[] getTypeUIElements(TraceElementType elType) {
String elementName = TYPE_ELEM;
if (elType == TraceElementType.EXPERIMENT) {
elementName = EXPERIMENT_ELEM;
}
IConfigurationElement[] elements =
Platform.getExtensionRegistry().getConfigurationElementsFor(TMF_TRACE_TYPE_UI_ID);
List<IConfigurationElement> typeElements = new LinkedList<>();
for (IConfigurationElement element : elements) {
if (element.getName().equals(elementName)) {
typeElements.add(element);
}
}
return typeElements.toArray(new IConfigurationElement[typeElements.size()]);
}
/**
* Get the UI elements for the given trace type
*
* @param traceType
* The tracetype ID
* @param elType
* The type of trace type requested, either TRACE or EXPERIMENT
* @return The top-level configuration element (access its children with
* .getChildren()). Or null if there is no such element.
*/
@Nullable
public static IConfigurationElement getTraceUIAttributes(String traceType, TraceElementType elType) {
IConfigurationElement[] elements = getTypeUIElements(elType);
for (IConfigurationElement ce : elements) {
if (traceType.equals(ce.getAttribute(TRACETYPE_ATTR))) {
return ce;
}
}
return null;
}
}