/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain Eclipse Public Licensed code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.debug.internal.core.model;
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.net.SocketException;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import com.aptana.ide.core.IdeLog;
import com.aptana.ide.core.StringUtils;
import com.aptana.ide.debug.core.JSDebugPlugin;
/**
* @author Max Stepanov
*
*/
public class DebugConnection
{
public interface IHandler {
void handleMessage(String message);
void handleShutdown();
}
/**
* COMMAND_TIMEOUT
*/
protected static final int COMMAND_TIMEOUT = 20000;
/**
* SOCKET_TIMEOUT
*/
public static final int SOCKET_TIMEOUT = 30000;
private static final String ARGS_SPLIT = "\\*"; //$NON-NLS-1$
private Socket socket;
private Reader reader;
private Writer writer;
private boolean connected = false;
private boolean terminated = false;
private Map<String, Object> locks = new Hashtable<String, Object>();/* synchronized get/put required */
private volatile long lastReqId = System.currentTimeMillis();
private IHandler handler;
/**
* @throws DebugException
* @throws DebugException
* @throws IOException
*
*/
public static DebugConnection createConnection(Socket socket) throws DebugException
{
try
{
return new DebugConnection(socket,
new InputStreamReader(socket.getInputStream()),
new OutputStreamWriter(socket.getOutputStream()));
}
catch (IOException e)
{
throwDebugException(e);
return null;
}
}
protected DebugConnection(Socket socket, Reader reader, Writer writer) {
this.socket = socket;
this.reader = reader;
this.writer = writer;
}
public void start(IHandler handler) {
this.handler = handler;
connected = true;
new Thread("Aptana: JS Debugger") { //$NON-NLS-1$
public void run()
{
while ((socket != null && !socket.isClosed()) || reader != null)
{
try
{
String message = readMessage();
if (message == null)
{
break;
}
handleMessage(message);
}
catch (SocketException e)
{
break;
}
catch (Exception e)
{
IdeLog.logError(JSDebugPlugin.getDefault(), StringUtils.EMPTY, e);
}
}
handleConnectionTerminated();
}
}.start();
}
public void stop() {
if (!connected)
{
return;
}
connected = false;
synchronized (locks)
{
Object[] list = locks.values().toArray();
locks.clear();
for (int i = 0; i < list.length; ++i)
{
Object lock = list[i];
synchronized (lock)
{
lock.notify();
}
}
}
}
public void dispose() throws IOException {
if (reader != null) {
reader.close();
writer.close();
reader = null;
writer = null;
}
if (socket != null)
{
socket.close();
socket = null;
}
}
private void handleMessage(String message) {
if (message.endsWith("*")) { //$NON-NLS-1$
message += "* "; //$NON-NLS-1$
}
handler.handleMessage(message);
if (!connected)
{
return;
}
/* check if action comes to waiting commands */
String[] args = message.split(ARGS_SPLIT);
String action = args[0];
Object lock = locks.get(action);
if (lock != null)
{
locks.put(action, args);
synchronized (lock)
{
lock.notify();
}
return;
}
}
/**
* handleConnectionTerminated
*/
private void handleConnectionTerminated()
{
if (terminated)
{
return;
}
terminated = true;
handler.handleShutdown();
}
public boolean isConnected() {
return connected;
}
public boolean isTerminated() {
return terminated;
}
/**
* sendCommand
*
* @param command
* @throws DebugException
*/
protected void sendCommand(String command) throws DebugException
{
sendCommand(StringUtils.EMPTY, command);
}
/**
* sendCommand
*
* @param reqid
* @param command
* @throws DebugException
*/
protected void sendCommand(String reqid, String command) throws DebugException
{
try
{
writer.write(StringUtils.format("{0}*{1}*{2}", //$NON-NLS-1$
new String[] { Integer.toString(command.length() + reqid.length() + 1), reqid, command }));
writer.flush();
}
catch (IOException e)
{
throwDebugException(e);
}
}
/**
* sendCommandAndWait
*
* @param command
* @return String[]
* @throws DebugException
*/
protected String[] sendCommandAndWait(String command) throws DebugException
{
long reqid = ++lastReqId;
return sendCommandAndWait(command, Long.toString(reqid));
}
/**
* sendCommandAndWait
*
* @param command
* @param reqid
* @return String[]
* @throws DebugException
*/
protected String[] sendCommandAndWait(String command, String reqid) throws DebugException
{
if (!connected)
{
return null;
}
Object lock = new Object();
synchronized (lock)
{
try
{
locks.put(reqid, lock);
sendCommand(reqid, command);
lock.wait(COMMAND_TIMEOUT);
}
catch (InterruptedException e)
{
throwDebugException(e);
}
}
lock = locks.remove(reqid);
if (lock instanceof String[])
{
return (String[]) lock;
}
return null;
}
/**
* readMessage
*
* @return String
* @throws IOException
*/
protected String readMessage() throws IOException
{
StringBuffer sb = new StringBuffer();
int messageSize = 0;
int i;
char ch;
while ((i = reader.read()) != -1)
{
ch = (char) i;
if (ch == '*' && sb.length() > 0)
{
try
{
messageSize = Integer.parseInt(sb.toString());
break;
}
catch (NumberFormatException e)
{
}
sb.setLength(0);
}
else if (ch >= '0' && ch <= '9')
{
sb.append(ch);
}
else if (sb.length() > 0)
{
sb.setLength(0);
}
}
if (i == -1)
{
return null;
}
char[] buffer = new char[1024];
sb.setLength(0); // clear the buffer
while (messageSize > sb.length())
{
int n = reader.read(buffer, 0, Math.min(messageSize - sb.length(), buffer.length));
if (n == -1)
{
return null;
}
sb.append(buffer, 0, n);
}
return sb.toString();
}
/**
* throwDebugException
*
* @param exception
* @throws DebugException
*/
protected static void throwDebugException(Exception exception) throws DebugException
{
throw new DebugException(new Status(IStatus.ERROR, JSDebugPlugin.ID, DebugException.TARGET_REQUEST_FAILED,
StringUtils.EMPTY, exception));
}
}