
package jde.debugger;

import jde.debugger.spec.*;

import com.sun.jdi.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;

import java.util.*;

/**
 * SpecCommands.java
 * <p>
 * Handles commands related to breakpointing, watchpointing and catching
 * exceptions. Jump to
 * '{@link #doTraceExceptions trace_exceptions}',
 * '{@link #doWatch watch}', '{@link #doBreak break}',
 * '{@link #doClear clear}',
 * <p>
 * Created: Fri Jul 30 15:44:59 1999
 * 
 * @author Amit Kumar
 * @since 0.1
 */

public class SpecCommands extends ApplicationCommands {

    /* Handling exceptions */


    /**
     * 'trace_exceptions' command.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * trace_exceptions classPattern <u>type</u>
     *      [{@link Etc#getThreadFromArgs(List) thread-restriction}]
     *      [{@link Etc#getSuspendPolicyFromArgs(List) suspend-policy}]
     *      [{@link Etc#getClassFiltersFromArgs(List) class-filters}]
     *      [{@link Etc#getClassExFiltersFromArgs(List) class-exclusion-filters}]
     * </pre>
     *
     * <b>Returns:</b>
     * <pre>
     * (jde-dbo-command-result cmd_id specID)
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> <u>type</u> can be "caught", "uncaught", or "both"
     * <li> specID is a 'long', and can be used in the 'clear'
     * command
     * </ul>
     *
     * <p>
     * @see EventHandler#exceptionEvent(ExceptionEvent)
     */
    public void doTraceExceptions(Integer cmd_id, List args)
	throws JDEException {

	if (args.size() < 2) 
	    throw new JDEException("Insufficient arguments");

	String classPattern = args.remove(0).toString();
	String type = args.remove(0).toString().toLowerCase();

	boolean caught = false;
	boolean uncaught = false;
	if (type.equals("both")) {
	    caught = true;
	    uncaught = true;
	} else if (type.equals("caught")) {
	    caught = true;
	} else if (type.equals("uncaught")) {
	    uncaught = true;
	} else {
	    throw new JDEException("'"+type+"' not understood");
	}
	    
	EventRequestSpec er = eventRequests.createExceptionIntercept(classPattern, caught, uncaught);
	er.setThread(Etc.getThreadFromArgs(args));
	er.setSuspendPolicy(Etc.getSuspendPolicyFromArgs(args));
	er.setClassFilters(Etc.getClassFiltersFromArgs(args));
	er.setClassExFilters(Etc.getClassExFiltersFromArgs(args));
	eventRequests.install(er);
	
	app.signalCommandResult(cmd_id, er.getID());
    }
    
	 
    /**
     * 'watch' command.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * watch classPattern fieldName <u>type</u>
     *      [{@link Etc#getThreadFromArgs(List) thread-restriction}]
     *      [{@link Etc#getExprFromArgs(List) expression-restriction}]
     *      [{@link Etc#getObjectIDFromArgs(List) object-id-restriction}]
     *      [{@link Etc#getSuspendPolicyFromArgs(List) suspend-policy}]
     *      [{@link Etc#getClassFiltersFromArgs(List) class-filters}]
     *      [{@link Etc#getClassExFiltersFromArgs(List) class-exclusion-filters}]
     * </pre>
     *
     * <b>Returns:</b>
     * <pre>
     * (jde-dbo-command-result cmd_id specID)
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> <u>type</u> can be "for_access" or "for_modification"
     * <li> 'classPattern' can be a string pattern of the type *.Test
     * <li> objectID is used when, for example, when you already know the
     *  object id of the object, the access/modification of which's field
     *  you're interested in. 
     * <li> specID is a 'long', that can be used in 'clear' commands.
     * </ul>
     *
     * <p>
     * @see EventHandler#watchpointEvent(WatchpointEvent)
     */
    public void doWatch(Integer cmd_id, List args)
	throws JDEException {
	
	if (args.size() < 3)
	    throw new JDEException("Insufficient arguments");

	String classPattern = args.remove(0).toString();
	String methodName = args.remove(0).toString();
	String typeString = args.remove(0).toString().toLowerCase();

	WatchpointSpec er = null;
	if (typeString.equals("for_access")) {
	    if (!app.getVM().canWatchFieldAccess()) 
		throw new JDEException("This VM implementation cannot watch field accesses");
	    er = eventRequests.createAccessWatchpoint(classPattern, methodName);
	} else if (typeString.equals("for_modification")) {
	    if (!app.getVM().canWatchFieldModification()) 
		throw new JDEException("This VM implementation cannot watch field modifications");
	    er = eventRequests.createModificationWatchpoint(classPattern, methodName);
	} else {
	    throw new JDEException("'"+typeString+"' not understood: use either 'for_access' or 'for_modification'");
	}
	er.setThread(Etc.getThreadFromArgs(args));
	er.setExpression(Etc.getExprFromArgs(args));
	er.setObjectID(Etc.getObjectIDFromArgs(args));
	er.setSuspendPolicy(Etc.getSuspendPolicyFromArgs(args));
	er.setClassFilters(Etc.getClassFiltersFromArgs(args));
	er.setClassExFilters(Etc.getClassExFiltersFromArgs(args));
	eventRequests.install(er);

	app.signalCommandResult(cmd_id, er.getID());
    }	


