/* * Copyright 2012 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.tools.core.dart2js; import com.google.dart.tools.core.DartCore; import org.eclipse.core.runtime.IPath; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** * Object managing interaction with a Dart compilation and analysis service running in a separate * process. * * @coverage dart.tools.core.dart2js */ public class JsonServer { private final Object lock = new Object(); private final Charset utf8Charset; private final Socket requestSocket; private final OutputStream requestStream; private final InputStream responseStream; private final Map<Integer, ResponseHandler> responseHandlers; private int nextRequestId = 0; public JsonServer(String host, int port) throws UnknownHostException, IOException { utf8Charset = Charset.forName("UTF-8"); requestSocket = new Socket(host, port); requestStream = requestSocket.getOutputStream(); responseStream = requestSocket.getInputStream(); responseHandlers = new HashMap<Integer, ResponseHandler>(); new Thread() { @Override public void run() { try { processResponses(); } catch (SocketException exception) { // java.net.SocketException: Socket closed if (!exception.toString().contains(" closed")) { // if (DartCoreDebug.VERBOSE) { // DartCore.logError("Exception from JSON server", exception); // } } } catch (IOException exception) { // java.io.IOException: ...stream is closed if (!exception.toString().contains(" closed")) { DartCore.logError("Exception from JSON server", exception); } } }; }.start(); } public void compile(IPath inputPath, IPath outputPath, ResponseHandler handler) throws IOException { JSONObject request = new JSONObject(); try { request.put("command", "compile"); request.put("input", inputPath.toOSString()); if (outputPath != null) { request.put("output", outputPath.toOSString()); } sendRequest(request, handler); } catch (JSONException e) { throw new IOException("Failed to format request", e); } } /** * Close the socket and streams, signaling the external process to terminate */ public void shutdown() throws IOException { JSONObject request = new JSONObject(); try { request.put("command", "close"); sendRequest(request, new ResponseHandler() { }); } catch (JSONException exception) { throw new IOException(exception); } responseStream.close(); requestStream.close(); requestSocket.close(); } /** * Process responses from the JSON server on a background thread. */ private void processResponses() throws IOException { final byte[] buf = new byte[4]; byte[] messageBuf = new byte[10]; while (true) { readBytes(buf, 4); int messageLen = ((buf[0] & 0xFF) << 24) | ((buf[1] & 0xFF) << 16) | ((buf[2] & 0xFF) << 8) | (buf[3] & 0xFF); if (messageBuf.length < messageLen) { messageBuf = new byte[messageLen]; } readBytes(messageBuf, messageLen); String message = new String(messageBuf, 0, messageLen, utf8Charset); Response response; int id; try { response = new Response(new JSONObject(message)); id = response.getId(); } catch (JSONException e) { DartCore.logError("Failed to parse server response: " + message, e); continue; } ResponseHandler handler = responseHandlers.get(id); if (handler == null) { DartCore.logError("Unknown handler for server response: " + message); continue; } if (handler.process(response)) { responseHandlers.remove(id); } } } /** * Read bytes from the response stream into the specified buffer. This blocks until either the * specified number of bytes have been received and placed into the buffer. * * @param buffer the byte buffer (not <code>null</code>) * @param numBytes the number of bytes to receive */ private void readBytes(final byte[] buffer, int numBytes) throws IOException { int start = 0; while (start < numBytes) { int count = responseStream.read(buffer, start, numBytes - start); if (count < 0) { throw new IOException("Failed to read response because stream is closed"); } start += count; } } /** * Send a request to the JSON server. * * @param message the message (not <code>null</code>) */ private void sendMessage(String message) throws IOException { byte[] bytes = message.getBytes(utf8Charset); int len = bytes.length; byte[] temp = new byte[len + 4]; System.arraycopy(bytes, 0, temp, 4, len); bytes = temp; bytes[0] = (byte) (len >>> 24); bytes[1] = (byte) (len >>> 16); bytes[2] = (byte) (len >>> 8); bytes[3] = (byte) len; // We send the bytes out in one write to decrease the chance that the receiving code will get // the data in multiple packets, and cause a false warning on the server side. requestStream.write(bytes); requestStream.flush(); } /** * Send a request to the JSON server after adding a unique "id" to the request * * @param request the request (not <code>null</code>) * @param handler the response handler * @return the response handler (not <code>null</code>) */ private ResponseHandler sendRequest(JSONObject request, ResponseHandler handler) throws IOException, JSONException { if (handler == null) { throw new IllegalArgumentException("handler cannot be null"); } synchronized (lock) { nextRequestId++; request.put("id", nextRequestId); responseHandlers.put(nextRequestId, handler); } sendMessage(request.toString()); return handler; } }