
package jde.debugger;

import jde.debugger.spec.*;

import java.util.*;

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

/**
 * EventHandler.java
 * <p>
 * Each debugee VM has an event handler thread on the jdebug side
 * associated with it that receive all the events from the debugee
 * vm. In turn, the event handler thread passes the events on to the
 * jde, indicating if the vm/current thread was suspended.
 * <p>
 * Created: Tue Jul  6 14:08:44 1999
 *
 * @author Amit Kumar
 * @since 0.1
 */

public class EventHandler implements Runnable, Protocol {

    /** Are we connected to the VM? */
    boolean connected = true;
    
    /** The application for which we're the event handler */
    final Application app;

    /** The ID of the application */
    final Integer my_id;

    /**
     * My own thread. Used when we want to suspend ourselves or some such
     * weird stuff :-)
     */
    final Thread thread;

    /** Keeping track of if the thread is over yet or not */
    boolean completed = false;

    /**
     * Used by the event handlers to indicate if the app should be
     * resumed after the event occured.
     */
    boolean resumeApp;

    public EventHandler(Application app) {
        /* the q gets all the events from the vm */
	this.app = app;
	this.my_id = app.getId();
	this.thread = new Thread(this, "Event Handler for App #"+my_id);
	this.thread.start();
    }

    public void shutdown() {
	connected = false;
	thread.interrupt();
	while (!completed) {
	    try {wait();} catch (InterruptedException ex) {}
	}
    }

