/*
* Copyright 2017 Nokia Solutions and Networks
* Licensed under the Apache License, Version 2.0,
* see license.txt file for details.
*/
package org.robotframework.ide.eclipse.main.plugin.launch;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.rf.ide.core.execution.LogLevel;
import org.rf.ide.core.execution.RobotDefaultAgentEventListener;
import org.rf.ide.core.execution.Status;
import org.rf.ide.core.execution.context.KeywordPosition;
import org.rf.ide.core.execution.context.RobotDebugExecutionContext;
import org.rf.ide.core.execution.server.AgentClient;
import org.rf.ide.core.execution.server.response.ContinueExecution;
import org.rf.ide.core.execution.server.response.EvaluateCondition;
import org.rf.ide.core.execution.server.response.ServerResponse.ResponseException;
import org.rf.ide.core.execution.server.response.StopExecution;
import org.rf.ide.core.testdata.RobotParser;
import org.robotframework.ide.eclipse.main.plugin.RedPlugin;
import org.robotframework.ide.eclipse.main.plugin.debug.model.RobotDebugTarget;
import org.robotframework.ide.eclipse.main.plugin.debug.utils.KeywordContext;
import org.robotframework.ide.eclipse.main.plugin.debug.utils.KeywordExecutionManager;
import org.robotframework.ide.eclipse.main.plugin.model.RobotSuiteFile;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.RobotFormEditor;
public class DebugExecutionEventsListener extends RobotDefaultAgentEventListener {
private AgentClient client;
private final RobotDebugTarget debugTarget;
private final RobotDebugExecutionContext executionContext;
private final KeywordExecutionManager keywordExecutionManager;
private boolean isStopping;
private boolean isBreakpointConditionFulfilled;
public DebugExecutionEventsListener(final RobotDebugTarget debugTarget, final List<IResource> resourcesUnderDebug) {
this.debugTarget = debugTarget;
this.executionContext = new RobotDebugExecutionContext();
this.keywordExecutionManager = new KeywordExecutionManager(resourcesUnderDebug);
}
@Override
public void setClient(final AgentClient client) {
this.client = client;
this.debugTarget.setClient(client);
}
public void terminated() {
debugTarget.terminated();
}
@Override
public void handleAgentInitializing() {
debugTarget.started();
}
@Override
public void handleSuiteStarted(final String suiteName, final File suiteFilePath) {
final IPath suitePath = Path.fromOSString(suiteFilePath.getAbsolutePath());
final IFile currentSuiteFile = keywordExecutionManager.extractCurrentSuite(suitePath);
if (currentSuiteFile != null) {
final RobotSuiteFile robotSuiteFile = RedPlugin.getModelManager().createSuiteFile(currentSuiteFile);
final RobotParser robotParser = robotSuiteFile.getProject().getEagerRobotParser();
executionContext.startSuite(robotParser.parse(currentSuiteFile.getLocation().toFile()).get(0), robotParser);
}
}
@Override
public void handleSuiteEnded(final String suiteName, final int elapsedTime, final Status status,
final String errorMessage) {
debugTarget.clearStackFrames();
executionContext.endSuite();
}
@Override
public void handleTestStarted(final String testCaseName, final String testCaseLongName) {
executionContext.startTest(testCaseName);
}
@Override
public void handleTestEnded(final String testCaseName, final String testCaseLongName, final int elapsedTime,
final Status status, final String errorMessage) {
executionContext.endTest();
}
@Override
public void handleKeywordStarted(final String name, final String type, final List<String> args) {
prepareKeywordStart(name, type, args);
executionContext.startKeyword(name, type, args);
String executedFileName = keywordExecutionManager.getCurrentSuiteName();
final KeywordPosition keywordPosition = executionContext.findKeywordPosition();
final int keywordLineNumber = keywordPosition.getLineNumber();
final String currentResourceFile = keywordExecutionManager
.extractCurrentResourceFile(keywordPosition.getFilePath());
if (currentResourceFile != null) {
executedFileName = new File(currentResourceFile).getName();
}
if (shouldStopExecution(executedFileName, keywordLineNumber)) {
activateSourcePageInActiveEditor();
isStopping = true;
resetSteppingState();
resetStackFramesState();
} else {
isStopping = false;
}
final KeywordContext newKeywordContext = new KeywordContext(
keywordExecutionManager.extractExecutedFileNameWithParentFolderInfo(executedFileName),
keywordLineNumber, null);
debugTarget.getCurrentKeywordsContext().put(name, newKeywordContext);
}
private void activateSourcePageInActiveEditor() {
final IWorkbench workbench = PlatformUI.getWorkbench();
workbench.getDisplay()
.syncExec(() -> RobotFormEditor.activateSourcePageInActiveEditor(workbench.getActiveWorkbenchWindow()));
}
private void prepareKeywordStart(final String name, final String type, final List<String> args) {
if (executionContext.isSuiteSetupTeardownKeyword(type) && !executionContext.isInSuite()
&& keywordExecutionManager.getCurrentSuiteLocation() != null) {
handleInitFile();
} else if (executionContext.isTestCaseTeardownKeyword(type)) {
debugTarget.clearStackFrames();
}
if (keywordExecutionManager.getCurrentSuiteFile() == null
&& !executionContext.isSuiteSetupTeardownKeyword(type)) {
final String message = String.format(
"Invalid execution context: suite='%s', name='%s', type='%s', args='%s'",
keywordExecutionManager.getCurrentSuiteName(), name, type, args);
showError("Debug Execution Context Error", message);
throw new RobotAgentEventsListenerException(message);
}
}
private void handleInitFile() {
final IFile currentInitFile = keywordExecutionManager.getCurrentInitFile();
if (currentInitFile == null) {
tryToFindInitSuiteFile();
} else {
switchSuite(currentInitFile.getParent(), currentInitFile);
}
}
private void tryToFindInitSuiteFile() {
final IContainer suiteContainer = ResourcesPlugin.getWorkspace()
.getRoot()
.getContainerForLocation(keywordExecutionManager.getCurrentSuiteLocation());
if (suiteContainer != null
&& (suiteContainer.getType() == IResource.FOLDER || suiteContainer.getType() == IResource.PROJECT)) {
final Optional<IFile> initFile = findInitSuiteFile(suiteContainer);
if (initFile.isPresent()) {
keywordExecutionManager.setCurrentInitFile(initFile.get());
switchSuite(suiteContainer, initFile.get());
}
}
}
private Optional<IFile> findInitSuiteFile(final IContainer suiteContainer) {
for (final String initFileName : Arrays.asList("__init__.robot", "__init__.txt", "__init__.tsv")) {
final IResource member = suiteContainer.findMember(initFileName);
if (member != null && member.getType() == IResource.FILE) {
return Optional.of((IFile) member);
}
}
return Optional.empty();
}
private void switchSuite(final IContainer suiteContainer, final IFile suiteFile) {
keywordExecutionManager.setCurrentSuiteParent(suiteContainer);
keywordExecutionManager.setCurrentSuiteName(suiteFile.getName());
keywordExecutionManager.setCurrentSuiteFile(suiteFile);
final RobotSuiteFile robotSuiteFile = RedPlugin.getModelManager().createSuiteFile(suiteFile);
final RobotParser robotParser = robotSuiteFile.getProject().getEagerRobotParser();
executionContext.startSuite(robotParser.parse(suiteFile.getLocation().toFile()).get(0), robotParser);
}
private void showError(final String title, final String message) {
final Display display = PlatformUI.getWorkbench().getDisplay();
display.syncExec(() -> MessageDialog.openError(display.getActiveShell(), title, message));
}
private boolean shouldStopExecution(final String executedFileName, final int keywordLineNumber) {
final boolean hasBreakpoint = keywordExecutionManager.hasBreakpointAtCurrentKeywordPosition(executedFileName,
keywordLineNumber, debugTarget);
return hasBreakpoint || (keywordLineNumber >= 0 && debugTarget.getRobotThread().isStepping()
&& !debugTarget.hasStepOver() && !debugTarget.hasStepReturn());
}
private void resetSteppingState() {
if (debugTarget.getRobotThread().isStepping()) {
debugTarget.getRobotThread().setSteppingOver(false);
debugTarget.getRobotThread().setSteppingReturn(false);
}
}
private void resetStackFramesState() {
debugTarget.setHasStackFramesCreated(false);
}
@Override
public void handleKeywordEnded(final String name, final String type) {
debugTarget.getCurrentKeywordsContext().remove(name);
executionContext.endKeyword(type);
}
@Override
public void handleResourceImport(final File resourceFilePath) {
executionContext.resourceImport(resourceFilePath);
}
@Override
public void handleGlobalVariables(final Map<String, String> globalVars) {
debugTarget.getRobotVariablesManager().setGlobalVariables(globalVars);
}
@Override
public void handleVariables(final Map<String, Object> vars) {
debugTarget.getLastKeywordFromCurrentContext().setVariables(vars);
debugTarget.getRobotVariablesManager().sortVariablesNames(vars);
}
@Override
public void handleLogMessage(final String msg, final LogLevel level, final String timestamp) {
}
@Override
public void handleOutputFile(final File outputFilepath) {
}
@Override
public void handleCheckCondition() {
try {
if (keywordExecutionManager.hasBreakpointCondition()) {
client.send(new EvaluateCondition(keywordExecutionManager.getBreakpointConditionCall()));
} else if (isStopping) {
client.send(new StopExecution());
} else {
client.send(new ContinueExecution());
}
} catch (ResponseException | IOException e) {
throw new RobotAgentEventsListenerException("Unable to send response to client", e);
}
}
@Override
public void handleConditionError(final String error) {
isBreakpointConditionFulfilled = true;
showError("Conditional Breakpoint Error", "Reason:\n" + error);
}
@Override
public void handleConditionResult(final boolean result) {
isBreakpointConditionFulfilled = result;
}
@Override
public void handleConditionChecked() {
try {
if (isStopping && isBreakpointConditionFulfilled) {
client.send(new StopExecution());
} else {
client.send(new ContinueExecution());
}
isBreakpointConditionFulfilled = false;
keywordExecutionManager.resetBreakpointCondition();
} catch (ResponseException | IOException e) {
throw new RobotAgentEventsListenerException("Unable to send response to client", e);
}
}
@Override
public void handleClosed() {
debugTarget.terminated();
}
@Override
public void handlePaused() {
debugTarget.suspended(DebugEvent.CLIENT_REQUEST);
}
}