    /**
     * 'break' command.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * break {@link #doBreakInMethod in_method} class method [(args)] 
     *      [{@link Etc#getThreadFromArgs(List) thread-restriction}]
     *      [{@link Etc#getExprFromArgs(List) expression-restriction}]
     *      [{@link Etc#getSuspendPolicyFromArgs(List) suspend-policy}]
     *     
     * break {@link #doBreakOnLine on_line}   class line
     *      [{@link Etc#getThreadFromArgs(List) thread-restriction}]
     *      [{@link Etc#getExprFromArgs(List) expression-restriction}]
     *      [{@link Etc#getSuspendPolicyFromArgs(List) suspend-policy}]
     *
     * break {@link #doBreakAbsolute absolute}  file line
     *      [{@link Etc#getThreadFromArgs(List) thread-restriction}]
     *      [{@link Etc#getExprFromArgs(List) expression-restriction}]
     *      [{@link Etc#getSuspendPolicyFromArgs(List) suspend-policy}]
     * </pre>
     *
     * <b>Returns:</b>
     * <pre>
     * (jde-dbo-command-result cmd_id specID)
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> There are exactly three kinds of 'break' commands. One 
     *	of in_method, on_line, or absolute need to be used.
     * <li> 'class' can be a string pattern of the type *.Test
     * <li> specID is a 'long', that can be used in 'clear' commands.
     * </ul>
     *
     * <p>
     * @see EventHandler#breakpointEvent(BreakpointEvent)
     */
    public void doBreak(Integer cmd_id, List args) throws JDEException {
	try {
	    // whatever function is called, should do a signalCommandResult
	    // during the execution.
	    String type = args.remove(0).toString().toLowerCase();
	    if (type.equals("in_method")) {
		doBreakInMethod(cmd_id, args);
	    } else if (type.equals("on_line")) {
		doBreakOnLine(cmd_id, args);
	    } else if (type.equals("absolute")) {
		doBreakAbsolute(cmd_id, args);
	    } else
		throw new JDEException("Syntax error: expecting one of 'in_method', 'on_line', or 'absolute'; '"+type+"' is not supported");
	} catch (UnsupportedOperationException ex) {
	    throw new JDEException("Unspecified Error occured");
	} catch (IndexOutOfBoundsException ex) {
	    throw new JDEException("Syntax error: argument missing");
	}
    }

    
    /**
     * 'clear' command. Clears a breakpoint, watchpoint or an exception
     * intercept
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * clear specID
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> specIDs are returned in the 'break'/'watch'/'trace_exceptions'
     * commands.
     * </ul>
     */
    public void doClear(Integer cmd_id, List args)
	throws JDEException {
	if (args.size() < 1)
	    throw new JDEException("Insufficient arguments");
	Long specID = Etc.safeGetLong(args.remove(0), "spec ID");
	eventRequests.removeSpec(specID);
	app.signalCommandResult(cmd_id);
    }