    /**
     * The thread reads an eventset at a time from the application queue,
     * and processes it. Essentially, one by one, each event of the eventset
     * is sent to the corresponding "handler", and if at least one of them
     * returns a non-null value, we pass on the eventset (and the values
     * returned by the handlers) to jde. Otherwise, we just resume the vm
     * (regardless of the suspend policy) and wait for the next eventset.
     * <p>
     * This is the syntax of the event set:
     * <pre>
     * (JDE_BUG_EVENTSET <u>suspend-state</u> <u>thread</u> [<u>event-string</u>]+)
     * </pre>
     * 
     * <ul>
     * <li> <u>suspend-state</u> is one of "all", "none" and "thread"
     * <li> <u>thread</u> is either nil, or the thread corresponding to the
     *      event set.
     * <li> <u>event-string</u> is the reply from a handler method. See the
     *      individual handler methods.
     * <li> The suspend policy is as follows. Some of the commands (see
     *      the {@link Protocol protocol}) allow for specifying the suspend
     *      policy for the corresponding event. For example, we might specify
     *      that the vm resume automatically after a  method entry event is
     *      reported.
     *      <p>
     *      Firstly, when multiple events are sent (in an event-set), the
     *      suspend policy of the entire event set is the one that would
     *      suspend the most threads. So, if a breakpoint-hit is sent with
     *      the method-entry event, and the breakpoint-hit wants to suspend
     *      the vm, while method-entry wants to resume, overall, the event
     *      set suspend policy will be to suspend
     *      <p>
     *      Further, some of the events might occur that haven't been
     *      explicitly requested, and hence we haven't set their suspend
     *      policy anyway. In these cases, and even otherwise, another
     *      decision is made at the time of handling of events regarding the
     *      suspension/resumption of the vm.
     *      <p>
     *      So, when an event set is received, each event is handled (via
     *      it's handler method). Each event ANDs to a variable resumeApp
     *      what it thinks should be done to the app: for example, the
     *      vmDeathEvent handler wants to resume, so it does
     *      <code>resumeApp &= true;</code>. In the same way, when a
     *      breakpoint is hit (and matches the constraints), it does a
     *      <code>resumeApp &= false:</code>.
     *      <p>
     *      After all events have been handled, we check the value of
     *      resumeApp. If it's true, we resume the vm. If not, we check
     *      the suspend-policy of the event set, and take appropriate action.
     *      <p>
     *      This two-tier suspend-policy handling might be simplified to
     *      some extent once all commands support setting of a suspend
     *      policy, with that and that alone being used to decide on what
     *      to do with the vm.
     *      <p>
     *      A problem with that approach is that since we do a second pass
     *      on the events when we get the eventset to determine if the
     *      events should be sent to the user (eg. a breakpoint-hit is still
     *      not interesting if it doesn't occur on the "right" thread), the
     *      latter approach might not work well at all.
     * </ul>
     */ 
    public void run() {
	EventQueue queue = app.getEventQueue();
        /* only need to run while we're connected */
        while (connected) {
            try {
                EventSet eventSet = queue.remove();

		resumeApp = true;

		List events = new ArrayList();
                EventIterator it = eventSet.eventIterator();

		//		System.out.println("Suspend policy of eventset: "+((eventSet.suspendPolicy() == EventRequest.SUSPEND_NONE)?"none":"not none"));

		while (it.hasNext()) {
		    //		    System.out.println("An event");
		    LispForm sendLispForm = handleEvent(it.nextEvent());
		    // if the handler thinks the user doesn't <i>deserve</i>
		    // the event, it'll return null. another microsoftism
		    // in the code... :-(
		    if (sendLispForm != null) {
			events.add(sendLispForm);
		    }
                }

		// if no event wants to inform itself to the user, what
		// are we doing anyway? 
		if (events.size() == 0) {
		    eventSet.resume();
		    continue;
		}
		
		String suspendStateString;
                if (resumeApp) {
		    // this resume does a vm resume or a thread resume
		    // depending on how it was suspended
		    suspendStateString = "none";
                    eventSet.resume();
                } else {
		    switch(eventSet.suspendPolicy()) {
		    case EventRequest.SUSPEND_ALL: {
			suspendStateString = "all";
			break;
		    }
		    case EventRequest.SUSPEND_EVENT_THREAD: {
			suspendStateString = "thread";
			break;
		    }
		    case EventRequest.SUSPEND_NONE: {
			suspendStateString = "none";
			eventSet.resume();
			break;
		    }
		    default: {
			suspendStateString = "invalid";
			break;
		    }
		    }
		}

		String eventSetString = "\""+suspendStateString+"\"";

		ThreadReference eventThread = getCurrentThread(eventSet);
		if (eventThread == null) {
		    eventSetString += " nil";
		} else {
		    eventSetString +=  BR +
			Rep.getThreadRep(eventThread, app.getStore());
		}
		
		Iterator iter = events.iterator();
		while (iter.hasNext()) {
		    eventSetString +=  BR +iter.next();
		}
		
		// finally, we send the events to The Man. (or woman...)
		app.signal(EVENTSET, new LispForm(eventSetString));
		
	    } catch (InterruptedException ex) {
		// Debug.printIf(ex);
		// used by shutdown
	    } catch (VMDisconnectedException ex) {
		Debug.printIf(ex);
		handleDisconnectedException();
	    }
	}
	synchronized (this) {
	    completed = true;
	    notifyAll();
	}

    }


    /**
     * Handles the events that happened
     *
     * @param event One of the events in the event set.
     * @return A LispForm that indicates what should be sent to jde. This
     * should be of the form "(list jde-dbo-EVENT-event [args])". Each
     * of the functions called herein should return a LispForm of the above
     * form.
     */
    private LispForm handleEvent(Event event) {
	if (event instanceof BreakpointEvent) {
	    return breakpointEvent((BreakpointEvent)event);
	} else if (event instanceof StepEvent) {
	    return stepEvent((StepEvent)event);
	} else if (event instanceof WatchpointEvent) {
	    return watchpointEvent((WatchpointEvent)event);
	} else if (event instanceof ExceptionEvent) {
	    return exceptionEvent((ExceptionEvent)event);
	}
	else if (event instanceof ThreadStartEvent) {
	    return threadStartEvent((ThreadStartEvent)event);
	} else if (event instanceof ThreadDeathEvent) {
	    return threadDeathEvent((ThreadDeathEvent)event);
	}
	else if (event instanceof MethodEntryEvent) {
	    return methodEntryEvent((MethodEntryEvent)event);
	} else if (event instanceof MethodExitEvent) {
	    return methodExitEvent((MethodExitEvent)event);
	}
	else if (event instanceof ClassPrepareEvent) {
	    return classPrepareEvent((ClassPrepareEvent)event);
	}
	else if (event instanceof VMStartEvent) {
	    return vmStartEvent((VMStartEvent)event);
	} else if (event instanceof VMDeathEvent) {
	    return vmDeathEvent((VMDeathEvent)event);
	} else if (event instanceof VMDisconnectEvent) {
	    return vmDisconnectEvent((VMDisconnectEvent)event);
	} else {
	    return otherEvent(event);
	}
    }

