content: /**
   *  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;
  }
  