blob: 2b75a32bfe19b3a9b9f34c743dcdd65b1d818b83 [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.transport.packet;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.wst.jsdt.debug.transport.Connection;
import org.eclipse.wst.jsdt.debug.transport.exception.DisconnectedException;
import org.eclipse.wst.jsdt.debug.transport.exception.TimeoutException;
/**
* Default implementation of {@link PacketManager} for receiving packets from the
* {@link VirtualMachine}
*
* @since 1.0
*/
public final class PacketReceiveManager extends PacketManager {
/**
* Generic timeout value for not blocking. <br>
* Value is: <code>0</code>
*/
public static final int TIMEOUT_NOT_BLOCKING = 0;
/**
* Generic timeout value for infinite timeout. <br>
* Value is: <code>-1</code>
*/
public static final int TIMEOUT_INFINITE = -1;
/**
* List of Command packets received from Virtual Machine.
*/
private List commandPackets = new LinkedList();
/**
* List of Reply packets received from Virtual Machine.
*/
private List responsePackets = new LinkedList();
/**
* List of Packets that have timed out already. Maintained so that responses can be discarded if/when they are received.
*/
private List timedOutPackets = new ArrayList();
/**
* Constructor
*
* @param connection the underlying connection to communicate on, <code>null</code> is not accepted
*/
public PacketReceiveManager(Connection connection) {
super(connection);
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.internal.core.jsdi.connect.PacketManager#disconnect()
*/
public void disconnect() {
super.disconnect();
synchronized (this.commandPackets) {
this.commandPackets.notifyAll();
}
synchronized (this.responsePackets) {
this.responsePackets.notifyAll();
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
public void run() {
try {
while (!isDisconnected()) {
// Read a packet from the input stream.
readAvailablePacket();
}
}
// if the remote runtime is interrupted, drop the connection and clean up, don't wait for it to happen on its own
catch (InterruptedIOException e) {
disconnect(e);
} catch (IOException e) {
disconnect(e);
}
}
/**
* Returns a specified command {@link Packet} from the {@link VirtualMachine}.<br>
* <br>
* This method cannot return <code>null</code> an exception is thrown if the desired packet cannot
* be returned.
*
* @param type the type of the packet to get
* @param timeToWait the amount of time to wait for the {@link Packet} to arrive
*
* @return the specified command packet never <code>null</code>
* @throws TimeoutException if the request times out
* @throws DisconnectedException if the manager is disconnected
*/
public Packet getCommand(String type, long timeToWait) throws TimeoutException, DisconnectedException {
synchronized (this.commandPackets) {
long remainingTime = timeToWait;
// Wait until command is available.
//even if disconnected try to send the remaining events
while (!isDisconnected() || !this.commandPackets.isEmpty()) {
Packet packet = removeCommandPacket(type);
if (packet != null)
return packet;
if (remainingTime < 0 && timeToWait != TIMEOUT_INFINITE)
break;
long timeBeforeWait = System.currentTimeMillis();
try {
waitForPacketAvailable(remainingTime, this.commandPackets);
} catch (InterruptedException e) {
break;
}
long waitedTime = System.currentTimeMillis() - timeBeforeWait;
remainingTime -= waitedTime;
}
}
// Check for an IO Exception.
if (isDisconnected()) {
throw new DisconnectedException("Runtime disconnected", getDisconnectedException()); //$NON-NLS-1$
}
// Check for a timeout.
throw new TimeoutException("Timed out waiting for command packet: " + type); //$NON-NLS-1$
}
/**
* Returns a specified response packet from the {@link VirtualMachine}.<br>
* <br>
* This method cannot return <code>null</code> an exception is thrown if the desired response cannot
* be returned.
*
* @param requestSequence the sequence of the response to acquire
* @param timeToWait the amount of time to wait for the {@link Response}
*
* @return a specified response packet from the {@link VirtualMachine} never <code>null</code>
* @throws TimeoutException if the request times out
* @throws DisconnectedException if the manager is disconnected
*/
public Response getResponse(int requestSequence, long timeToWait) throws TimeoutException, DisconnectedException {
synchronized (this.responsePackets) {
long remainingTime = timeToWait;
// Wait until command is available.
while (!isDisconnected()) {
Response response = removeResponsePacket(requestSequence);
if (response != null)
return response;
if (remainingTime < 0 && timeToWait != TIMEOUT_INFINITE)
break;
long timeBeforeWait = System.currentTimeMillis();
try {
waitForPacketAvailable(remainingTime, this.responsePackets);
} catch (InterruptedException e) {
break;
}
long waitedTime = System.currentTimeMillis() - timeBeforeWait;
remainingTime -= waitedTime;
}
}
// Check for an IO Exception.
if (isDisconnected()) {
throw new DisconnectedException("Runtime disconnected", getDisconnectedException()); //$NON-NLS-1$
}
synchronized (this.responsePackets) {
this.timedOutPackets.add(new Integer(requestSequence));
}
throw new TimeoutException("Timed out waiting for packet: " + requestSequence); //$NON-NLS-1$
}
/**
* Wait for an available packet from the Virtual Machine.
*/
private void waitForPacketAvailable(long timeToWait, Object lock) throws InterruptedException {
if (timeToWait == 0)
return;
else if (timeToWait < 0)
lock.wait();
else
lock.wait(timeToWait);
}
/**
* Removes and returns the command packet for the given type or <code>null</code> if there is no command packet for the given type.
*
* @param type
* @return The command packet for the given type that has been removed
*/
private Packet removeCommandPacket(String type) {
ListIterator iter = this.commandPackets.listIterator();
while (iter.hasNext()) {
Packet packet = (Packet) iter.next();
if (type == null || packet.getType().equals(type)) {
iter.remove();
return packet;
}
}
return null;
}
/**
* Removes and returns the response packet with the given request sequence or <code>null</code> if there is no response packet with the given request sequence
*
* @param requestSequence
* @return the response packet with the given request sequence that has been removed
*/
private Response removeResponsePacket(int requestSequence) {
ListIterator iter = this.responsePackets.listIterator();
while (iter.hasNext()) {
Response response = (Response) iter.next();
if (requestSequence == response.getRequestSequence()) {
iter.remove();
return response;
}
}
return null;
}
/**
* Adds the given command packet to the listing of command packets. This method is a no-op if the given packet is <code>null</code>
*
* @param packet
*/
private void addCommandPacket(Packet packet) {
if (packet == null) {
return;
}
synchronized (this.commandPackets) {
this.commandPackets.add(packet);
this.commandPackets.notifyAll();
}
}
/**
* Adds the given response packet to the listing of response packets. This method is a no-op if the given packet is <code>null</code>
*
* @param response
*/
private void addResponsePacket(Response response) {
if (response == null) {
return;
}
synchronized (this.responsePackets) {
if (!this.timedOutPackets.isEmpty()) {
Integer requestSeq = new Integer(response.getRequestSequence());
if (this.timedOutPackets.remove(requestSeq))
return; // already timed out. No need to keep this one
}
this.responsePackets.add(response);
this.responsePackets.notifyAll();
}
}
/**
* Reads the next packet from the underlying connection and adds it to the correct packet listing.
*
* @throws IOException
*/
private void readAvailablePacket() throws IOException {
// Read a packet from the Input Stream.
if(getConnection().isOpen()) {
Packet packet = getConnection().readPacket();
if (packet instanceof Response) {
addResponsePacket((Response) packet);
} else {
addCommandPacket(packet);
}
}
}
}