    /**
     * Duh... we don't recognize this event... or, we choose not to do
     * anything about it
     */
    private LispForm otherEvent(Event event) {
	resumeApp &= true;
	return new LispForm("(list '"+EVENT_OTHER+")");
    }

    /**
     * What we should do if we get disconnected while we're doing something
     * else.
     */
    private void handleDisconnectedException() {
	// disconnected while handling some other event. flush queue
	// and deal with disconnectEvent and deathEvents
	EventQueue queue = app.getEventQueue();
	while (connected) {
	    try {
		EventSet eventSet = queue.remove();
		EventIterator iter = eventSet.eventIterator();
		while (iter.hasNext()) {
		    Event evt = (Event)iter.next();
		    if (evt instanceof VMDeathEvent) {
			vmDeathEvent(evt);
		    } else if (evt instanceof VMDisconnectEvent) {
			vmDisconnectEvent(evt);
		    }
		}
	    } catch (InterruptedException ex) {
		Debug.printIf(ex);
		// ignore
	    }
	}
    }

    /**
     * Get the current thread of this event set. It's not necessary for
     * an event set to have a thread associated with it: in those cases
     * just return null.
     *
     * @param eventSet The event set that occured
     * @return <code>null</code> if there is no thread associated, otherwise
     * the ThreadReference of the Thread
     */
    private ThreadReference getCurrentThread(EventSet eventSet) {
	ThreadReference thread;
	if (eventSet.size() > 0) {
            /*
             * If any event in the set has a thread associated with it, 
             * they all will, so just grab the first one. 
             */
            Event event = (Event)eventSet.iterator().next(); // Is there a better way?
            thread = getEventThread(event);
        } else {
            thread = null;
        }
	return thread;
    }

    /**
     * Black magic to divine the ThreadReference of the
     * event. Question: can we use "Black magic" and "divine" in the same
     * sentence?
     *
     * @param event An event from the event set: any event will do
     * @return The ThreadReference of the thread
     */
    private ThreadReference getEventThread(Event event) {
        if (event instanceof ClassPrepareEvent) {
            return ((ClassPrepareEvent)event).thread();
        } else if (event instanceof LocatableEvent) {
            return ((LocatableEvent)event).thread();
        } else if (event instanceof ThreadStartEvent) {
            return ((ThreadStartEvent)event).thread();
        } else if (event instanceof ThreadDeathEvent) {
            return ((ThreadDeathEvent)event).thread();
        } else if (event instanceof VMStartEvent) {
            return ((VMStartEvent)event).thread();
        } else {
            return null;
        }
    }


    /**
     * For events with corresponding specs (eg. watchpoints), checks to see
     * if the event thread matches the thread constraint in the spec.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * nil                              # If no such constraint
     *                                  #   was found in the spec
     * "Error mesg"                     # Constraint found, but error
     *                                  #   during evaluation
     * (list "on_thread_id" threadID)   # Constraint found, satisfied
     * (list "on_thread_name" "name")
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> The above are strings: so the '"'s above are actually quoted
     *      in the string returned.
     * <li> Note that if the Constraint is <b>found but does not satisfy</b>
     *      'null' is returned: this is tested in the handler routines.
     * </ul>
     */
    private String threadMatch(Event event) {
	Object thread =
	    event.request().getProperty(EventRequestSpec.threadKey);
	if (thread == null) {
	    return "nil";
	} else if (thread instanceof Long) {
	    ThreadReference t = getEventThread(event);
	    if (t.uniqueID() == ((Long)thread).longValue()) {
		return "(list \"on_thread_id\" "+t.uniqueID()+")";
	    } else {
		return null;
	    }
	} else if (thread instanceof String) {
	    ThreadReference tRef =
		ThreadCommands.getThread(app.getVM(), thread.toString());
	    ThreadReference t = getEventThread(event);
	    
	    if (t.equals(tRef)) {
		return "(list \"on_thread_name\""
		    +" \""+thread.toString()+"\")";
	    } else {
		return null;
	    }
	} else {
	    return "\"Error matching thread\"";
	}
    }

