/*
* eXist Open Source Native XML Database
* Copyright (C) 2009 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: DebuggerImpl.java 12465 2010-08-20 09:07:49Z shabanovd $
*/
package org.exist.debugger;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.exist.debuggee.dbgp.packets.*;
import org.exist.debugger.Debugger;
import org.exist.debugger.DebuggingSource;
import org.exist.debugger.dbgp.CodecFactory;
import org.exist.debugger.dbgp.ProtocolHandler;
import org.exist.debugger.dbgp.ResponseImpl;
import org.exist.debugger.model.Breakpoint;
import org.exist.debugger.model.BreakpointImpl;
import org.exist.debugger.model.Location;
import org.exist.debugger.model.LocationImpl;
import org.exist.debugger.model.Variable;
import org.exist.debugger.model.VariableImpl;
import org.exist.util.Base64Decoder;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
*
*/
public class DebuggerImpl implements Debugger, org.exist.debuggee.Status {
protected final static Logger LOG = Logger.getLogger(DebuggerImpl.class);
private static DebuggerImpl instance = null;
public static Debugger getDebugger() throws IOException {
if (instance == null)
instance = new DebuggerImpl();
return instance;
}
public static void shutdownDebugger() {
if (instance == null)
return;
instance.acceptor.unbind();
instance = null;
}
private NioSocketAcceptor acceptor;
private int eventPort = 9000;
private IoSession session;
// uri -> source
private Map<String, DebuggingSource> sources = new HashMap<String, DebuggingSource>();
int currentTransactionId = 1;
// private String lastStatus = FIRST_RUN;
protected int responseCode = 0;
private DebuggerImpl() throws IOException {
acceptor = new NioSocketAcceptor();
acceptor.setCloseOnDeactivation(true);
acceptor.getFilterChain().addLast("protocol", new ProtocolCodecFilter(new CodecFactory()));
acceptor.setHandler(new ProtocolHandler(this));
acceptor.bind(new InetSocketAddress(eventPort));
}
private int getNextTransaction() {
return currentTransactionId++;
}
private void setSession(IoSession session) {
this.session = session;
}
public DebuggingSource init(String url) throws IOException,
ExceptionTimeout {
LOG.info("Debugger is listening at port " + eventPort);
if (this.session != null) new IOException("Another debugging session is active.");
responseCode = 0;
responses = new HashMap<String, Response>();
sources = new HashMap<String, DebuggingSource>();
currentTransactionId = 1;
Thread session = new Thread(new HttpSession(this, url));
session.start();
// 30s timeout
ResponseImpl response = (ResponseImpl) getResponse("init", 30 * 1000);
setSession(response.getSession());
// TODO: fileuri as constant???
return getSource(response.getAttribute("fileuri"));
}
/*
* (non-Javadoc)
*
* @see org.exist.debugger.Debugger#source(java.lang.String)
*/
public DebuggingSource getSource(String fileURI) throws IOException {
if (fileURI == null)
return null;
if (sources.containsKey(fileURI))
return sources.get(fileURI);
Source command = new Source(session, " -i " + getNextTransaction());
command.setFileURI(fileURI);
command.toDebuggee();
Response response = getResponse(command.getTransactionId());
if ("1".equals(response.getAttribute("success"))) {
DebuggingSourceImpl source = new DebuggingSourceImpl(this, fileURI);
Base64Decoder dec = new Base64Decoder();
dec.translate(response.getText());
byte[] c = dec.getByteArray();
String s = new String(c);
source.setText(s);
sources.put(fileURI, source);
return source;
}
return null;
}
public List<Variable> getVariables() throws IOException {
return getVariables(null);
}
public List<Variable> getLocalVariables() throws IOException {
return getVariables(ContextNames.LOCAL);
}
public List<Variable> getGlobalVariables() throws IOException {
return getVariables(ContextNames.GLOBAL);
}
private List<Variable> getVariables(String contextID) throws IOException {
ContextGet command = new ContextGet(session,
" -i " + getNextTransaction());
if (contextID != null)
command.setContextID(contextID);
command.toDebuggee();
Response response = getResponse(command.getTransactionId());
//XXX: handle errors
List<Variable> variables = new ArrayList<Variable>();
NodeList children = response.getElemetsByName("property");
for (int i = 0; i < children.getLength(); i++) {
variables.add(new VariableImpl(children.item(i)));
}
return variables;
}
public List<Location> getStackFrames() throws IOException {
StackGet command = new StackGet(session, " -i " + getNextTransaction());
command.toDebuggee();
Response response = getResponse(command.getTransactionId());
//XXX: handle errors
List<Location> variables = new ArrayList<Location>();
NodeList children = response.getElemetsByName("stack");
for (int i = 0; i < children.getLength(); i++) {
variables.add(new LocationImpl(children.item(i)));
}
return variables;
}
public void sessionClosed() {
if (session != null && !session.isClosing())
session.close(true);
session = null;
}
// weak map???
private Map<String, Response> responses = new HashMap<String, Response>();
public synchronized void addResponse(Response response) {
if (currentCommand != null
&& currentCommand.getTransactionId().equals(
response.getTransactionID()))
currentCommand.putResponse(response);
//it should be commands map, this implementation is dangerous
//rethink!!!
responses.put(response.getTransactionID(), response);
notifyAll();
}
public Response getResponse(String transactionID) throws IOException {
try {
return getResponse(transactionID, 0);
} catch (ExceptionTimeout e) {
return null;
}
}
public synchronized Response getResponse(String transactionID, int timeout)
throws ExceptionTimeout, IOException {
long sTime = System.currentTimeMillis();
while (!responses.containsKey(transactionID)) {
try {
if (responseCode != 0) {
if (responses.containsKey(transactionID))
break;
throw new IOException("Got responce code "+responseCode+" on debugging request");
}
else if (timeout == 0)
wait(10); //slow down next check
else
wait(timeout);
if (timeout != 0
&& (System.currentTimeMillis() - sTime) > timeout)
throw new ExceptionTimeout();
} catch (InterruptedException e) {
}
}
if (responses.containsKey(transactionID)) {
Response response = responses.get(transactionID);
responses.remove(transactionID);
return response;
}
//UNDERSTAND: throw error???
return null;
}
private AbstractCommandContinuation currentCommand = null;
private void waitFor(String transactionId, String status) throws IOException {
Response response = null;
while (true) {
response = getResponse(transactionId);
if (response != null) {
if (response.getElemetsByName("error").getLength() != 0)
break;
String getStatus = response.getAttribute("status");
if (getStatus.equals(status)) {
break;
} else if (getStatus.equals(STOPPED)) {
break;
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public void run(ResponseListener listener) {
Run command = new Run(session, " -i " + getNextTransaction());
command.addResponseListener(listener);
command.toDebuggee();
}
public void run() throws IOException {
Run command = new Run(session, " -i " + getNextTransaction());
command.toDebuggee();
waitFor(command.getTransactionId(), BREAK);
}
public void stepInto(ResponseListener listener) {
StepInto command = new StepInto(session, " -i " + getNextTransaction());
command.addResponseListener(listener);
command.toDebuggee();
}
public void stepInto() throws IOException {
StepInto command = new StepInto(session, " -i " + getNextTransaction());
command.toDebuggee();
waitFor(command.getTransactionId(), BREAK);
}
public void stepOut(ResponseListener listener) {
StepOut command = new StepOut(session, " -i " + getNextTransaction());
command.addResponseListener(listener);
command.toDebuggee();
}
public void stepOut() throws IOException {
StepOut command = new StepOut(session, " -i " + getNextTransaction());
command.toDebuggee();
waitFor(command.getTransactionId(), BREAK);
}
public void stepOver(ResponseListener listener) {
StepOver command = new StepOver(session, " -i " + getNextTransaction());
command.addResponseListener(listener);
command.toDebuggee();
}
public void stepOver() throws IOException {
StepOver command = new StepOver(session, " -i " + getNextTransaction());
command.toDebuggee();
waitFor(command.getTransactionId(), BREAK);
}
public void stop(ResponseListener listener) {
Stop command = new Stop(session, " -i " + getNextTransaction());
command.addResponseListener(listener);
command.toDebuggee();
}
public void stop() throws IOException {
Stop command = new Stop(session, " -i " + getNextTransaction());
command.toDebuggee();
try {
waitFor(command.getTransactionId(), BREAK);
} catch (IOException e) {
//closed?
}
}
public boolean setBreakpoint(Breakpoint breakpoint) throws IOException {
BreakpointSet command = new BreakpointSet(session, " -i " + getNextTransaction());
command.setBreakpoint((BreakpointImpl) breakpoint);
command.toDebuggee();
Response response = getResponse(command.getTransactionId());
//XXX: handle error
breakpoint.setState("enabled".equals(response.getAttribute("state")));
breakpoint.setId(Integer.parseInt(response.getAttribute("id")));
return true;
}
public boolean updateBreakpoint(Breakpoint breakpoint) throws IOException {
BreakpointUpdate command = new BreakpointUpdate(session, " -i " + getNextTransaction());
command.setBreakpoint(breakpoint);
command.toDebuggee();
// Response response =
getResponse(command.getTransactionId());
//XXX: handle error
return true;
}
public boolean removeBreakpoint(BreakpointImpl breakpoint) throws IOException {
BreakpointRemove command = new BreakpointRemove(session, " -i " + getNextTransaction());
command.setBreakpoint(breakpoint);
command.toDebuggee();
// Response response =
getResponse(command.getTransactionId());
//XXX: handle error
return true;
}
protected synchronized void terminate(String url, int code) {
responseCode = code;
notifyAll();
System.out.println("setResponseCode responseCode = "+responseCode);
}
private String getText(NodeList nodes) {
if ((nodes.getLength() == 1) && (nodes.item(0).getNodeType() == Node.TEXT_NODE))
return ((Text)nodes.item(0)).getData();
return "";
}
@Override
public String evaluate(String script) throws IOException {
Eval command = new Eval(session, " -i " + getNextTransaction());
command.setScript(script);
command.toDebuggee();
Response response = getResponse(command.getTransactionId());
if ("1".equals(response.getAttribute("success"))) {
Node property = response.getElemetsByName("property").item(0);
Base64Decoder dec = new Base64Decoder();
dec.translate(getText(property.getChildNodes()));
byte[] c = dec.getByteArray();
return new String(c);
}
return null;
}
public boolean isSuspended() {
if (currentCommand == null) return false;
if (currentCommand.getStatus() == null) return false;
return (currentCommand.getStatus().equals(BREAK));
}
public boolean isTerminated() {
if (currentCommand == null) return false;
if (currentCommand.getStatus() == null) return false;
return (currentCommand.equals(STOPPED));
}
}