    /**
     * A break in a particular method.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * break in_method class method [(arg1,arg2,...)] 
     *      [{@link Etc#getThreadFromArgs(List) thread-restriction}]
     *      [{@link Etc#getExprFromArgs(List) expression-restriction}]
     *      [{@link Etc#getSuspendPolicyFromArgs(List) suspend-policy}]
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> There should be <b>no spaces</b> before or after the ','; when 
     *      the arguments are supplied.
     * <li> A void method should be indicated by <code>()</code>
     * <li> A unique method doesn't need to supply the arguments. The
     * <b>entire</b> argument list should be absent in this case.
     * </ul>
     */
    public void doBreakInMethod(Integer cmd_id, List args)
	throws JDEException {

	if (args.size() < 2)
	    throw new JDEException("Insufficient arguments");
	
	String classPattern = args.remove(0).toString();
	String method = args.remove(0).toString();

	// the argument list
	List argumentList = null;

	// see if more arguments are present
	if (args.size() > 0) {

	    String arg = args.remove(0).toString();

	    // see if any arglist was provided at all
	    if (arg.startsWith("(")) {
		// apparently it was. double check.
		if (!arg.endsWith(")")) {
		    throw new JDEException("The argument list seems to be corrupt");
		}
		// trim the parens
		arg = arg.substring(1, arg.length() - 1);
		argumentList = new ArrayList();
		StringTokenizer t = new StringTokenizer(arg, ",");
		while (t.hasMoreTokens()) {
		    argumentList.add(t.nextToken());
		}
	    }
	}
	EventRequestSpec er = eventRequests.createMethodBreakpoint(classPattern, method, argumentList);
	er.setThread(Etc.getThreadFromArgs(args));
	er.setExpression(Etc.getExprFromArgs(args));
	er.setSuspendPolicy(Etc.getSuspendPolicyFromArgs(args));
	eventRequests.install(er);

	app.signalCommandResult(cmd_id, er.getID());
    }

    /** A break on a particular line of a class */
    public void doBreakOnLine(Integer cmd_id, List args)
	throws JDEException {

	if (args.size() < 2)
	    throw new JDEException("Insufficient arguments");
	
	String classPattern = args.remove(0).toString();
	int line = Etc.safeGetint(args.remove(0), "line number");

	EventRequestSpec er =
	    eventRequests.createClassLineBreakpoint(classPattern, line);
	er.setThread(Etc.getThreadFromArgs(args));
	er.setExpression(Etc.getExprFromArgs(args));
	er.setSuspendPolicy(Etc.getSuspendPolicyFromArgs(args));
	eventRequests.install(er);
	    
	app.signalCommandResult(cmd_id, er.getID());
    }

    /** A break on a line of a given source file */
    public void doBreakAbsolute(Integer cmd_id, List args)
	throws JDEException {

	if (args.size() < 2)
	    throw new JDEException("Insufficient arguments");
	
	String file = args.remove(0).toString();
	int line = Etc.safeGetint(args.remove(0), "line number");

	EventRequestSpec er =
	    eventRequests.createSourceLineBreakpoint(file, line);
	er.setThread(Etc.getThreadFromArgs(args));
	er.setExpression(Etc.getExprFromArgs(args));
	er.setSuspendPolicy(Etc.getSuspendPolicyFromArgs(args));
	eventRequests.install(er);
	    
	app.signalCommandResult(cmd_id, er.getID());
    }
    
    final EventRequestSpecList eventRequests;

    public SpecCommands(Application a, EventRequestSpecList e,
			      ObjectStore s) {
	super(a, s);
	this.eventRequests = e;
    }

} // SpecCommands