    /**
     * For events with corresponding specs (eg. watchpoints), evaluates the
     * expression stored in the spec, if any, to check if the event is
     * interesting to the user or not. The constraint is thus the expression.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * nil                              # If no such constraint
     *                                  #   was found in the spec
     * "Error mesg"                     # Constraint found, but error
     *                                  #   during evaluation
     * (list "expression")              # Constraint found, satisfied
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> The above are strings: so the '"'s above are actually quoted
     *      in the string returned.
     * <li> Note that if the Constraint is <b>found but does not satisfy</b>
     *      'null' is returned: this is tested in the handler routines.
     * </ul>
     */
    private String expressionSuccess(Event event) {
	Object exprObject =
	    event.request().getProperty(EventRequestSpec.expressionKey);
	if (exprObject != null) {
	    String expr = exprObject.toString();
	    try {
		StackFrame frame = getEventThread(event).frame(0);
		Value val = Etc.evaluate(expr, frame);
		if (!val.type().name().equals("boolean")) {
		    return "\"Expression evaluates to non-boolean\"";
		} else {
		    BooleanValue boolValue = (BooleanValue)val;
		    if (boolValue.value() == true) {
			return "(list \""+expr+"\")";
		    } else {
			return null;
		    }
		}
	    } catch (Exception ex) {
		Debug.printIf(ex);
		return "\"Expression didn't evaluate correctly\"";
	    }
	}
	return "nil";
    }


    /**
     * For watchpoints, when the object-id specified during the command
     * should match the object id of the event.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * nil                              # if no such constraint was in spec
     * "Error mesg"                     # Constraint found, error in eval.
     * (list <u>object-id</u>)                 # Constraint found, satisfied.
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> The above are strings: so the '"'s above are actually quoted
     *      in the string returned.
     * <li> Note that if the Constraint is <b>found but does not satisfy</b>
     *      'null' is returned: this is tested in the handler routines.
     * <li> <u>object-id</u> is the object id that was specified in the
     *      command
     * </ul>
     */
    private String objectIDMatches(WatchpointEvent event) {
	String objectIDString;
	Object idObject =
	    event.request().getProperty(WatchpointSpec.objectIDKey);
	if (idObject == null) {
	    return "nil";
	} else if (idObject instanceof Long) {
	    Long id = (Long)idObject;
	    if (event.object() == null) {
		return "(list "+id+")";
	    } else if (event.object().uniqueID() == id.longValue()) {
		return "(list "+id+")";
	    } else {
		return null;
	    }
	} else {
	    return "\"Object ID was not a Long\"";
	}
    } 

    
    /**
     * A Breakpoint was hit. We check (based on information in the event
     * request; put in when the spec was resolved; that's sent along with
     * the event) if the user wants to know about this event. If so, we
     * return a lispform, else return null.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_BREAKPOINT_HIT breakpoint-hit-function} specID {@link Rep#getLocationRep(Location) location} {@link #threadMatch thread-string} {@link #expressionSuccess expression-string})
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> If one of the constraints returns null instead of a string, it
     *      means the constraint isn't satisfied. We return null, indicating
     *      that the user is not interested in the event of this event :-)
     * </ul>
     */
    private LispForm breakpointEvent(BreakpointEvent event) {

	Long specID = ((EventRequestSpec)event.request().getProperty(EventRequestSpec.specPropertyKey)).getID();

	// check if the current thread is ok.
	String threadString = threadMatch(event);
	if (threadString == null) {
	    resumeApp &= true;
	    return null;
	}
		
	// check if the expression matches
	String exprString = expressionSuccess(event);
	if (exprString == null) {
	    resumeApp &= true;
	    return null;
	}

	resumeApp &= false;
	return new LispForm("(list '"+EVENT_BREAKPOINT_HIT
			    +" "+specID
			    + BR +Rep.getLocationRep(event.location())
			    +" "+threadString
			    +" "+exprString+")");

    }


