/*
* 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: DebuggeeJointImpl.java 12466 2010-08-20 09:38:51Z shabanovd $
*/
package org.exist.debuggee;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import org.apache.log4j.Logger;
import org.exist.debuggee.dbgp.packets.AbstractCommandContinuation;
import org.exist.debuggee.dbgp.packets.Command;
import org.exist.debuggee.dbgp.packets.Init;
import org.exist.debugger.model.Breakpoint;
import org.exist.dom.QName;
import org.exist.storage.serializers.Serializer;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SerializerPool;
import org.exist.xquery.CompiledXQuery;
import org.exist.xquery.Expression;
import org.exist.xquery.TerminatedException;
import org.exist.xquery.Variable;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQuery;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.*;
/**
* @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
*
*/
public class DebuggeeJointImpl implements DebuggeeJoint, Status {
private final static Logger LOG = Logger.getLogger(DebuggeeJoint.class);
private Map<String, String> features = new HashMap<String, String>(DebuggeeImpl.SET_GET_FEATURES);
private Expression firstExpression = null;
private List<Expression> stack = new ArrayList<Expression>();
private int stackDepth = 0;
private CommandContinuation command = null;
private Stack<CommandContinuation> commands = new Stack<CommandContinuation>();
private int breakpointNo = 0;
//<fileName, Map<line, breakpoint>>
private Map<String, Map<Integer, Breakpoint>> filesBreakpoints =
new HashMap<String, Map<Integer, Breakpoint>>();
private Set<Breakpoint> activeBreakpoints = new CopyOnWriteArraySet<Breakpoint>();
//id, breakpoint
private Map<Integer, Breakpoint> breakpoints = new HashMap<Integer, Breakpoint>();
private CompiledXQuery compiledXQuery;
private boolean inProlog = false;
public DebuggeeJointImpl() {
}
protected void setCompiledScript(CompiledXQuery compiledXQuery) {
this.compiledXQuery = compiledXQuery;
stack.add(null);
}
public void stackEnter(Expression expr) {
if (LOG.isDebugEnabled())
LOG.debug("" + expr.getLine() + " expr = "+ expr.toString());
stack.add(expr);
stackDepth++;
}
public void stackLeave(Expression expr) {
if (LOG.isDebugEnabled())
LOG.debug("" + expr.getLine() + " expr = "+ expr.toString());
stack.remove(stack.size()-1);
stackDepth--;
if (command.is(CommandContinuation.STEP_OUT)
&& command.getCallStackDepth() > stackDepth
&& command.isStatus(RUNNING)) {
command.setStatus(BREAK);
}
}
public void prologEnter(Expression expr) {
inProlog = true;
}
/* (non-Javadoc)
* @see org.exist.debuggee.DebuggeeJoint#expressionStart(org.exist.xquery.Expression)
*/
public void expressionStart(Expression expr) throws TerminatedException {
if (LOG.isDebugEnabled())
LOG.debug("" + expr.getLine() + " expr = "+ expr.toString());
if (compiledXQuery == null)
return;
if (firstExpression == null)
firstExpression = expr;
stack.set(stackDepth, expr);
String fileName = Command.getFileuri(expr.getSource());
Integer lineNo = expr.getLine();
//check breakpoint
for (Breakpoint breakpoint : activeBreakpoints) {
if (breakpoint.getFilename().equals(fileName)
&& breakpoint.getType().equals(Breakpoint.TYPE_LINE) ) {
if (breakpoint.getLineno() != lineNo) {
activeBreakpoints.remove(breakpoint);
}
}
}
Map<Integer, Breakpoint> fileBreakpoints = null;
while (true) {
//didn't receive any command, wait for any
if (command == null ||
//the status is break, wait for changes
command.isStatus(BREAK)) {
waitCommand();
continue;
}
//wait for connection
if (command.is(CommandContinuation.INIT) && command.isStatus(STARTING)) {
Init init = (Init)command;
init.getSession().setAttribute("joint", this);
init.setFileURI(compiledXQuery.getSource());
//break on first line
command.setStatus(BREAK);
}
//disconnected
if (compiledXQuery == null)
return;
//stop command, terminate
if (command.is(CommandContinuation.STOP) && !command.isStatus(STOPPED)) {
command.setStatus(STOPPED);
sessionClosed(true);
throw new TerminatedException(expr.getLine(), expr.getColumn(), "Debuggee STOP command.");
}
//step-into is done
if (command.is(CommandContinuation.STEP_INTO) && command.isStatus(RUNNING)) {
command.setStatus(BREAK);
//step-over should stop on same call's stack depth
} else if (command.is(CommandContinuation.STEP_OVER)
&& command.getCallStackDepth() == stackDepth
&& command.isStatus(RUNNING)) {
command.setStatus(BREAK);
}
//checking breakpoints
synchronized (breakpoints) {
if (filesBreakpoints.containsKey(fileName)) {
fileBreakpoints = filesBreakpoints.get(fileName);
if (fileBreakpoints.containsKey(lineNo)) {
Breakpoint breakpoint = fileBreakpoints.get(lineNo);
if (!activeBreakpoints.contains(breakpoint)
&& breakpoint.getState()
&& breakpoint.getType().equals(Breakpoint.TYPE_LINE)) {
activeBreakpoints.add(breakpoint);
command.setStatus(BREAK);
//waitCommand();
//break;
}
}
}
}
//RUN command with status RUNNING can be break only on breakpoints
if (command.getType() >= CommandContinuation.RUN && command.isStatus(RUNNING)) {
break;
//any continuation command with status RUNNING
} else if (command.getType() >= CommandContinuation.RUN && command.isStatus(STARTING)) {
command.setStatus(RUNNING);
break;
}
waitCommand();
}
}
/* (non-Javadoc)
* @see org.exist.debuggee.DebuggeeJoint#expressionEnd(org.exist.xquery.Expression)
*/
public void expressionEnd(Expression expr) {
if (LOG.isDebugEnabled())
LOG.debug("expr = "+expr.toString());
if (firstExpression == expr) {
firstExpression = null;
if (!inProlog) {
command.setStatus(BREAK);
command.setStatus(STOPPED);
sessionClosed(true);
reset();
}
inProlog = false;
}
}
private synchronized void waitCommand() {
if (commands.size() != 0 && command.isStatus(BREAK)) {
command = commands.pop();
((AbstractCommandContinuation)command).setCallStackDepth(stackDepth);
return;
}
notifyAll();
try {
wait();
} catch (InterruptedException e) {
//UNDERSTAND: what to do?
}
}
/* (non-Javadoc)
* @see org.exist.debuggee.DebuggeeJoint#getContext()
*/
public XQueryContext getContext() {
return compiledXQuery.getContext();
}
public void reset() {
firstExpression = null;
stack = new ArrayList<Expression>();
stackDepth = 0;
command = null;
breakpointNo = 0;
filesBreakpoints = new HashMap<String, Map<Integer, Breakpoint>>();
breakpoints = new HashMap<Integer, Breakpoint>();
}
public synchronized void continuation(CommandContinuation command) {
if (firstExpression == null && !command.is(CommandContinuation.INIT))
command.setStatus(STOPPED);
else
command.setStatus(STARTING);
if (this.command == null || this.command.isStatus(STOPPED)) {
((AbstractCommandContinuation)command).setCallStackDepth(stackDepth);
this.command = command;
} else {
commands.add(command);
}
notifyAll();
}
public boolean featureSet(String name, String value) {
if (features.containsKey(name)) {
features.put(name, value);
return true;
}
return false;
}
public String featureGet(String name) {
if (DebuggeeImpl.GET_FEATURES.containsKey(name))
return DebuggeeImpl.GET_FEATURES.get(name);
return features.get(name);
}
public synchronized List<Expression> stackGet() {
//wait, script didn't started
while (stack.size() == 0)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
return stack;
}
public Map<QName, Variable> getVariables() {
if (stack.size() == 0)
return new HashMap<QName, Variable>();
Expression expr = stack.get(0);
return expr.getContext().getVariables();
}
public Map<QName, Variable> getLocalVariables() {
if (stack.size() == 0)
return new HashMap<QName, Variable>();
Expression expr = stack.get(0);
return expr.getContext().getLocalVariables();
}
public Map<QName, Variable> getGlobalVariables() {
if (stack.size() == 0)
return new HashMap<QName, Variable>();
Expression expr = stack.get(0);
return expr.getContext().getGlobalVariables();
}
public Variable getVariable(String name) {
if (stack.size() == 0)
return null;
Expression expr = stack.get(0);
try {
return expr.getContext().resolveVariable(name);
} catch (XPathException e) {
return null;
}
}
public int setBreakpoint(Breakpoint breakpoint) {
synchronized (breakpoints) {
breakpointNo++;
breakpoint.setId(breakpointNo);
breakpoints.put(breakpointNo, breakpoint);
Map<Integer, Breakpoint> fileBreakpoints;
String fileName = breakpoint.getFilename();
if (filesBreakpoints.containsKey(fileName))
fileBreakpoints = filesBreakpoints.get(fileName);
else {
fileBreakpoints = new HashMap<Integer, Breakpoint>();
filesBreakpoints.put(fileName, fileBreakpoints);
}
fileBreakpoints.put(breakpoint.getLineno(), breakpoint);
}
return 1;//TODO: create constant
}
public Breakpoint getBreakpoint(int breakpointID) {
return breakpoints.get(breakpointID);
}
public Breakpoint removeBreakpoint(int breakpointID) {
Breakpoint breakpoint = breakpoints.get(breakpointID);
if (breakpoint == null)
return breakpoint;
String fileName = breakpoint.getFilename();
Integer lineNo = breakpoint.getLineno();
Map<Integer, Breakpoint> fileBreakpoints = null;
synchronized (breakpoints) {
if (filesBreakpoints.containsKey(fileName)) {
fileBreakpoints = filesBreakpoints.get(fileName);
if (fileBreakpoints.containsKey(lineNo)) {
fileBreakpoints.remove(lineNo);
if (fileBreakpoints.isEmpty()) {
filesBreakpoints.remove(fileName);
}
}
}
breakpoints.remove(breakpointID);
activeBreakpoints.remove(breakpoint);
}
return breakpoint;
}
public Map<Integer, Breakpoint> getBreakpoints() {
return breakpoints;
}
public synchronized void sessionClosed(boolean disconnect) {
//disconnected already
if (compiledXQuery == null)
return;
//disconnect debuggee & compiled source
XQueryContext context = compiledXQuery.getContext();
context.setDebuggeeJoint(null);
compiledXQuery = null;
if (command != null && disconnect)
command.disconnect();
reset();
notifyAll();
}
public CommandContinuation getCurrentCommand() {
return command;
}
@Override
public String evalution(String script) throws Exception {
XQueryContext context = compiledXQuery.getContext().copyContext();
context.setDebuggeeJoint(null);
context.undeclareGlobalVariable(Debuggee.SESSION);
XQuery service = context.getBroker().getXQueryService();
CompiledXQuery compiled = service.compile(context, script);
Sequence resultSequence = service.execute(compiled, null);
SAXSerializer sax = null;
Serializer serializer = context.getBroker().getSerializer();
serializer.reset();
try {
sax = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
Properties outputProps = new Properties();
StringWriter writer = new StringWriter();
sax.setOutput(writer, outputProps);
serializer.setSAXHandlers(sax, sax);
for (SequenceIterator i = resultSequence.iterate(); i.hasNext();) {
Item next = i.nextItem();
if (Type.subTypeOf(next.getType(), Type.NODE))
serializer.toSAX((NodeValue) next);
else
writer.write(next.getStringValue());
}
return writer.toString();
} finally {
if (sax != null) {
SerializerPool.getInstance().returnObject(sax);
}
}
}
}