/**
* Copyright 2014 SAP AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spotter.eclipse.ui.view;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
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.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
import org.lpe.common.util.LpeFileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spotter.eclipse.ui.Activator;
import org.spotter.eclipse.ui.ServiceClientWrapper;
import org.spotter.eclipse.ui.editors.HierarchyEditor;
import org.spotter.eclipse.ui.model.IExtensionItem;
import org.spotter.eclipse.ui.model.IExtensionItemFactory;
import org.spotter.eclipse.ui.model.ImmutableExtensionItemFactory;
import org.spotter.eclipse.ui.navigator.SpotterProjectResults;
import org.spotter.eclipse.ui.navigator.SpotterProjectRunResult;
import org.spotter.eclipse.ui.providers.ResultExtensionsImageProvider;
import org.spotter.eclipse.ui.providers.SpotterExtensionsLabelProvider;
import org.spotter.eclipse.ui.util.DialogUtils;
import org.spotter.eclipse.ui.util.SpotterUtils;
import org.spotter.eclipse.ui.util.WidgetUtils;
import org.spotter.eclipse.ui.viewers.ExtensionsGroupViewer;
import org.spotter.eclipse.ui.viewers.ResourceViewer;
import org.spotter.shared.hierarchy.model.XPerformanceProblem;
import org.spotter.shared.result.ResultsLocationConstants;
import org.spotter.shared.result.model.ResultsContainer;
import org.spotter.shared.result.model.SpotterResult;
/**
* A view to display results of a DynamicSpotter run.
*
* @author Denis Knoepfle
*
*/
public class ResultsView extends ViewPart implements ISelectionListener {
public static final String VIEW_ID = "org.spotter.eclipse.ui.view.resultsView";
private static final Logger LOGGER = LoggerFactory.getLogger(ResultsView.class);
private static final String RESULTS_VIEW_TITLE = "Results";
private static final int RESOURCES_LIST_RATIO = 45;
private static final int RESOURCES_CANVAS_RATIO = 55;
private static final String RESULTS_CONTENT_DESC_TEMPLATE = "DynamicSpotter Run '%s' of project '%s'";
private static final String RESULTS_EMPTY_CONTENT_DESC = "None selected.";
private static final String EMPTY_RESULTS = "No results selected.";
private static final String ERR_MSG_PARSE_ERROR = "An error occured while parsing the file '%s'.";
private static final String ERR_MSG_RES_REFRESH = "Error occured while refreshing resource!";
private static final String ERR_MSG_MISSING_REPORT = "Either file is missing or report is not set.";
private static final String ERR_MSG_MISSING_SER_FILE = "Could not find the spotter serialization file related with result '%s'.";
private static final String LABEL_NONE_SELECTED = "<none selected>";
private static final String LABEL_DETECTED = "Detected";
private static final String LABEL_NOT_DETECTED = "Not Detected";
private static final String LABEL_NO_LOOKUP = "The corresponding result could not be looked up. (erroneous analysis)";
private static final String LABEL_NO_INFO = "No description available.";
private static final String TAB_HIERARCHY_NAME = "Hierarchy";
private static final String TAB_REPORT_NAME = "Report";
private static final String TAB_ANNOTATION_NAME = "Annotations";
private final IExtensionItemFactory extensionItemFactory;
private Group grpDetails;
private TreeViewer hierarchyTreeViewer;
private ResultExtensionsImageProvider imageProvider;
private Text textReport;
private Text textAnnotation;
private Button btnSaveAnnotation;
private Label lblProblemName;
private Label lblStatus;
private Label lblDescription;
private Text textResult;
private List listResources;
private ResourceViewer resourceViewer;
private ServiceClientWrapper client;
private SpotterProjectRunResult runResultItem;
private ResultsContainer resultsContainer;
private XPerformanceProblem currentSelectedProblem;
/**
* The constructor.
*/
public ResultsView() {
this.client = null;
this.runResultItem = null;
this.extensionItemFactory = new ImmutableExtensionItemFactory(null);
}
@Override
public void createPartControl(Composite parent) {
setPartName(RESULTS_VIEW_TITLE);
setContentDescription(RESULTS_EMPTY_CONTENT_DESC);
// ensure that the parent's layout is a FillLayout
if (!(parent.getLayout() instanceof FillLayout)) {
parent.setLayout(new FillLayout());
}
TabFolder folder = new TabFolder(parent, SWT.NONE);
createHierarchyTab(folder);
createReportTab(folder);
createAnnotationTab(folder);
getViewSite().getPage().addPostSelectionListener(this);
}
/**
* Sets the run result to show.
*
* @param runResultItem
* the run result item to set
*/
public void setResult(SpotterProjectRunResult runResultItem) {
this.runResultItem = runResultItem;
if (runResultItem != null) {
String projectName = runResultItem.getProject().getName();
this.client = Activator.getDefault().getClient(projectName);
} else {
this.client = null;
}
updateTabs();
}
/**
* Returns the run result currently shown.
*
* @return the run result currently shown or <code>null</code> if none
*/
public SpotterProjectRunResult getResult() {
return runResultItem;
}
@Override
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
if (part == this) {
return;
}
if (!selection.isEmpty() && selection instanceof IStructuredSelection) {
Object first = ((IStructuredSelection) selection).getFirstElement();
if (first instanceof SpotterProjectRunResult) {
SpotterProjectRunResult runResult = (SpotterProjectRunResult) first;
if (runResult.isErroneous()) {
return;
}
IFolder newFolder = runResult.getResultFolder();
if (getResult() == null || !getResult().getResultFolder().equals(newFolder)) {
setResult(runResult);
}
}
}
}
@Override
public void setFocus() {
hierarchyTreeViewer.getTree().setFocus();
}
@Override
public void dispose() {
getViewSite().getPage().removePostSelectionListener(this);
resourceViewer.dispose();
}
/**
* Updates the content description string of the results view.
*/
public static void updateContentDescription() {
for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) {
for (IWorkbenchPage page : window.getPages()) {
ResultsView resultsView = (ResultsView) page.findView(ResultsView.VIEW_ID);
if (resultsView != null) {
resultsView.setContentDescription(resultsView.createContentDescription());
}
}
}
}
/**
* Resets the results view and deletes its contents if the given project
* matches the current content's associated project. If <code>project</code>
* is <code>null</code>, then the view is reset regardless of its current
* content.
*
* @param project
* the project to match for the reset or <code>null</code> to
* match any
*/
public static void reset(IProject project) {
for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) {
for (IWorkbenchPage page : window.getPages()) {
ResultsView resultsView = (ResultsView) page.findView(ResultsView.VIEW_ID);
if (resultsView != null) {
SpotterProjectRunResult result = resultsView.getResult();
if (project == null || result != null && project.equals(result.getProject())) {
resultsView.setResult(null);
}
}
}
}
}
/**
* Resets the results view and deletes its contents if the given project
* matches the current content's associated folder. If <code>folder</code>
* is <code>null</code>, then the view is reset regardless of its current
* content.
*
* @param folder
* the folder to match for the reset or <code>null</code> to
* match any
*/
public static void reset(IFolder folder) {
for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) {
for (IWorkbenchPage page : window.getPages()) {
ResultsView resultsView = (ResultsView) page.findView(ResultsView.VIEW_ID);
if (resultsView != null) {
SpotterProjectRunResult result = resultsView.getResult();
if (folder == null || result != null && folder.equals(result.getResultFolder())) {
resultsView.setResult(null);
}
}
}
}
}
private void createHierarchyTab(TabFolder folder) {
TabItem tabItem = new TabItem(folder, SWT.NONE);
tabItem.setText(TAB_HIERARCHY_NAME);
Composite parent = new Composite(folder, SWT.NONE);
parent.setLayout(new FillLayout());
SashForm container = new SashForm(parent, SWT.VERTICAL | SWT.SMOOTH);
hierarchyTreeViewer = ExtensionsGroupViewer.createTreeViewer(container,
extensionItemFactory.createExtensionItem(), null, false);
SpotterExtensionsLabelProvider labelProvider = (SpotterExtensionsLabelProvider) hierarchyTreeViewer
.getLabelProvider();
imageProvider = new ResultExtensionsImageProvider();
labelProvider.setImageProvider(imageProvider);
grpDetails = new Group(container, SWT.NONE);
grpDetails.setText("Details");
grpDetails.setLayout(WidgetUtils.createGridLayout(1, true));
createHierarchyDetailsUpperPart(grpDetails);
createHierarchyDetailsLowerPart(grpDetails);
container.setWeights(new int[] { 1, 2 });
addSelectionListeners();
tabItem.setControl(parent);
}
private void createHierarchyDetailsUpperPart(Composite parent) {
Composite compUpperPart = new Composite(parent, SWT.NONE);
compUpperPart.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
compUpperPart.setLayout(new GridLayout(2, false));
Label label = new Label(compUpperPart, SWT.NONE);
label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
label.setText("Problem Name:");
lblProblemName = new Label(compUpperPart, SWT.NONE);
lblProblemName.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false));
lblProblemName.setText(LABEL_NONE_SELECTED);
label = new Label(compUpperPart, SWT.NONE);
label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
label.setText("Status:");
lblStatus = new Label(compUpperPart, SWT.NONE);
lblStatus.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false));
lblStatus.setText("");
label = new Label(compUpperPart, SWT.NONE);
label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
label.setText("Description:");
lblDescription = new Label(compUpperPart, SWT.WRAP);
lblDescription.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false));
lblDescription.setText("");
}
private void createHierarchyDetailsLowerPart(Composite parent) {
Composite compLowerPart = new Composite(parent, SWT.NONE);
compLowerPart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
compLowerPart.setLayout(new FillLayout(SWT.HORIZONTAL));
SashForm sashContainer = new SashForm(compLowerPart, SWT.HORIZONTAL | SWT.SMOOTH);
Group grpResult = new Group(sashContainer, SWT.NONE);
grpResult.setText("Result Message");
grpResult.setLayout(new FillLayout());
textResult = new Text(grpResult, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
textResult.setText("");
textResult.setEditable(false);
Group grpResources = new Group(sashContainer, SWT.NONE);
grpResources.setText("Resources");
grpResources.setLayout(new FillLayout(SWT.HORIZONTAL));
SashForm sashResources = new SashForm(grpResources, SWT.HORIZONTAL | SWT.SMOOTH);
listResources = new List(sashResources, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
resourceViewer = new ResourceViewer(sashResources);
sashResources.setWeights(new int[] { RESOURCES_LIST_RATIO, RESOURCES_CANVAS_RATIO });
}
private void addSelectionListeners() {
hierarchyTreeViewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
IStructuredSelection sel = (IStructuredSelection) event.getSelection();
currentSelectedProblem = null;
if (!sel.isEmpty()) {
IExtensionItem item = (IExtensionItem) sel.getFirstElement();
Object problem = item.getModelWrapper().getXMLModel();
if (problem instanceof XPerformanceProblem) {
currentSelectedProblem = (XPerformanceProblem) problem;
}
}
updateProblemDetails();
}
});
hierarchyTreeViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
Object selectedNode = selection.getFirstElement();
hierarchyTreeViewer.setExpandedState(selectedNode, !hierarchyTreeViewer.getExpandedState(selectedNode));
}
});
listResources.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
if (listResources.getSelectionCount() == 0) {
resourceViewer.clear();
} else {
String selection = listResources.getSelection()[0];
String prefix = getCurrentResourceFolder();
String resourceFile = prefix + selection;
resourceViewer.setResource(resourceFile, runResultItem.getProject().getName());
}
}
});
}
private void updateProblemDetails() {
listResources.removeAll();
resourceViewer.clear();
if (currentSelectedProblem == null) {
lblProblemName.setText(LABEL_NONE_SELECTED);
lblDescription.setText("");
lblStatus.setText("");
textResult.setText("");
} else {
String name = currentSelectedProblem.getExtensionName();
lblProblemName.setText(name);
String description = client.getExtensionDescription(name);
lblDescription.setText(description == null ? LABEL_NO_INFO : description);
String id = currentSelectedProblem.getUniqueId();
SpotterResult spotterResult = resultsContainer == null ? null : resultsContainer.getResultsMap().get(id);
if (spotterResult == null) {
lblStatus.setText(LABEL_NO_LOOKUP);
textResult.setText(LABEL_NO_LOOKUP);
} else {
lblStatus.setText(spotterResult.isDetected() ? LABEL_DETECTED : LABEL_NOT_DETECTED);
textResult.setText(spotterResult.getMessage());
populateResourcesList(spotterResult);
}
}
grpDetails.layout();
}
private void populateResourcesList(SpotterResult spotterResult) {
for (String resourceFile : spotterResult.getResourceFiles()) {
listResources.add(resourceFile);
}
}
private void createReportTab(TabFolder folder) {
TabItem tabItem = new TabItem(folder, SWT.NONE);
tabItem.setText(TAB_REPORT_NAME);
textReport = new Text(folder, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
textReport.setText(EMPTY_RESULTS);
textReport.setEditable(false);
tabItem.setControl(textReport);
}
private void createAnnotationTab(TabFolder folder) {
TabItem tabItem = new TabItem(folder, SWT.NONE);
tabItem.setText(TAB_ANNOTATION_NAME);
Composite parent = new Composite(folder, SWT.NONE);
parent.setLayout(WidgetUtils.createGridLayout(1, true));
btnSaveAnnotation = new Button(parent, SWT.PUSH);
btnSaveAnnotation.setLayoutData(new GridData(SWT.BEGINNING, SWT.TOP, false, false));
btnSaveAnnotation.setText("Save Changes");
btnSaveAnnotation.setEnabled(false);
btnSaveAnnotation.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (runResultItem != null && resultsContainer != null) {
String oldAnnotation = textAnnotation.getText();
resultsContainer.setAnnotation(textAnnotation.getText());
if (SpotterUtils.writeResultsContainer(runResultItem.getResultFolder(), resultsContainer)) {
btnSaveAnnotation.setEnabled(false);
} else {
resultsContainer.setAnnotation(oldAnnotation);
}
}
}
});
Group group = new Group(parent, SWT.NONE);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
group.setLayout(new FillLayout());
textAnnotation = new Text(group, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
textAnnotation.setText("");
textAnnotation.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
if (resultsContainer != null && !btnSaveAnnotation.isEnabled()) {
btnSaveAnnotation.setEnabled(true);
}
}
});
tabItem.setControl(parent);
}
private void updateTabs() {
setContentDescription(createContentDescription());
if (runResultItem == null) {
resetHierarchy();
resetReport();
resetAnnotation();
} else if (updateResultsContainer()) {
updateHierarchy();
updateReport();
updateAnnotation();
}
}
private String createContentDescription() {
String desc = RESULTS_EMPTY_CONTENT_DESC;
if (runResultItem != null) {
String itemText = runResultItem.getText();
String projectName = runResultItem.getProject().getName();
desc = String.format(RESULTS_CONTENT_DESC_TEMPLATE, itemText, projectName);
}
return desc;
}
private void resetHierarchy() {
hierarchyTreeViewer.setInput(extensionItemFactory.createExtensionItem());
lblProblemName.setText(LABEL_NONE_SELECTED);
lblDescription.setText("");
lblStatus.setText("");
textResult.setText("");
listResources.removeAll();
resourceViewer.clear();
}
private void resetReport() {
textReport.setText(EMPTY_RESULTS);
}
private void resetAnnotation() {
textAnnotation.setText("");
btnSaveAnnotation.setEnabled(false);
}
private boolean updateResultsContainer() {
IFolder resultFolder = runResultItem.getResultFolder();
IFile file = resultFolder.getFile(ResultsLocationConstants.RESULTS_SERIALIZATION_FILE_NAME);
resultsContainer = null;
String errorMsg = null;
Exception exception = null;
try {
File containerFile = new File(file.getLocation().toString());
if (!containerFile.exists()) {
try {
if (!resultFolder.isSynchronized(IResource.DEPTH_INFINITE)) {
resultFolder.refreshLocal(IResource.DEPTH_INFINITE, null);
}
if (resultFolder.exists()) {
resultFolder.delete(true, null);
}
SpotterProjectResults parent = (SpotterProjectResults) runResultItem.getParent();
parent.refreshChildren();
} catch (CoreException e) {
LOGGER.error("Error while deleting result folder.", e);
}
}
if (!file.isSynchronized(IResource.DEPTH_ZERO)) {
file.refreshLocal(IResource.DEPTH_ZERO, null);
}
resultsContainer = (ResultsContainer) LpeFileUtils.readObject(containerFile);
} catch (CoreException e) {
errorMsg = ERR_MSG_RES_REFRESH;
exception = e;
LOGGER.error(ERR_MSG_RES_REFRESH, e);
} catch (FileNotFoundException e) {
errorMsg = String.format(ERR_MSG_MISSING_SER_FILE, runResultItem.getText());
exception = e;
LOGGER.error(DialogUtils.appendCause(errorMsg, e.getMessage()));
} catch (IOException | ClassNotFoundException e) {
errorMsg = String.format(ERR_MSG_PARSE_ERROR, file.getLocation());
exception = e;
LOGGER.error(errorMsg, e);
}
if (errorMsg != null) {
resultsContainer = null;
DialogUtils.handleError(errorMsg, exception);
setResult(null);
}
return errorMsg == null;
}
private void updateHierarchy() {
IExtensionItem input = null;
if (resultsContainer != null) {
XPerformanceProblem root = resultsContainer.getRootProblem();
if (root != null) {
String projectName = runResultItem.getProject().getName();
input = HierarchyEditor.createPerformanceProblemHierarchy(projectName, extensionItemFactory, root);
}
}
imageProvider.setResultsContainer(resultsContainer);
if (input == null) {
input = extensionItemFactory.createExtensionItem();
}
hierarchyTreeViewer.setInput(input);
hierarchyTreeViewer.expandAll();
}
private void updateReport() {
if (resultsContainer != null && resultsContainer.getReport() != null) {
textReport.setText(resultsContainer.getReport());
} else {
textReport.setText(ERR_MSG_MISSING_REPORT);
}
}
private void updateAnnotation() {
if (resultsContainer != null && resultsContainer.getAnnotation() != null) {
textAnnotation.setText(resultsContainer.getAnnotation());
} else {
textAnnotation.setText("");
}
btnSaveAnnotation.setEnabled(false);
}
private String getCurrentResourceFolder() {
IFolder folder = runResultItem.getResultFolder();
String currentRunFolder = folder.getLocation().toString() + "/";
String subDirPath = getSubDirPathForProblem(currentSelectedProblem);
return currentRunFolder + subDirPath;
}
private static String getSubDirPathForProblem(XPerformanceProblem problem) {
// TODO: there must be a way to retrieve this path from the result
// independent of the extension's implementation!
String name = problem.getExtensionName();
String idTag = "" + problem.getUniqueId().hashCode();
String subDirPath = name + "-" + idTag + "/" + ResultsLocationConstants.RESULT_RESOURCES_SUB_DIR + "/";
return subDirPath;
}
}