/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.nodejsdbg.server;
import org.eclipse.che.api.debug.shared.model.Breakpoint;
import org.eclipse.che.api.debug.shared.model.DebuggerInfo;
import org.eclipse.che.api.debug.shared.model.Location;
import org.eclipse.che.api.debug.shared.model.SimpleValue;
import org.eclipse.che.api.debug.shared.model.StackFrameDump;
import org.eclipse.che.api.debug.shared.model.Variable;
import org.eclipse.che.api.debug.shared.model.VariablePath;
import org.eclipse.che.api.debug.shared.model.action.ResumeAction;
import org.eclipse.che.api.debug.shared.model.action.StartAction;
import org.eclipse.che.api.debug.shared.model.action.StepIntoAction;
import org.eclipse.che.api.debug.shared.model.action.StepOutAction;
import org.eclipse.che.api.debug.shared.model.action.StepOverAction;
import org.eclipse.che.api.debug.shared.model.impl.DebuggerInfoImpl;
import org.eclipse.che.api.debug.shared.model.impl.SimpleValueImpl;
import org.eclipse.che.api.debug.shared.model.impl.StackFrameDumpImpl;
import org.eclipse.che.api.debug.shared.model.impl.event.BreakpointActivatedEventImpl;
import org.eclipse.che.api.debug.shared.model.impl.event.DisconnectEventImpl;
import org.eclipse.che.api.debug.shared.model.impl.event.SuspendEventImpl;
import org.eclipse.che.api.debugger.server.Debugger;
import org.eclipse.che.api.debugger.server.exceptions.DebuggerException;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.plugin.nodejsdbg.server.command.NodeJsDebugCommandsLibrary;
import org.eclipse.che.plugin.nodejsdbg.server.exception.NodeJsDebuggerException;
import org.eclipse.che.plugin.nodejsdbg.server.exception.NodeJsDebuggerTerminatedException;
import org.eclipse.che.plugin.nodejsdbg.server.parser.NodeJsBackTraceParser;
import org.eclipse.che.plugin.nodejsdbg.server.parser.NodeJsStepParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import static org.eclipse.che.plugin.nodejsdbg.server.OutputReader.DEBUG_TIMED_OUT_MSG;
/**
* Server side NodeJs debugger.
*
* @author Anatoliy Bazko
*/
public class NodeJsDebugger implements Debugger, NodeJsProcessObserver {
private static final Logger LOG = LoggerFactory.getLogger(NodeJsDebugger.class);
private final Integer pid;
private final URI uri;
private final String name;
private final String version;
private final String script;
private final NodeJsDebugProcess nodeJsDebugProcess;
private final DebuggerCallback debuggerCallback;
private final NodeJsDebugCommandsLibrary library;
NodeJsDebugger(@Nullable Integer pid,
@Nullable URI uri,
@Nullable String script,
NodeJsDebugProcess nodeJsDebugProcess,
DebuggerCallback debuggerCallback) throws NodeJsDebuggerException {
this.pid = pid;
this.uri = uri;
this.script = script;
this.nodeJsDebugProcess = nodeJsDebugProcess;
this.library = new NodeJsDebugCommandsLibrary(nodeJsDebugProcess);
this.name = library.getName();
this.version = library.getVersion();
this.debuggerCallback = debuggerCallback;
this.nodeJsDebugProcess.addObserver(this);
}
public static NodeJsDebugger newInstance(@Nullable Integer pid,
@Nullable URI uri,
String file,
DebuggerCallback debuggerCallback) throws DebuggerException {
NodeJsDebugProcess nodeJsDebugProcess = NodeJsDebugProcess.start(file);
return new NodeJsDebugger(pid,
uri,
file,
nodeJsDebugProcess,
debuggerCallback);
}
@Override
public DebuggerInfo getInfo() throws DebuggerException {
return new DebuggerInfoImpl(uri == null ? "" : uri.getHost(),
uri == null ? -1 : uri.getPort(),
name,
version,
pid == null ? -1 : pid,
script);
}
@Override
public void disconnect() {
debuggerCallback.onEvent(new DisconnectEventImpl());
nodeJsDebugProcess.stop();
}
@Override
public void addBreakpoint(Breakpoint breakpoint) throws DebuggerException {
try {
Location location = breakpoint.getLocation();
library.setBreakpoint(location.getTarget(), location.getLineNumber());
debuggerCallback.onEvent(new BreakpointActivatedEventImpl(breakpoint));
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
throw e;
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Can't add breakpoint: " + breakpoint + ". " + e.getMessage(), e);
}
}
@Override
public void deleteBreakpoint(Location location) throws DebuggerException {
try {
library.clearBreakpoint(location.getTarget(), location.getLineNumber());
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
throw e;
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Can't delete breakpoint: " + location + ". " + e.getMessage(), e);
}
}
@Override
public void deleteAllBreakpoints() throws DebuggerException {
try {
for (Breakpoint breakpoint : library.getBreakpoints()) {
try {
deleteBreakpoint(breakpoint.getLocation());
} catch (NodeJsDebuggerException e) {
LOG.error("Can't delete breakpoint: {}", breakpoint.getLocation(), e);
}
}
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
throw e;
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Can't delete all breakpoints. " + e.getMessage(), e);
}
}
@Override
public List<Breakpoint> getAllBreakpoints() throws DebuggerException {
try {
return library.getBreakpoints();
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
throw e;
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Can't get all breakpoints. " + e.getMessage(), e);
}
}
@Override
public void start(StartAction action) throws DebuggerException {
try {
for (Breakpoint breakpoint : action.getBreakpoints()) {
Location location = breakpoint.getLocation();
library.setBreakpoint(location.getTarget(), location.getLineNumber());
debuggerCallback.onEvent(new BreakpointActivatedEventImpl(breakpoint));
}
library.backtrace();
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
throw e;
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Start error. " + e.getMessage(), e);
}
}
@Override
public void stepOver(StepOverAction action) throws DebuggerException {
try {
library.next();
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Step over error. " + e.getMessage(), e);
}
}
@Override
public void stepInto(StepIntoAction action) throws DebuggerException {
try {
library.stepIn();
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Step into error. " + e.getMessage(), e);
}
}
@Override
public void stepOut(StepOutAction action) throws DebuggerException {
try {
library.stepOut();
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Step out error. " + e.getMessage(), e);
}
}
@Override
public void resume(ResumeAction action) throws DebuggerException {
try {
library.cont();
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Resume error. " + e.getMessage(), e);
}
}
@Override
public void setValue(Variable variable) throws DebuggerException {
try {
List<String> path = variable.getVariablePath().getPath();
if (path.isEmpty()) {
throw new DebuggerException("Variable path is empty");
}
library.setVar(path.get(0), variable.getValue());
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
throw e;
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Can't set value for " + variable.getName() + ". " + e.getMessage(), e);
}
}
@Override
public SimpleValue getValue(VariablePath variablePath) throws DebuggerException {
try {
List<String> path = variablePath.getPath();
if (path.isEmpty()) {
throw new DebuggerException("Variable path is empty");
}
return new SimpleValueImpl(Collections.emptyList(), library.getVar(path.get(0)));
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
throw e;
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Can't get value for " + variablePath + ". " + e.getMessage(), e);
}
}
@Override
public String evaluate(String expression) throws DebuggerException {
try {
return library.evaluate(expression);
} catch (NodeJsDebuggerTerminatedException e) {
disconnect();
throw e;
} catch (NodeJsDebuggerException e) {
throw new DebuggerException("Can't evaluate '" + expression + "'. " + e.getMessage(), e);
}
}
@Override
public StackFrameDump dumpStackFrame() throws DebuggerException {
return new StackFrameDumpImpl(Collections.emptyList(), Collections.emptyList());
}
@Override
public boolean onOutputProduced(NodeJsOutput nodeJsOutput) throws NodeJsDebuggerException {
if (NodeJsStepParser.INSTANCE.match(nodeJsOutput)) {
SuspendEventImpl suspendEvent = new SuspendEventImpl(NodeJsStepParser.INSTANCE.parse(nodeJsOutput));
debuggerCallback.onEvent(suspendEvent);
} else if (NodeJsBackTraceParser.INSTANCE.match(nodeJsOutput)) {
SuspendEventImpl suspendEvent = new SuspendEventImpl(NodeJsBackTraceParser.INSTANCE.parse(nodeJsOutput));
debuggerCallback.onEvent(suspendEvent);
} else if (DEBUG_TIMED_OUT_MSG.equals(nodeJsOutput.getOutput())) {
disconnect();
} else {
return false;
}
return true;
}
}