/******************************************************************************* * 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.command; import org.eclipse.che.api.debug.shared.model.Breakpoint; import org.eclipse.che.api.debug.shared.model.Location; import org.eclipse.che.api.debug.shared.model.impl.BreakpointImpl; import org.eclipse.che.api.debug.shared.model.impl.LocationImpl; import org.eclipse.che.plugin.nodejsdbg.server.NodeJsDebugProcess; import org.eclipse.che.plugin.nodejsdbg.server.NodeJsDebugger; import org.eclipse.che.plugin.nodejsdbg.server.exception.NodeJsDebuggerException; import org.eclipse.che.plugin.nodejsdbg.server.parser.NodeJsBackTraceParser; import org.eclipse.che.plugin.nodejsdbg.server.parser.NodeJsBreakpointsParser; import org.eclipse.che.plugin.nodejsdbg.server.parser.NodeJsBreakpointsParser.Breakpoints; import org.eclipse.che.plugin.nodejsdbg.server.parser.NodeJsOutputParser; import org.eclipse.che.plugin.nodejsdbg.server.parser.NodeJsOutputParser.NodeJsOutputRegExpParser; import org.eclipse.che.plugin.nodejsdbg.server.parser.NodeJsScriptsParser; import org.eclipse.che.plugin.nodejsdbg.server.parser.NodeJsScriptsParser.Scripts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.regex.Pattern; import static java.lang.String.format; /** * Library of the NodeJs debug commands: https://nodejs.org/api/debugger.html * * @author Anatolii Bazko */ public class NodeJsDebugCommandsLibrary { private static final Logger LOG = LoggerFactory.getLogger(NodeJsDebugger.class); private static final Pattern PROCESS_TITLE_COMMAND_OUTPUT_PATTERN = Pattern.compile("^'?(node|nodejs)'?$"); private static final Pattern PROCESS_VERSION_COMMAND_OUTPUT_PATTERN = Pattern.compile("^'?(v|)[0-9\\.]+'?$"); private static final Pattern PROCESS_PID_COMMAND_OUTPUT_PATTERN = Pattern.compile("^[0-9]+$"); private static final Pattern RUN_COMMAND_OUTPUT_PATTERN = Pattern.compile("(break in.*|App is already running.*)"); private final NodeJsDebugProcess process; private final String name; private final String version; private final int pid; public NodeJsDebugCommandsLibrary(NodeJsDebugProcess process) throws NodeJsDebuggerException { this.process = process; run(); this.name = detectName(); this.version = detectVersion(); this.pid = detectPid(); } /** * Execute {@code bt} command. */ public Void backtrace() throws NodeJsDebuggerException { doExecute(createCommand("bt", NodeJsOutputParser.VOID)); return null; } /** * Execute {@code sb} command. */ public Void setBreakpoint(String scriptPath, int lineNumber) throws NodeJsDebuggerException { String scriptName = Paths.get(scriptPath).getFileName().toString(); String input = format("sb('%s', %d)", scriptName, lineNumber); NodeJsDebugCommand<Void> command = createCommand(input, NodeJsOutputParser.VOID); return doExecute(command); } /** * Execute {@code breakpoints} command. * @see NodeJsBackTraceParser */ public List<Breakpoint> getBreakpoints() throws NodeJsDebuggerException { NodeJsDebugCommand<Breakpoints> breakpointsCommand = createCommand("breakpoints", NodeJsBreakpointsParser.INSTANCE); List<Breakpoint> breakpoints = doExecute(breakpointsCommand).getAll(); NodeJsDebugCommand<Scripts> scriptsCommand = createCommand("scripts", NodeJsScriptsParser.INSTANCE); Map<Integer, String> scripts = doExecute(scriptsCommand).getAll(); for (int i = 0; i < breakpoints.size(); i++) { Breakpoint breakpoint = breakpoints.get(i); Location location = breakpoint.getLocation(); String newTarget; String[] target = location.getTarget().split(":"); if (target.length != 2) { LOG.error(format("Illegal breakpoint location format %s", target[0])); continue; } if (target[0].equals("scriptId")) { newTarget = scripts.get((int)Double.parseDouble(target[1])); } else { newTarget = target[1]; } Location newLocation = new LocationImpl(newTarget, location.getLineNumber()); Breakpoint newBreakpoint = new BreakpointImpl(newLocation, breakpoint.isEnabled(), breakpoint.getCondition()); breakpoints.set(i, newBreakpoint); } return breakpoints; } /** * Execute {@code cb} command. */ public Void clearBreakpoint(String script, int lineNumber) throws NodeJsDebuggerException { String input = format("cb('%s', %d)", script, lineNumber); NodeJsDebugCommand<Void> command = createCommand(input, NodeJsOutputParser.VOID); return doExecute(command); } /** * Execute {@code run} command. */ public String run() throws NodeJsDebuggerException { NodeJsDebugCommand<String> nextCommand = createCommand("run", new NodeJsOutputRegExpParser(RUN_COMMAND_OUTPUT_PATTERN)); return doExecute(nextCommand); } /** * Execute {@code next} command. */ public Void next() throws NodeJsDebuggerException { doExecute(createCommand("next", NodeJsOutputParser.VOID)); return null; } /** * Execute {@code cont} command. */ public Void cont() throws NodeJsDebuggerException { doExecute(createCommand("cont", NodeJsOutputParser.VOID)); return null; } /** * Execute {@code step in} command. */ public Void stepIn() throws NodeJsDebuggerException { doExecute(createCommand("step", NodeJsOutputParser.VOID)); return null; } /** * Execute {@code step out} command. */ public Void stepOut() throws NodeJsDebuggerException { doExecute(createCommand("out", NodeJsOutputParser.VOID)); return null; } /** * Execute {@code exec} command to set a new value for the giving variable. */ public Void setVar(String varName, String newValue) throws NodeJsDebuggerException { String input = format("exec %s=%s", varName, newValue); NodeJsDebugCommand<Void> command = createCommand(input, NodeJsOutputParser.VOID); return doExecute(command); } /** * Execute {@code exec} command to get value for the giving variable. */ public String getVar(String varName) throws NodeJsDebuggerException { String line = format("exec %s", varName); NodeJsDebugCommand<String> command = createCommand(line, NodeJsOutputParser.DEFAULT); return doExecute(command); } /** * Execute {@code exec} command to evaluate expression. */ public String evaluate(String expression) throws NodeJsDebuggerException { String line = format("exec %s", expression); NodeJsDebugCommand<String> command = createCommand(line, NodeJsOutputParser.DEFAULT); return doExecute(command); } /** * Returns NodeJs version. */ public String getVersion() { return version; } /** * Returns NodeJs title. */ public String getName() { return name; } /** * Returns NodeJs pid. */ public int getPid() { return pid; } /** * Returns NodeJs version. */ private String detectVersion() throws NodeJsDebuggerException { NodeJsDebugCommand<String> command = createCommand("process.version", new NodeJsOutputRegExpParser(PROCESS_VERSION_COMMAND_OUTPUT_PATTERN)); return doExecute(command); } /** * Returns NodeJs pid. */ private int detectPid() throws NodeJsDebuggerException { NodeJsDebugCommand<String> command = createCommand("process.pid", new NodeJsOutputRegExpParser(PROCESS_PID_COMMAND_OUTPUT_PATTERN)); return Integer.parseInt(doExecute(command)); } /** * Returns NodeJs title. */ private String detectName() throws NodeJsDebuggerException { NodeJsDebugCommand<String> command = createCommand("process.title", new NodeJsOutputRegExpParser(PROCESS_TITLE_COMMAND_OUTPUT_PATTERN)); return doExecute(command); } /** * Executes {@link NodeJsDebugCommand} and waits result. * * @throws NodeJsDebuggerException * if execution failed */ private <V> V doExecute(NodeJsDebugCommand<V> command) throws NodeJsDebuggerException { process.addObserver(command); try { Future<V> result = command.execute(process); return result.get(); } catch (InterruptedException | ExecutionException e) { throw new NodeJsDebuggerException(e.getMessage(), e); } finally { process.removeObserver(command); } } private <T> NodeJsDebugCommand<T> createCommand(String input, NodeJsOutputParser<T> outputParser) { return new NodeJsDebugCommandImpl<>(outputParser, input); } }