/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.server.internal.remote;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.dart.server.AnalysisServerSocket;
import com.google.dart.server.utilities.logging.Logging;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import java.util.ArrayList;
import java.util.List;
/**
* A remote server socket over standard input and output.
*
* @coverage dart.server.remote
*/
public class StdioServerSocket implements AnalysisServerSocket {
private final String runtimePath;
private final List<String> vmArguments;
private final String analysisServerPath;
private final List<String> serverArguments;
private final DebugPrintStream debugStream;
private RequestSink requestSink;
private ResponseStream responseStream;
private ByteLineReaderStream errorStream;
private Process process;
/**
* The identifier used to identify this client to the server, or {@code null} if the client does
* not choose to identify itself.
*/
private String clientId;
/**
* The identifier used to identify this client to the server, or {@code null} if the client does
* not choose to identify itself.
*/
private String clientVersion;
public StdioServerSocket(String runtimePath, List<String> additionalVmArguments,
String analysisServerPath, List<String> additionalServerArguments,
DebugPrintStream debugStream) {
this.runtimePath = runtimePath;
this.vmArguments = defaultIfNull(additionalVmArguments, Lists.<String> newArrayList());
this.analysisServerPath = analysisServerPath;
this.serverArguments = defaultIfNull(additionalServerArguments, Lists.<String> newArrayList());
this.debugStream = debugStream;
}
@Override
public ByteLineReaderStream getErrorStream() {
Preconditions.checkNotNull(errorStream, "Server is not started.");
return errorStream;
}
@Override
public RequestSink getRequestSink() {
Preconditions.checkNotNull(requestSink, "Server is not started.");
return requestSink;
}
@Override
public ResponseStream getResponseStream() {
Preconditions.checkNotNull(responseStream, "Server is not started.");
return responseStream;
}
@Override
public boolean isOpen() {
try {
if (process != null) {
process.exitValue();
}
return false;
} catch (IllegalThreadStateException ex) {
return true;
}
}
/**
* Set the identifier used to identify this client to the server to the given identifier. The
* identifier must be set before the server has been started.
*/
public void setClientId(String id) {
clientId = id;
}
/**
* Set the identifier used to identify this client version to the server to the given identifier.
* The identifier must be set before the server has been started.
*/
public void setClientVersion(String version) {
clientVersion = version;
}
@Override
public void start() throws Exception {
String[] arguments = computeProcessArguments();
if (debugStream != null) {
StringBuilder builder = new StringBuilder();
builder.append(" ");
int count = arguments.length;
for (int i = 0; i < count; i++) {
if (i > 0) {
builder.append(' ');
}
builder.append(arguments[i]);
}
debugStream.println(System.currentTimeMillis() + " started analysis server:");
debugStream.println(builder.toString());
}
ProcessBuilder processBuilder = new ProcessBuilder(arguments);
process = processBuilder.start();
requestSink = new ByteRequestSink(process.getOutputStream(), debugStream);
responseStream = new ByteResponseStream(process.getInputStream(), debugStream, () -> requestSink.close());
errorStream = new ByteLineReaderStream(process.getErrorStream());
}
/**
* Wait up to 5 seconds for process to gracefully exit, then forcibly terminate the process if it
* is still running.
*/
@Override
public void stop() {
if (process == null) {
return;
}
final Process processToStop = process;
process = null;
long endTime = System.currentTimeMillis() + 5000;
while (System.currentTimeMillis() < endTime) {
try {
int exit = processToStop.exitValue();
if (exit != 0) {
Logging.getLogger().logInformation(
"Non-zero exit code: " + exit + " for\n " + analysisServerPath);
}
return;
} catch (IllegalThreadStateException e) {
//$FALL-THROUGH$
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
//$FALL-THROUGH$
}
}
processToStop.destroy();
Logging.getLogger().logInformation("Terminated " + analysisServerPath);
}
/**
* Compute and return the command-line arguments used to start the analysis server process.
*
* @return the command-line arguments that were computed
*/
private String[] computeProcessArguments() {
List<String> args = new ArrayList<String>();
//
// The path to the VM.
//
args.add(runtimePath);
//
// VM arguments.
//
args.addAll(vmArguments);
//
// The analysis server path.
//
args.add(analysisServerPath);
//
// Analysis server arguments.
//
if (clientId != null) {
args.add("--client-id=" + clientId);
}
if (clientVersion != null) {
args.add("--client-version=" + clientVersion);
}
args.addAll(serverArguments);
// Done.
return args.toArray(new String[args.size()]);
}
}