package edu.mit.ai.psg.traveler.jevaHooks;

import edu.mit.ai.psg.jeva.*;
import edu.mit.ai.psg.traveler.*;
import java.lang.reflect.*;
import java.util.*;
// main (example of use):
import java.io.*;
import java.awt.*;
import java.awt.event.*;
// import edu.mit.ai.psg.jevaUI.*;
// import edu.mit.ai.psg.strings.Stringify;

/** A Jeva EvalHook which calls Trace methods to record method call and
    instance creation expressions, and records activities which use
    interpreted synchronized methods and blocks in the biography for
    the synchronizing object.

    @see EvalMethods
    @see Trace

    @author CarlManning, caroma@ai.mit.edu<br>
    Copyright (c) 1999 Massachusetts Institute of Technology
**/
public class TraceCallsEvalHook extends JevaVisitorBase {
  public TraceCallsEvalHook() {}
  /** default -- do nothing **/
  public Object visit(IJevaNode node, Object data) { 
    return null; }

  /** calls {@link Trace#invoking}, {@link Trace#returning}, and
      {@link Trace#throwing} to trace calls.  Records of interpreted
      synchronized methods are added to target biography after the synchronized
      target lock is acquired, by using
      {@link Trace#synchronizing} at the beginning of the body; see
      {@link #visit(JNSBlock, Object)}. **/
  public Object visit(JNEMethodInvocationExpression node, Object data) {
    if (data instanceof EvalMethods.PreApplyData) {
      Object[] values = ((EvalMethods.PreApplyData)data).getValues();
      IMethod iMethodSignature = node.getMethodSignature();
      Method methodSignature =
	(iMethodSignature instanceof WrappedMethod ? 
	 ((WrappedMethod)iMethodSignature).getWrappedMethod() :
	 ((InterpretedMethod)iMethodSignature).getProxyMethod());
      if (Modifier.isStatic(methodSignature.getModifiers())) {
	Trace.invokingStatic(methodSignature, values);
      } else {
	Object target = values[0];
	Object[] argValues = new Object[values.length - 1];
	System.arraycopy(values, 1, argValues, 0, argValues.length);
	Trace.invoking(target, methodSignature, argValues);
	// special methods 
	if (target instanceof Thread &&
	    "start".equals(methodSignature.getName()) &&
	    0 == argValues.length) {
	  Trace.starting((Thread)target);
	}
      }
    } else if (data instanceof EvalMethods.EvalHookPostData &&
	       Trace.getCurrentRecord() instanceof CallRecord) { 
      if (data instanceof EvalMethods.ResultData) {
	CallRecord record = (CallRecord) Trace.getCurrentRecord();
	if (record.getMember() instanceof Method &&
	    void.class.equals( ((Method)record.getMember()).getReturnType() ))
	  Trace.returningVoid(record);
	else 
	  Trace.returning(record, ((EvalMethods.ResultData)data).getResult());
      } else if (data instanceof EvalMethods.AbruptData) {
	Trace.throwing((CallRecord) Trace.getCurrentRecord(),
		       ((EvalMethods.AbruptData)data).getThrown());
      }
    }
    return null;
  }

  /** calls {@link Trace#invoking}, {@link Trace#returning}, and
      {@link Trace#throwing} to trace new expressions.  **/
  public Object visit(JNEInstanceCreationExpression node, Object data) {
    if (data instanceof EvalMethods.PreApplyData) {
      Object[] values = ((EvalMethods.PreApplyData)data).getValues();
      IConstructor iConstructor = node.getConstructor();
      Constructor constructor =
	(iConstructor instanceof WrappedConstructor ? 
	 ((WrappedConstructor)iConstructor).getWrappedConstructor() :
	 ((InterpretedConstructor)iConstructor).getProxyConstructor());
      Trace.invokingStatic(constructor, values);
    } else if (data instanceof EvalMethods.EvalHookPostData &&
	       Trace.getCurrentRecord() instanceof CallRecord) { 
      if (data instanceof EvalMethods.ResultData) {
	CallRecord record = (CallRecord) Trace.getCurrentRecord();
	Trace.returning(record, ((EvalMethods.ResultData)data).getResult());
      } else if (data instanceof EvalMethods.AbruptData) {
	Trace.throwing((CallRecord) Trace.getCurrentRecord(),
		       ((EvalMethods.AbruptData)data).getThrown());
      }
    }
    return null;
  }

