/**
* 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.internal;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import tern.TernException;
import tern.server.nodejs.process.AbstractNodejsProcess;
import tern.server.nodejs.process.NodejsProcessException;
/**
* node.js process which starts tern server with node.js
*/
public class NodejsProcess extends AbstractNodejsProcess {
/**
* The node.js tern file.
*/
private final File nodejsTernFile;
/**
* node.js process.
*/
private Process process;
/**
* StdOut thread.
*/
private Thread outThread;
/**
* StdErr thread.
*/
private Thread errThread;
/**
* StdOut of the node.js process.
*/
private class StdOut implements Runnable {
@Override
public void run() {
try {
long startTime = System.nanoTime();
// start the node.js process with tern.
Integer port = null;
String line = null;
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
try {
while ((line = br.readLine()) != null) {
if (port == null) {
// port was not getted, try to get it.
if (line.startsWith("Listening on port ")) {
port = Integer.parseInt(line.substring("Listening on port ".length(), line.length()));
// port is getted, notify that process is
// started.
setPort(port);
synchronized (lock) {
lock.notifyAll();
}
notifyStartProcess(startTime);
}
} else {
// notify data
notifyDataProcess(line);
}
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.waitFor();
}
notifyStopProcess();
kill();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
/**
* StdErr of the node.js process.
*/
private class StdErr implements Runnable {
@Override
public void run() {
String line = null;
InputStream is = process.getErrorStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
try {
while ((line = br.readLine()) != null) {
notifyErrorProcess(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Nodejs process constructor which uses the installed node.js.
*
* @param nodejsTernBaseDir
* the node.js tern base dir.
* @param projectDir
* the project base dir where .tern-project is hosted.
* @throws TernException
*/
public NodejsProcess(File nodejsTernBaseDir, File projectDir) throws TernException {
this(null, nodejsTernBaseDir, projectDir);
}
/**
* 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 NodejsProcess(File nodejsBaseDir, File nodejsTernBaseDir, File projectDir) throws TernException {
super(nodejsBaseDir, projectDir);
this.nodejsTernFile = getNodejsTernFile(nodejsTernBaseDir);
}
/**
* Returns the node.js tern file.
*
* @param nodejsTernBaseDir
* tern base dir.
* @return the node.js tern file.
* @throws TernException
*/
private File getNodejsTernFile(File nodejsTernBaseDir) throws TernException {
if (nodejsTernBaseDir == null) {
throw new TernException("You must initialize the base dir of the tern node.js server.");
}
File ternServerFile = new File(nodejsTernBaseDir, "bin/tern");
if (!ternServerFile.exists()) {
try {
throw new TernException("Cannot find tern node.js server at " + ternServerFile.getCanonicalPath());
} catch (IOException e) {
throw new TernException("Cannot find tern node.js server at " + ternServerFile.getPath());
}
}
return ternServerFile;
}
/**
* Create process commands to start tern with node.js
*
* @return
* @throws NodejsProcessException
* @throws IOException
*/
private List<String> createCommands() throws NodejsProcessException {
List<String> commands = new LinkedList<String>();
if (nodejsBaseDir == null) {
// for osx, path of node.js should be setted?
if (new File("/usr/local/bin/node").exists()) {
commands.add("/usr/local/bin/node");
} else if (new File("/opt/local/bin/node").exists()) {
commands.add("/opt/local/bin/node");
} else {
commands.add("node");
}
} else {
commands.add(nodejsBaseDir.getPath());
}
try {
commands.add(nodejsTernFile.getCanonicalPath());
} catch (IOException e) {
commands.add(nodejsTernFile.getPath());
}
commands.addAll(createNodejsArgs());
return commands;
}
/**
* Start process.
*
* @throws IOException
* @throws InterruptedException
*/
public void start() throws NodejsProcessException {
if (isStarted()) {
notifyErrorProcess("Nodejs tern Server is already started.");
throw new NodejsProcessException("Nodejs tern Server is already started.");
}
try {
List<String> commands = createCommands();
ProcessBuilder builder = new ProcessBuilder(commands);
// builder.redirectErrorStream(true);
builder.directory(getProjectDir());
notifyCreateProcess(commands, projectDir);
this.process = builder.start();
outThread = new Thread(new StdOut());
outThread.setDaemon(true);
outThread.start();
errThread = new Thread(new StdErr());
errThread.setDaemon(true);
errThread.start();
} catch (Throwable e) {
notifyErrorProcess(e.getMessage());
notifyErrorProcess("");
throw new NodejsProcessException(e);
}
}
/**
* Return true id process is started and false otherwise.
*
* @return
*/
@Override
public boolean isStarted() {
return process != null;
}
/**
* Kill the process.
*/
@Override
public void kill() {
if (process != null) {
process.destroy();
process = null;
}
if (outThread != null) {
outThread.interrupt();
outThread = null;
}
if (errThread != null) {
errThread.interrupt();
errThread = null;
}
}
/**
* Joint to the stdout thread;
*
* @throws InterruptedException
*/
public void join() throws InterruptedException {
if (outThread != null) {
outThread.join();
}
}
}