blob: 86b49643889b1c9d67a2b0614591819de5a10298 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Movidius Inc. 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
*
* Contributors:
* Robert Kiss - Initial API and implementation
* Mikael Ferland - Refactor API to support multiple symbol files
*
*******************************************************************************/
package org.eclipse.tracecompass.internal.analysis.profiling.ui.symbols;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.ArrayUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Table;
import org.eclipse.tracecompass.internal.analysis.profiling.ui.Activator;
import org.eclipse.tracecompass.tmf.core.symbols.IMappingFile;
import org.eclipse.tracecompass.tmf.ui.dialog.TmfFileDialogFactory;
import org.eclipse.tracecompass.tmf.ui.symbols.AbstractSymbolProviderPreferencePage;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import com.google.common.collect.ImmutableList;
/**
* Preference page that allows the user to configure a
* {@link BasicSymbolProvider}
*
* @author Robert Kiss
* @author Mikael Ferland
*
*/
public class BasicSymbolProviderPreferencePage extends AbstractSymbolProviderPreferencePage {
private static final ImageDescriptor BIN_ICON = getImageDescriptor("icons/obj16/binary_mapping_file.gif"); //$NON-NLS-1$
private static final ImageDescriptor TXT_ICON = getImageDescriptor("icons/obj16/text_mapping_file.gif"); //$NON-NLS-1$
// Typical magic numbers
private static final byte @NonNull[] ELF_HEADER = { 0x7f, 'E', 'L', 'F' }; // ...for Linux executables
private static final byte @NonNull[] MZ_HEADER = { 'M', 'Z' }; // ...for Windows executables
private static final byte @NonNull[] COMPILED_JAVA_CLASS_HEADER = { 'C', 'A', 'F', 'E', 'B', 'A', 'B', 'E' }; // ...for compiled Java class files
private static final ImmutableList<byte[]> BINARY_HEADERS = ImmutableList.of(ELF_HEADER, MZ_HEADER, COMPILED_JAVA_CLASS_HEADER);
private BasicSymbolProvider fSymbolProvider;
private Button fRemoveFile;
private Button fPriorityUp;
private Button fPriorityDown;
private TableViewer fMappingTable;
private final @NonNull List<@NonNull IMappingFile> fMappingFiles = new ArrayList<>();
ColumnLabelProvider clp = new ColumnLabelProvider() {
@Override
public String getText(Object element) {
if (element instanceof IMappingFile) {
return ((IMappingFile) element).getFullPath();
}
return null;
}
@Override
public Image getImage(Object element) {
if (element instanceof IMappingFile) {
// Add binary/mapping icons to the image registry if needed
ImageRegistry registry = Objects.requireNonNull(Activator.getDefault()).getImageRegistry();
Image binImage = registry.get(BIN_ICON.toString());
if (binImage == null) {
registry.put(BIN_ICON.toString(), BIN_ICON);
binImage = registry.get(BIN_ICON.toString());
}
Image txtImage = registry.get(TXT_ICON.toString());
if (txtImage == null) {
registry.put(TXT_ICON.toString(), TXT_ICON);
txtImage = registry.get(TXT_ICON.toString());
}
return ((IMappingFile) element).isBinaryFile() ? binImage : txtImage;
}
return null;
}
};
/**
* Creates a new object for the given provider
*
* @param provider
* a non-null provider
*/
public BasicSymbolProviderPreferencePage(@NonNull BasicSymbolProvider provider) {
super(provider);
fSymbolProvider = provider;
setDescription(MessageFormat.format(Messages.BasicSymbolProviderPrefPage_description, provider.getTrace().getName()));
setValid(true);
setTitle(MessageFormat.format(Messages.BasicSymbolProviderPrefPage_tabTitle, provider.getTrace().getName()));
}
@Override
protected Control createContents(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
composite.setLayout(new GridLayout(2, false));
fMappingTable = new TableViewer(composite, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
fMappingTable.setContentProvider(ArrayContentProvider.getInstance());
Table table = fMappingTable.getTable();
table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
table.setHeaderVisible(false);
table.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// Enable/disable buttons (except "Add...")
int selectionCount = table.getSelectionCount();
boolean enablePriorityButtons = (table.getItemCount() >= 2 && selectionCount == 1);
fPriorityUp.setEnabled(enablePriorityButtons);
fPriorityDown.setEnabled(enablePriorityButtons);
fRemoveFile.setEnabled(selectionCount >= 1);
}
});
TableViewerColumn col = new TableViewerColumn(fMappingTable, SWT.None);
col.setLabelProvider(clp);
fMappingFiles.addAll(fSymbolProvider.getMappingFiles());
fMappingTable.setInput(fMappingFiles);
col.getColumn().pack();
Composite buttonContainer = new Composite(composite, SWT.NULL);
buttonContainer.setLayout(new GridLayout());
buttonContainer.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, false, false));
Button fAddFile = new Button(buttonContainer, SWT.NONE);
fAddFile.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
fAddFile.setText(Messages.BasicSymbolProviderPrefPage_addFile_text);
fAddFile.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
FileDialog dialog = TmfFileDialogFactory.create(Display.getCurrent().getActiveShell(), SWT.OPEN | SWT.MULTI);
dialog.open();
addSelectedMappingFiles(dialog.getFilterPath(), dialog.getFileNames());
}
});
fRemoveFile = new Button(buttonContainer, SWT.NONE);
fRemoveFile.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
fRemoveFile.setText(Messages.BasicSymbolProviderPrefPage_removeFile_text);
fRemoveFile.setEnabled(false);
fRemoveFile.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// Files must be removed in reversed order to prevent
// runtime errors
int[] indices = fMappingTable.getTable().getSelectionIndices();
ArrayUtils.reverse(indices);
for (int index : indices) {
fMappingFiles.remove(index);
}
fMappingTable.refresh();
fPriorityUp.setEnabled(false);
fPriorityDown.setEnabled(false);
fRemoveFile.setEnabled(false);
}
});
Composite priorityContainer = new Composite(buttonContainer, SWT.NONE);
GridLayout priorityContainerLayout = new GridLayout();
priorityContainerLayout.marginTop = 5;
priorityContainer.setLayout(priorityContainerLayout);
priorityContainer.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, false, false));
fPriorityUp = new Button(buttonContainer, SWT.NONE);
fPriorityUp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
fPriorityUp.setText(Messages.BasicSymbolProviderPrefPage_priorityUp_text);
fPriorityUp.setToolTipText(Messages.BasicSymbolProviderPrefPage_priorityUp_tooltip);
fPriorityUp.setEnabled(false);
fPriorityUp.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int index = fMappingTable.getTable().getSelectionIndex();
if (index > 0) {
Collections.swap(fMappingFiles, index, index - 1);
fMappingTable.refresh();
}
}
});
fPriorityDown = new Button(buttonContainer, SWT.NONE);
fPriorityDown.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
fPriorityDown.setText(Messages.BasicSymbolProviderPrefPage_priorityDown_text);
fPriorityDown.setToolTipText(Messages.BasicSymbolProviderPrefPage_priorityDown_tooltip);
fPriorityDown.setEnabled(false);
fPriorityDown.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int index = table.getSelectionIndex();
if (index < table.getItemCount() - 1) {
Collections.swap(fMappingFiles, index, index + 1);
fMappingTable.refresh();
}
}
});
return composite;
}
@Override
public void saveConfiguration() {
fSymbolProvider.setMappingFiles(fMappingFiles);
}
/**
* Retrieve the image descriptor of an icon
*
* @param file
* Path leading to the icon image
*/
private static ImageDescriptor getImageDescriptor(String file) {
return AbstractUIPlugin.imageDescriptorFromPlugin(Activator.PLUGIN_ID, file);
}
/**
* Add valid mapping file(s) to the table viewer
*
* @param filterPath
* Directory in which the mapping files are located
* @param fileNames
* Names of the selected mapping files
*/
private void addSelectedMappingFiles(String filterPath, String[] fileNames) {
List<String> invalidFiles = new ArrayList<>();
for (String fileName : fileNames) {
String fullPath = filterPath + File.separator + fileName;
boolean isBinaryFile = isBinaryFile(fullPath);
IMappingFile mf = IMappingFile.create(fullPath, isBinaryFile);
if (mf != null) {
if (!fMappingFiles.contains(mf)) {
fMappingFiles.add(mf);
}
} else {
invalidFiles.add(fullPath);
}
}
if (!invalidFiles.isEmpty()) {
displayErrorMessage(invalidFiles);
}
fMappingTable.refresh();
}
/**
* Determine if a mapping file is binary
*
* @param fullPath
* Path to the mapping file
*/
private static boolean isBinaryFile(String fullPath) {
// 1- Retrieve the first 8 bytes of the file
byte[] firstBytes = new byte[8];
try (FileInputStream input = new FileInputStream(fullPath)) {
input.read(firstBytes);
} catch (IOException e) {
Activator.getDefault().logError("Cannot read the file " + fullPath, e); //$NON-NLS-1$//$NON-NLS-2$
}
// 2- Verify if the bytes correspond to a known magic number for binary
// files
for (byte[] header : BINARY_HEADERS) {
if (firstBytes.length >= header.length && startsWith(header, firstBytes)) {
return true;
}
}
return false;
}
/**
* Determine if an array begins with the elements of another specified array
*/
private static boolean startsWith(byte[] a, byte[] b) {
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
/**
* Display dialog when the user attempts to import invalid mapping file(s)
*
* @param invalidFiles
* List of all the invalid mapping files
*/
private void displayErrorMessage(@NonNull List<String> invalidFiles) {
StringBuilder errorMessageContent = new StringBuilder();
errorMessageContent.append(System.lineSeparator()).append(System.lineSeparator());
invalidFiles.forEach(file -> errorMessageContent.append("• ").append(file).append(System.lineSeparator())); //$NON-NLS-1$
MessageDialog.openInformation(
getShell(),
Messages.BasicSymbolProviderPrefPage_invalidMappingFileDialogHeader,
Messages.BasicSymbolProviderPrefPage_invalidMappingFileMessage + errorMessageContent.toString());
}
}