    /**
     * A step event occured.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_STEP_COMPLETED step-function} {@link Rep#getLocationRep(Location) location-string})
     * </pre>
     */
    private LispForm stepEvent(StepEvent event) {
	resumeApp &= false;
	return new LispForm("(list '"
			    +EVENT_STEP_COMPLETED
			    +" "+Rep.getLocationRep(event.location())
			    +")");
    }

    /**
     * A Watchpoint occured. We check (based on information in the event
     * request; put in when the spec was resolved; that's sent along with
     * the event) if the user wants to know about this event. If so, we
     * return a lispform, else return null.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_WATCHPOINT_HIT watchpoint-hit-function} specID {@link Rep#getObjectRep(ObjectReference,ObjectStore) object-on-which-hit} {@link Rep#getFieldValueRep(Field, Value, ObjectStore) field-and-value} {@link Rep#getLocationRep(Location) location}
     *       {@link #objectIDMatches object-id-string} {@link #threadMatch thread-string} {@link #expressionSuccess expression-string})
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> If one of the constraints returns null instead of a string, it
     *      means the constraint isn't satisfied. We return null, indicating
     *      that the user is not interested in the event of this event :-)
     * </ul>
     */
    private LispForm watchpointEvent(WatchpointEvent event) {

	Long specID = ((EventRequestSpec)event.request().getProperty(EventRequestSpec.specPropertyKey)).getID();

	//	System.out.println("Suspend policy of WatchpointEvent: "+((event.request().suspendPolicy() == EventRequest.SUSPEND_NONE)?"none":"not none"));

	// check if the current thread is ok.
	String threadString = threadMatch(event);
	if (threadString == null) {
	    resumeApp &= true;
	    return null;
	}
		
	// check if the expression matches
	String exprString = expressionSuccess(event);
	if (exprString == null) {
	    resumeApp &= true;
	    return null;
	}

	// check if the object id (if specified) matches the event object
	String objectIDString = objectIDMatches(event);
	if (objectIDString == null) {
	    resumeApp &= true;
	    return null;
	}

	String fieldValueString =
	    Rep.getFieldValueRep(event.field(), event.valueCurrent(),
				 app.getStore()).toString();

	String objectString = Rep.getObjectRep(event.object(),
					       app.getStore()).toString();

	resumeApp &= false;
	return new LispForm("(list '"+EVENT_WATCHPOINT_HIT
			    +" "+specID
			    + BR +objectString
			    + BR +fieldValueString
			    + BR +Rep.getLocationRep(event.location())
			    + BR +objectIDString
			    +" "+threadString
			    +" "+exprString+")");

    }


    /**
     * An exception event occured. Depending on the command, it could occur
     * for both caught and uncaught exceptions.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_EXCEPTION exception-function} specID {@link Rep#getObjectRep(ObjectReference,ObjectStore) exception} {@link #threadMatch thread-string})
     * </pre>
     *
     * <b>Comments:</b>
     * <ul>
     * <li> If one of the constraints returns null instead of a string, it
     *      means the constraint isn't satisfied. We return null, indicating
     *      that the user is not interested in the event of this event :-)
     * </ul>
     */
    private LispForm exceptionEvent(ExceptionEvent event) {

	if (event.request() == null) {
	    resumeApp &= true;
	    return null;
	}
	
	Long specID = ((EventRequestSpec)event.request().getProperty(EventRequestSpec.specPropertyKey)).getID();
	    
	ExceptionRequest request =
	    (ExceptionRequest)event.request();

	// check if the current thread is ok.
	String threadString = threadMatch(event);
	if (threadString == null) {
	    resumeApp &= true;
	    return null;
	}
		
	resumeApp &= false;
	return new LispForm("(list '"
			    +EVENT_EXCEPTION
			    +" "+specID
			    + BR +Rep.getObjectRep(event.exception(),
						   app.getStore())
			    + BR +threadString+")");
    }


