--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/transport/Message.java Wed Dec 23 17:13:18 2009 -0800
@@ -0,0 +1,229 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk.internal.transport;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A transport message encapsulating the data sent/received over the wire
+ * (protocol headers and content). This class can serialize and deserialize
+ * itself into a BufferedWriter according to the ChromeDevTools Protocol
+ * specification.
+ */
+public class Message {
+
+ /**
+ * This exception is thrown during Message deserialization whenever the input
+ * is malformed.
+ */
+ public static class MalformedMessageException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public MalformedMessageException() {
+ super();
+ }
+
+ public MalformedMessageException(String message) {
+ super(message);
+ }
+
+ public MalformedMessageException(Throwable cause) {
+ super(cause);
+ }
+
+ public MalformedMessageException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ }
+
+ /**
+ * Known ChromeDevTools Protocol headers (ToolHandler implementations
+ * can add their own headers.)
+ */
+ public enum Header {
+ CONTENT_LENGTH("Content-Length"),
+ TOOL("Tool"),
+ DESTINATION("Destination"), ;
+
+ public final String name;
+
+ Header(String value) {
+ this.name = value;
+ }
+ }
+
+ /**
+ * The class logger.
+ */
+ private static final Logger LOGGER = Logger.getLogger(Message.class.getName());
+
+ /**
+ * The end of protocol header line.
+ */
+ private static final String HEADER_TERMINATOR = "\r\n";
+
+ private final HashMap<String, String> headers;
+
+ private final String content;
+
+ public Message(Map<String, String> headers, String content) {
+ this.headers = new HashMap<String, String>(headers);
+ this.content = content;
+ this.headers.put(Header.CONTENT_LENGTH.name, String.valueOf(content == null
+ ? 0
+ : content.length()));
+ }
+
+ /**
+ * Sends a message through the specified writer.
+ *
+ * @param writer to send the message through
+ * @throws IOException
+ */
+ public void sendThrough(Writer writer) throws IOException {
+ String content = maskNull(this.content);
+ for (Map.Entry<String, String> entry : this.headers.entrySet()) {
+ writeNonEmptyHeader(writer, entry.getKey(), entry.getValue());
+ }
+ writer.write(HEADER_TERMINATOR);
+ if (content.length() > 0) {
+ writer.write(content);
+ }
+ writer.flush();
+ }
+
+ /**
+ * Reads a message from the specified reader.
+ *
+ * @param reader to read message from
+ * @return a new message, or {@code null} if input is invalid (end-of-stream
+ * or bad message format)
+ * @throws IOException
+ * @throws MalformedMessageException if the input does not represent a valid
+ * message
+ */
+ public static Message fromBufferedReader(BufferedReader reader)
+ throws IOException, MalformedMessageException {
+ Map<String, String> headers = new HashMap<String, String>();
+ synchronized (reader) {
+ while (true) { // read headers
+ String line = reader.readLine();
+ if (line == null) {
+ LOGGER.fine("End of stream");
+ return null;
+ }
+ if (line.length() == 0) {
+ break; // end of headers
+ }
+ String[] nameValue = line.split(":", 2);
+ if (nameValue.length != 2) {
+ LOGGER.log(Level.SEVERE, "Bad header line: {0}", line);
+ return null;
+ } else {
+ String trimmedValue = nameValue[1].trim();
+ headers.put(nameValue[0], trimmedValue);
+ }
+ }
+
+ // Read payload if applicable
+ String contentLengthStr = getHeader(headers, Header.CONTENT_LENGTH.name, "0");
+ int contentLength = Integer.valueOf(contentLengthStr.trim());
+ char[] content = new char[contentLength];
+ int totalRead = 0;
+ LOGGER.log(Level.FINER, "Reading payload: {0} bytes", contentLength);
+ while (totalRead < contentLength) {
+ int readBytes = reader.read(content, totalRead, contentLength - totalRead);
+ if (readBytes == -1) {
+ // End-of-stream (browser closed?)
+ LOGGER.fine("End of stream while reading content");
+ return null;
+ }
+ totalRead += readBytes;
+ }
+
+ // Construct response message
+ String contentString = new String(content);
+ return new Message(headers, contentString);
+ }
+ }
+
+ /**
+ * @return the "Tool" header value
+ */
+ public String getTool() {
+ return getHeader(Header.TOOL.name, null);
+ }
+
+ /**
+ * @return the "Destination" header value
+ */
+ public String getDestination() {
+ return getHeader(Header.DESTINATION.name, null);
+ }
+
+ /**
+ * @return the message content. Never {@code null} (for no content, returns an
+ * empty String)
+ */
+ public String getContent() {
+ return content;
+ }
+
+ /**
+ * @param name of the header
+ * @param defaultValue to return if the header is not found in the message
+ * @return the {@code name} header value or {@code defaultValue} if the header
+ * is not found in the message
+ */
+ public String getHeader(String name, String defaultValue) {
+ return getHeader(this.headers, name, defaultValue);
+ }
+
+ private static String getHeader(Map<? extends String, String> headers, String headerName,
+ String defaultValue) {
+ String value = headers.get(headerName);
+ if (value == null) {
+ value = defaultValue;
+ }
+ return value;
+ }
+
+ private static String maskNull(String string) {
+ return string == null
+ ? ""
+ : string;
+ }
+
+ private static void writeNonEmptyHeader(Writer writer, String headerName, String headerValue)
+ throws IOException {
+ if (headerValue != null) {
+ writeHeader(writer, headerName, headerValue);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ try {
+ this.sendThrough(sw);
+ } catch (IOException e) {
+ // never occurs
+ }
+ return sw.toString();
+ }
+
+ private static void writeHeader(Writer writer, String name, String value) throws IOException {
+ writer.append(name).append(':').append(value).append(HEADER_TERMINATOR);
+ }
+}