  /** makes a record for synchronized blocks.  Will be inserted into biography
      after synchronized target lock is acquired
      @see #visit(JNSBlock, Object) **/
  public Object visit(JNSSynchronizedStatement node, Object data) {
    if (data instanceof EvalMethods.PreApplyData) {
      Object[] values = ((EvalMethods.PreApplyData)data).getValues();
      Node parent = node.jjtGetParent();
      while (parent != null &&
	     ! (parent instanceof JNDMethodDeclaration) &&
	     ! (parent instanceof JNDConstructorDeclaration))
	parent = parent.jjtGetParent();
      Member member = 
	(parent instanceof JNDMethodDeclaration ? 
	 (Member) ((JNDMethodDeclaration)parent).getMethod().getProxyMethod() :
	 parent instanceof JNDConstructorDeclaration ?
	 (Member) ((JNDConstructorDeclaration)
		   parent).getConstructor().getProxyConstructor() : null );
      ActivityRecord record =
	new SynchronizedRecordDefault(values[0], member);
      Trace.entering(record);
    } else if (data instanceof EvalMethods.EvalHookPostData) {
      Trace.exiting(Trace.getCurrentRecord());
    }
    return null;
  }

  /** Check if parent is synchronized block or synchronized method. If so,
      and current trace record.isSynchronized, call <code>
      Trace.synchronizing(record, record.getSynchronizedTarget())</code> 
      (Trace.synchronizing must be called while synchronized lock is held,
      so it is called before evaluating the block that implements the body
      of the method or synchronized statement.  Calling it when receiving
      preEvalData or preApplyData is too early, and when receiving
      resultData is too late.) **/
  public Object visit(JNSBlock node, Object data) {
    boolean parentIsSynchronized = false;
    if (node.jjtGetParent() instanceof JNDMethodDeclaration) {
      JNDMethodDeclaration methodDecl =
	(JNDMethodDeclaration) node.jjtGetParent();
      if (data instanceof EvalMethods.PreEvalData) {
	InterpretedMethod method = methodDecl.getMethod();
	if (Modifier.isSynchronized(method.getModifiers()))
	  parentIsSynchronized = true;
      }
    } else if (node.jjtGetParent() instanceof JNSSynchronizedStatement) {
      parentIsSynchronized = true;
    }
    if (parentIsSynchronized) {
      ActivityRecord record = Trace.getCurrentRecord();
      if (record.isSynchronized())
	Trace.synchronizing(record); // gets target from record
    }
    return null;
  }

  /** Given a class name for a class in classpath, 
      runs its public static void main(...) method with a TraceCallsEvalHook.
      Jeva will run compiled .class file if it exists, so delete it first.
      @see EvalMethods
  **/
  public static void main(String[] arguments) throws FileNotFoundException {
    if (arguments.length == 0) {
      System.err.println
	("Use: java edu.mit.ai.psg.traveler.jeva.TraceCallsEvalHook "+
	 "ClassName [args]");
      System.exit(1);
    }
    String className = arguments[0];
    String[] programArguments = new String[arguments.length - 1];
    System.arraycopy(arguments,1, programArguments,0, programArguments.length);

    ActivityRecord record = Trace.entering(new StringRecord("TracingCalls"));

    EvalMethods.setEvalHook(new TraceCallsEvalHook());
    
    try { // reuse proxy classes
      CompileUnit.setTempRoot(new File("./proxies")); // optional
      Jeva.runClass(className, programArguments);
    }
    catch (ThrowException e) { } // continue on program exceptions
    catch (ParseException e) { System.err.println(e); return; }
    catch (ClassNotFoundException e) { System.err.println(e); return; }
    catch (Throwable e) { e.printStackTrace(); return; }

    TravelerOutliner.ensureInitialized();
    java.awt.Window frame =
      TravelerOutliner.makeFrame("Recording. (close this window to exit)",
				 TravelerOutliner.makeOutlineNode(record));
    frame.addWindowListener(new WindowAdapter() {
                              public void windowClosing(WindowEvent e) {
				System.exit(0); }});
    frame.show();
    /*
    try { 
      JevaGUI.repl
	(0,
	 ("Record of "+className+
	  ".main("+Stringify.printToString(programArguments)+")"),
	 "record: "+record,
	 null, null, 
	 Jeva.extendEnv
	 (ActivityRecord.class, "record", record,
	  Jeva.makeDefaultEnv()));
    } catch (Throwable t) {}
    */

  }
}