    /**
     * A method was entered.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_METHOD_ENTRY method-entry-function} {@link Rep#getMethodRep method})
     * </pre>
     */
    private LispForm methodEntryEvent(MethodEntryEvent event) {
	resumeApp &= false;
	return new LispForm("(list '"
			    + EVENT_METHOD_ENTRY
			    + BR +Rep.getMethodRep(event.method())
			    +")");
    }
			    
    /**
     * A method was exit.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_METHOD_EXIT method-exit-function} {@link Rep#getMethodRep method})
     * </pre>
     */
    private LispForm methodExitEvent(MethodExitEvent event) {
	resumeApp &= false;
	return new LispForm("(list '"
			    + EVENT_METHOD_EXIT
			    + BR +Rep.getMethodRep(event.method())
			    +")");
    }
			    

    /**
     * A thread started
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_THREAD_START thread-start-function} {@link Rep#getThreadRep thread})
     * </pre>
     */
    private LispForm threadStartEvent(ThreadStartEvent event) {
	resumeApp &= false;
	return new LispForm("(list '"
			    + EVENT_THREAD_START
			    + BR +Rep.getThreadRep(event.thread(),
						   app.getStore())
			    +")");
    }
			    
    /**
     * A thread died.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_THREAD_DEATH thread-death-function} {@link Rep#getThreadRep thread})
     * </pre>
     */
    private LispForm threadDeathEvent(ThreadDeathEvent event) {
	resumeApp &= false;
	return new LispForm("(list '"
			    + EVENT_THREAD_DEATH
			    + BR +Rep.getThreadRep(event.thread(),
						   app.getStore())
			    +")");
    }
			    

    /**
     * A class was prepared. The user might not have even requested for
     * this event: we set it up by default in {@link Application#initialize
     * Application} because we need it for resolution of specs.
     * <p>
     * If a user also requests for this event, a particular property is
     * likely not set, and that is how we know that we're supposed to
     * handle the event for the user.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_CLASS_PREPARE class-prepare-function} reference-type)
     * </pre>
     */
    private LispForm classPrepareEvent(ClassPrepareEvent event) {
	app.resolve(event.referenceType());
	// now find out if this event was also requested by the user.
	// it will be, if the "default" property does NOT exists in the
	// corresponding request. 
	EventRequest request = event.request();
	if (request.getProperty("default") == null) {
	    resumeApp &= false;
	    return new LispForm("(list '"+EVENT_CLASS_PREPARE
				+" \""+event.referenceType().name()+"\")");
	} else {
	    resumeApp &= true;
	    return null;
	}
    }

    /**
     * A class was unloaded.
     * <p>
     *
     * <b>Syntax:</b>
     * <pre>
     * (list {@link Protocol#EVENT_CLASS_UNLOAD class-unload-function} reference-type)
     * </pre>
     */
    private LispForm classUnloadEvent(ClassUnloadEvent event) {
	resumeApp &= false;
	return new LispForm("(list '"+EVENT_CLASS_UNLOAD
			    +" \""+event.className()+"\")");
    }
    
    private LispForm vmStartEvent(Event event) {
	resumeApp &= false;
	return new LispForm("(list '"+EVENT_VM_START+")");
    }
    
    private LispForm vmDeathEvent(Event event) {
	resumeApp &= true;
	return new LispForm("(list '"+EVENT_VM_DEATH+")");
    }

    private LispForm vmDisconnectEvent(Event event) {
	connected = false;
	resumeApp &= true;
	app.shutdown();
	return new LispForm("(list '"+EVENT_VM_DISCONNECT+")");
    }
			
} // EventHandler
