/** * Copyright (c) 2013-2016 Angelo ZERR. * 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: * Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation */ package tern.eclipse.ide.server.nodejs.core.debugger; import java.io.File; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationType; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.IStreamListener; import org.eclipse.debug.core.model.IStreamMonitor; import tern.TernException; import tern.eclipse.ide.server.nodejs.core.debugger.launchConfigurations.NodejsCliFileHelper; import tern.server.nodejs.process.AbstractNodejsProcess; import tern.server.nodejs.process.NodejsProcessException; import tern.utils.TernModuleHelper; /** * Abstract class for node debug process. * */ public abstract class AbstractNodejsDebugProcess extends AbstractNodejsProcess { private final IFile jsFile; // (ex : bin/tern file) private final String launchConfigId; private final List<StreamProcessor> streamProcessors; protected ILaunch launch; public AbstractNodejsDebugProcess(IFile jsFile, File workingDir, File nodejsInstallPath, String launchConfigId) throws TernException { super(nodejsInstallPath, workingDir); this.jsFile = jsFile; this.launchConfigId = launchConfigId; this.streamProcessors = new ArrayList<AbstractNodejsDebugProcess.StreamProcessor>(); } protected String getNodeInstallPath() { return TernModuleHelper.getPath(nodejsBaseDir); } protected String getWorkingDir() { return NodejsCliFileHelper.getWorkspaceLoc(getProjectDir()); } @Override protected void notifyStopProcess() { for (StreamProcessor proc : streamProcessors) { proc.close(); } super.notifyStopProcess(); } @Override public void start() throws NodejsProcessException { if (isStarted()) { notifyErrorProcess("Nodejs tern Server is already started."); //$NON-NLS-1$ throw new NodejsProcessException("Nodejs tern Server is already started."); //$NON-NLS-1$ } ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager(); try { ILaunchConfigurationType type = manager.getLaunchConfigurationType(launchConfigId); String launchName = generateConfigurationName(); ILaunchConfigurationWorkingCopy workingCopy = null; // Try to find existing launch ILaunchConfiguration configuration = getExistingLaunchConfiguration(type, launchName); if (configuration != null) { workingCopy = configuration.getWorkingCopy(); } else { workingCopy = type.newInstance(null, manager.generateLaunchConfigurationName(launchName)); } start(workingCopy); if (isSaveLaunch()) { workingCopy.doSave(); } } catch (Exception e) { if (e instanceof NodejsProcessException) { throw (NodejsProcessException) e; } throw new NodejsProcessException(e); } } private ILaunchConfiguration getExistingLaunchConfiguration(ILaunchConfigurationType type, String launchName) throws CoreException { ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager(); ILaunchConfiguration[] configs = manager.getLaunchConfigurations(type); for (ILaunchConfiguration config : configs) { if (config.getName().startsWith(launchName)) { return config; } } return null; } @Override public boolean isStarted() { return launch != null && !launch.isTerminated(); } @Override public void kill() { if (launch != null && !launch.isTerminated()) { try { launch.terminate(); } catch (DebugException e) { throw new RuntimeException(e); } } launch = null; } @Override public void join() throws InterruptedException { throw new UnsupportedOperationException(); } private abstract class StreamProcessor implements IStreamListener { private StringBuilder lineBuilder = new StringBuilder(); private IStreamMonitor monitor; public StreamProcessor(IStreamMonitor monitor) { this.monitor = monitor; monitor.addListener(this); streamProcessors.add(this); streamAppended(monitor.getContents(), monitor); } public void close() { streamAppended("\n", monitor); //$NON-NLS-1$ monitor.removeListener(this); } @Override public synchronized void streamAppended(String text, IStreamMonitor monitor) { int pos = 0; int lastPos = 0; while ((pos = text.indexOf('\n', pos)) >= 0) { lineBuilder.append(text.substring(0, pos++)); lastPos = pos; if (lineBuilder.length() > 0) { processLine(lineBuilder.toString()); } lineBuilder.setLength(0); } lineBuilder.append(text.substring(lastPos)); } protected abstract void processLine(String line); } protected class StdOut extends StreamProcessor { private long startTime; public StdOut(IStreamMonitor monitor) { super(monitor); startTime = System.nanoTime(); } @Override protected void processLine(String line) { if (getPort() == null) { if (isWaitOnPort()) { // port was not getted, try to get it. if (line.length() > 0) { // $NON-NLS-1$ Integer port = Integer.parseInt(line.substring("Listening on port ".length(), line.length())); //$NON-NLS-1$ // port is getted, notify that process is // started. setPort(port); synchronized (lock) { lock.notifyAll(); } if (startTime == 0) { startTime = System.nanoTime(); } notifyStartProcess(startTime); } } else { synchronized (lock) { lock.notifyAll(); } if (startTime == 0) { startTime = System.nanoTime(); } notifyStartProcess(startTime); notifyDataProcess(line); } } else { // notify data notifyDataProcess(line); } } } protected class StdErr extends StreamProcessor { public StdErr(IStreamMonitor monitor) { super(monitor); } @Override protected void processLine(String line) { if (!line.startsWith("debugger listening on port")) { //$NON-NLS-1$ notifyErrorProcess(line); } } } public IFile getJsFile() { return jsFile; } protected String getMode() throws NodejsProcessException { return getLaunchConfiguration().getLaunchMode(); } private boolean isSaveLaunch() throws NodejsProcessException { return getLaunchConfiguration().isSaveLaunch(); } private boolean isWaitOnPort() { try { return getLaunchConfiguration().isWaitOnPort(); } catch (NodejsProcessException e) { return false; } } private String generateConfigurationName() throws NodejsProcessException { return getLaunchConfiguration().generateLaunchConfigurationName(); } protected abstract void start(ILaunchConfigurationWorkingCopy lcwc) throws Exception; }