blob: 9f13e6a76cfc004cbfd230253c3b73a829c25b29 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 IBM Corporation and others 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: IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.jsdt.debug.internal.crossfire.transport;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;
import java.util.Map;
import org.eclipse.wst.jsdt.debug.internal.crossfire.Constants;
import org.eclipse.wst.jsdt.debug.internal.crossfire.CrossFirePlugin;
import org.eclipse.wst.jsdt.debug.internal.crossfire.Tracing;
/**
* A specialized {@link Connection} that communicates using {@link Socket}s
*
* @since 1.0
*/
public class SocketConnection implements Connection {
private Writer writer;
private Reader reader;
private Socket socket;
/**
* Constructor
*
* @param socket the underlying {@link Socket}, <code>null</code> is not accepted
*
* @throws IOException
*/
public SocketConnection(Socket socket) throws IOException {
if(socket == null) {
throw new IllegalArgumentException("You cannot create a new SocketConnection on a null Socket"); //$NON-NLS-1$
}
this.socket = socket;
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), Constants.UTF_8));
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), Constants.UTF_8));
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.internal.core.jsdi.connect.Connection#isOpen()
*/
public boolean isOpen() {
return !socket.isClosed();
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.internal.core.jsdi.connect.Connection#close()
*/
public void close() throws IOException {
socket.close();
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.internal.core.jsdi.connect.Connection#writePacket(org.eclipse.wst.jsdt.debug.internal.core.jsdi.connect.Packet)
*/
public void writePacket(Packet packet) throws IOException {
String serialized = JSON.serialize(packet);
if(Packet.TRACE) {
Tracing.writeString("WRITE PACKET: "+serialized); //$NON-NLS-1$
}
writer.write(serialized);
writer.flush();
}
/**
* Writes the standard handshake packet to connect
*
* @param packet
* @throws IOException
*/
public void writeHandShake() throws IOException {
if(Packet.TRACE) {
Tracing.writeString("WRITE HANDSHAKE: "+HandShake.getHandshake()); //$NON-NLS-1$
}
writer.write(HandShake.getHandshake());
writer.flush();
waitForReadyRead();
}
/**
* Method to wait for the socket reader to become ready after the handshake
*
* @throws IOException
*/
void waitForReadyRead() throws IOException {
long timeout = System.currentTimeMillis() + 1000;
boolean timedout = System.currentTimeMillis() > timeout;
while(!reader.ready() && !timedout) {
try {
Thread.sleep(100);
timedout = System.currentTimeMillis() > timeout;
} catch (InterruptedException e) {
CrossFirePlugin.log(e);
}
}
if(timedout) {
if(Packet.TRACE) {
Tracing.writeString("HANDSHAKE: Timed out waiting for ready read from handshake"); //$NON-NLS-1$
}
//throw new IOException("Waiting for the socket to become available after receiving handshake timed out."); //$NON-NLS-1$
}
}
/**
* Reads the {@link HandShake} packet from the the stream
*
* @return the {@link HandShake}, never <code>null</code>
* @throws IOException
*/
public Packet readHandShake() throws IOException {
StringBuffer buffer = new StringBuffer();
//read the header first
int c = 0;
boolean r = false;
while((c = reader.read()) > -1) {
buffer.append((char)c);
if(r) {
if(c == '\n') {
break;
}
}
r = c == '\r';
}
if(buffer.toString().equals(HandShake.getHandshake())) {
HandShake ack = new HandShake();
if(Packet.TRACE) {
Tracing.writeString("ACK HANDSHAKE: "+ack); //$NON-NLS-1$
}
return ack;
}
while(reader.ready()) {
c = reader.read();
if(Packet.TRACE) {
Tracing.writeString("Reading extra post-amble from socket: "+(char)c); //$NON-NLS-1$
}
}
throw new IOException("Did not get correct CrossFire handshake"); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.internal.core.jsdi.connect.Connection#readPacket()
*/
public Packet readPacket() throws IOException {
StringBuffer buffer = new StringBuffer();
int c = -1;
boolean len = false;
while((c = reader.read()) > -1) {
if(c == '\r') {
break;
}
if(len) {
buffer.append((char)c);
continue;
}
len = c == ':';
}
int length = 0;
try {
length = Integer.parseInt(buffer.toString());
} catch (NumberFormatException e) {
throw new IOException("Failed to parse content length: " + buffer.toString()); //$NON-NLS-1$
}
c = reader.read();
if(c != '\n') {
throw new IOException("Failed to parse content length: " + buffer.toString() + "next char was not '\n'" + (char)c); //$NON-NLS-1$ //$NON-NLS-2$
}
char[] message = new char[length];
int n = 0;
int off = 0;
while (n < length) {
int count = reader.read(message, off + n, length - n);
if (count < 0) {
throw new EOFException();
}
n += count;
}
if(Packet.TRACE) {
Tracing.writeString("READ PACKET: [length - "+length+"]"+new String(message)); //$NON-NLS-1$ //$NON-NLS-2$
}
Map json = (Map) JSON.read(new String(message));
String type = Packet.getType(json);
if (Event.EVENT.equals(type)) {
return new Event(json);
}
if (Request.REQUEST.equals(type)) {
return new Request(json);
}
if (Response.RESPONSE.equals(type)) {
return new Response(json);
}
throw new IOException("Unknown packet type: " + type); //$NON-NLS-1$
}
}