/**
* Copyright (c) 2013-2014 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.server.nodejs.npm;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import tern.TernException;
import tern.server.ITernModule;
import tern.server.nodejs.process.NodejsProcessException;
public class NPMProcess {
/**
* The node.js base dir.
*/
private final String npmPath;
/**
* The project dir where .tern-project is hosted.
*/
private final File projectDir;
/**
* node.js process.
*/
private Process process;
/**
* StdOut thread.
*/
private Thread outThread;
/**
* StdErr thread.
*/
private Thread errThread;
private boolean hasError;
/**
* Process listeners.
*/
private final List<INPMProcessListener> listeners;
public NPMProcess(String npmPath, File projectDir) throws TernException {
this.npmPath = npmPath;
this.projectDir = projectDir;
this.listeners = new ArrayList<INPMProcessListener>();
this.hasError = false;
}
/**
* Start process.
*
* @throws IOException
* @throws InterruptedException
*/
public void install(ITernModule module) 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(module);
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);
}
}
/**
* Create process commands to start tern with node.js
*
* @return
* @throws IOException
*/
/*protected List<String> createCommands() {
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");
}
if (new File("/opt/local/bin/node").exists()) {
commands.add("/opt/local/bin/node");
} else {
commands.add("npm");
}
} else {
commands.add(nodejsBaseDir.getPath());
}
return commands;
}*/
private List<String> createCommands(ITernModule module) {
List<String> cmds = new ArrayList<String>();
cmds.add(npmPath);
cmds.add("install");
cmds.add(new StringBuilder("tern-").append(module.getName()).toString());
return cmds;
}
/**
* Return the project dir of the tern repository;
*
* @return
*/
public File getProjectDir() {
return projectDir;
}
/**
* StdOut of the node.js process.
*/
private class StdOut implements Runnable {
@Override
public void run() {
try {
long startTime = System.nanoTime();
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();
}
}
}
/**
* Return true id process is started and false otherwise.
*
* @return
*/
public boolean isStarted() {
return process != null;
}
/**
* Kill the process.
*/
public void kill() {
if (process != null) {
process.destroy();
process = null;
}
if (outThread != null) {
outThread.interrupt();
outThread = null;
}
if (errThread != null) {
errThread.interrupt();
errThread = null;
}
}
public void addProcessListener(INPMProcessListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/**
* Remove the given process listener.
*
* @param listener
*/
public void removeProcessListener(INPMProcessListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
/**
* Notify start process.
*/
private void notifyCreateProcess(List<String> commands, File projectDir) {
synchronized (listeners) {
for (INPMProcessListener listener : listeners) {
listener.onCreate(this, commands, projectDir);
}
}
}
/**
* Notify start process.
*
* @param startTime
* time when node.js process is started.
*/
private void notifyStartProcess(long startTime) {
// this.elapsedSartTime =
// NodejsTernHelper.getElapsedTimeInMs(startTime);
synchronized (listeners) {
for (INPMProcessListener listener : listeners) {
listener.onStart(this);
}
}
}
/**
* Notify stop process.
*/
private void notifyStopProcess() {
synchronized (listeners) {
for (INPMProcessListener listener : listeners) {
listener.onStop(this);
}
}
}
/**
* Notify data process.
*
* @param line
*/
private void notifyDataProcess(String line) {
synchronized (listeners) {
for (INPMProcessListener listener : listeners) {
listener.onData(this, line);
}
}
}
/**
* Notify error process.
*/
private void notifyErrorProcess(String line) {
this.hasError = true;
synchronized (listeners) {
for (INPMProcessListener listener : listeners) {
listener.onError(NPMProcess.this, line);
}
}
}
}