/** * Copyright (c) 2013-2016 Angelo ZERR and Genuitec LLC. * 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 * Piotr Tomiak <piotr@genuitec.com> - support for tern.js debugging */ package tern.server.nodejs.process; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import tern.TernException; import tern.server.nodejs.NodejsTernHelper; /** * node.js process which starts tern server with node.js */ public abstract class AbstractNodejsProcess implements INodejsProcess { /** * The node.js base dir. */ protected final File nodejsBaseDir; /** * The project dir where .tern-project is hosted. */ protected final File projectDir; private INodejsLaunchConfiguration launchConfiguration; /** * Port of the node.js server. */ private Integer port; /** * Elapsed time to start node.js process. */ private long elapsedSartTime = 0; /** * Process listeners. */ protected final List<INodejsProcessListener> listeners; /** * Lock used to wait the start of the server to retrieve port in the getPort * method. */ protected final Object lock = new Object(); private boolean hasError; /** * Nodejs process constructor. * * @param nodejsBaseDir * the node.js base dir. * @param nodejsTernBaseDir * the node.js tern base dir. * @param projectDir * the project base dir where .tern-project is hosted. * @throws TernException */ public AbstractNodejsProcess(File nodejsBaseDir, File projectDir) throws TernException { this.projectDir = projectDir; this.nodejsBaseDir = nodejsBaseDir; this.listeners = new ArrayList<INodejsProcessListener>(); this.hasError = false; } /** * Start the process and returns the port of the started process. * * @param timeout * to wait until the process start to retrieve the port to * return. * @return * @throws InterruptedException * @throws IOException * @throws TernException */ public int start(long timeout, int testNumber) throws NodejsProcessException, InterruptedException { if (!isStarted()) { start(); } waitOnStartNodejs(timeout, testNumber); return getPort(); } /** * Wait until node.js process is started. * * @param timeout * to wait until the process start to retrieve the port to * return. * @param testNumber * number of test to do to wait until timeout ms. * @throws InterruptedException * throw this exception if node.js process cannot be started. * @throws NodejsProcessException * throw this exception if node.js process cannot be started. */ private void waitOnStartNodejs(long timeout, int testNumber) throws InterruptedException, NodejsProcessException { if (port == null) { // node.js process is not started, loop for test number and wait on // timeout. if (!hasError) { for (int i = 0; i < testNumber; i++) { synchronized (lock) { // wait until timeout. lock.wait(timeout); if (port != null || hasError) { // here node.js is started, stop teh wait. break; } } } } if (port == null) { // here node.js cannot be started. throw new NodejsProcessException("Cannot start node process."); } } } /** * Return the node.js port and null if not started. * * @return */ public Integer getPort() { return port; } /** * Set the port when process is started; * * @param port */ protected void setPort(Integer port) { this.port = port; } /** * return the project dir where .tern-project is hosted. * * @return */ public File getProjectDir() { return projectDir; } /** * Returns the elapsed time to start node.js process. * * @return */ public long getElapsedStartTime() { return elapsedSartTime; } /** * Add the given process listener. * * @param listener */ public void addProcessListener(INodejsProcessListener listener) { synchronized (listeners) { listeners.add(listener); } } /** * Remove the given process listener. * * @param listener */ public void removeProcessListener(INodejsProcessListener listener) { synchronized (listeners) { listeners.remove(listener); } } /** * Notify start process. */ protected void notifyCreateProcess(List<String> commands, File projectDir) { synchronized (listeners) { for (INodejsProcessListener listener : listeners) { listener.onCreate(this, commands, projectDir); } } } /** * Notify start process. * * @param startTime * time when node.js process is started. */ protected void notifyStartProcess(long startTime) { this.elapsedSartTime = NodejsTernHelper.getElapsedTimeInMs(startTime); synchronized (listeners) { for (INodejsProcessListener listener : listeners) { listener.onStart(this); } } } /** * Notify stop process. */ protected void notifyStopProcess() { synchronized (listeners) { for (INodejsProcessListener listener : listeners) { listener.onStop(this); } } } /** * Notify data process. * * @param line */ protected void notifyDataProcess(String line) { synchronized (listeners) { for (INodejsProcessListener listener : listeners) { listener.onData(this, line); } } } /** * Notify error process. */ protected void notifyErrorProcess(String line) { this.hasError = true; synchronized (listeners) { for (INodejsProcessListener listener : listeners) { listener.onError(AbstractNodejsProcess.this, line); } } } protected List<String> createNodejsArgs() throws NodejsProcessException { return getLaunchConfiguration().createNodeArgs(); } protected INodejsLaunchConfiguration getLaunchConfiguration() throws NodejsProcessException { if (launchConfiguration == null) { throw new NodejsProcessException("Launch configuration cannot be null."); } return launchConfiguration; } @Override public void setLaunchConfiguration(INodejsLaunchConfiguration launchConfiguration) { this.launchConfiguration = launchConfiguration; } }