/*******************************************************************************
*                                                                              *
*  File:        Rhino.java                              Revision:  1.0         *
*                                                                              *
*  Contents:    a very first and simple shell                                  *
*                                                                              *
*  Creation:    26.10.2001                     Last Modification:  27.10.2001  *
*                                                                              *
*  Platform:    IBM-compatible PC running Windows 98SE                         *
*                                                                              *
*  Environment: Java 1.3                                                       *
*                                                                              *
*  Author:      Andreas Rozek           Phone:  ++49 (711) 6770682             *
*               Kirschblütenweg 15      Fax:    -                              *
*             D-70569 Stuttgart         EMail:  Andreas.Rozek@T-Online.De      *
*               Germany                                                        *
*                                                                              *
*  URL:         http://www.Andreas-Rozek.de/                                   *
*                                                                              *
*  Copyright:   this software is published under the  "GNU Lesser General Pub- *
*               lic  License"  (see  "http://www.fsf.org/copyleft/lesser.html" *
*               for additional information)                                    *
*                                                                              *
*  Comments:    this implementation is based on the Rhino "shell" example      *
*                                                                              *
*               For console input, use the internal function "inputln" rather  *
*               than "input" - this is due to problems with console input in   *
*               Java...                                                        *
*                                                                              *
*******************************************************************************/

import java.io.*;                            // Java file handling and stream IO
import org.mozilla.javascript.*;                   // Rhino distribution classes
import sunda.rhino.*;                      // the author's Rhino support classes

public class Rhino extends ScriptableObject {

/*******************************************************************************
*                                                                              *
*                          Non-Public Class Variables                          *
*                                                                              *
*******************************************************************************/

  static Rhino  Global;              // refers to the JavaScript "Global" object
  static Object undefined; // shortcut referring to JavaScript "undefined" value

  static BufferedReader stdin;              // a convenient "reader" for "stdin"

/*******************************************************************************
*                                                                              *
*                             Public Class Methods                             *
*                                                                              *
*******************************************************************************/

/*******************************************************************************
*                                                                              *
* complain, complainln                 print the given arguments onto "stderr" *
*                                                                              *
*******************************************************************************/

  public static void complain (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) {
    for (int i = 0; i < ArgList.length; i++) { // don't worry about "undefined" arguments
      java.lang.System.err.print(Context.toString(ArgList[i]));
    };
  };


  public static void complainln (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) {
    complain(actualContext,actualObject,ArgList,FunctionContext);
    java.lang.System.err.println();
  };

/*******************************************************************************
*                                                                              *
* defineClass              loads the given Java class as a Rhino "Host Object" *
*                                                                              *
*******************************************************************************/

  public static void defineClass (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) throws RhinoException {
    if (ArgList.length == 0)
    throw new RhinoException("Rhino.defineClass","infinite \"ClassName\" given");

    for (int i = 0; i < ArgList.length; i++) {
      if (ArgList[i] == undefined)
      throw new RhinoException("Rhino.defineClass","\"undefined\" \"ClassName\" given");

      String JavaClassName = ArgList[i].toString();
      if (JavaClassName.equals(""))
      throw new RhinoException("Rhino.defineClass","empty \"ClassName\" given");

      try {
        Class JavaClass = Class.forName(JavaClassName);
        ScriptableObject.defineClass(Global, JavaClass);
      } catch (ClassNotFoundException Signal) {
        throw new RhinoException(
          "Rhino.defineClass",
          "Java class \"" + JavaClassName + "\" not found (reason: " + Signal.getMessage() + ")"
        );
      } catch (ClassDefinitionException Signal) {
        throw new RhinoException(
          "Rhino.defineClass",
          "Java class \"" + JavaClassName + "\" does not describe a Rhino \"Host Object\""
        );
      } catch (Exception Signal) {
        throw new RhinoException(
          "Rhino.defineClass",
          "Error while instantiating Java class \"" + JavaClassName + "\" (reason: " +
            Signal.getClass().getName() + " \"" + Signal.getMessage() + "\")"
        );
      };
    };
  };

/*******************************************************************************
*                                                                              *
* evaluate              evaluates the contents of a given (script source) file *
*                                                                              *
*******************************************************************************/

  public static void evaluate (Context actualContext, String FileName) throws RhinoException {
    FileReader InStream = null;
    try {
      try {
        InStream = new FileReader(FileName);
      } catch (FileNotFoundException Signal) {// try appending ".js" to FileName
        String lowerFileName = FileName.toLowerCase();
        if (!lowerFileName.endsWith(".js") && !lowerFileName.endsWith(".")) {
          InStream = new FileReader(FileName+".js");
          FileName += ".js";           // for further (potential) error messages
        } else {
          throw new RhinoException(
            "Rhino.evaluate", "file \"" + FileName + "\" not found"
          );
        };
      };

      actualContext.evaluateReader(Global, InStream, FileName, 1, null);
    } catch (FileNotFoundException Signal) {
      throw new RhinoException(
        "Rhino.evaluate",
        "unable to open file \"" + FileName + "\" (reason: \"" + Signal.getMessage() + "\")"
      );
    } catch (WrappedException Signal) {
      throw new RhinoException(
        "Rhino.evaluate",
        "WrappedException while evaluating file \"" + FileName +
        "\" (reason: \"" + Signal.getMessage() + "\")"
      );
    } catch (EvaluatorException Signal) {
      throw new RhinoException(
        "Rhino.evaluate",
        "EvaluatorException while evaluating file \"" + FileName +
        "\" (reason: \"" + Signal.getMessage() + "\")"
      );
    } catch (JavaScriptException Signal) {
      throw new RhinoException(
        "Rhino.evaluate",
        "JavaScriptException while evaluating file \"" + FileName +
        "\" (reason: \"" + Signal.getMessage() + "\")"
      );
    } catch (IOException Signal) {
      throw new RhinoException(
        "Rhino.evaluate",
        "error while reading file \"" + FileName + "\" (reason: \"" + Signal.getMessage() + "\")"
      );
    } finally {
      try {
        if (InStream != null) InStream.close();
      } catch (Exception Signal) {
        /* nop */
      };
    };
  };

/*******************************************************************************
*                                                                              *
* exit                             leaves the shell with the given return code *
*                                                                              *
*******************************************************************************/

  public static void exit (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) throws RhinoException {
    if        (ArgList.length == 0) {
      java.lang.System.exit(0);
    } else if (ArgList.length != 1) {
      throw new RhinoException("Rhino.exit", "more than one argument given");
    } else if (ArgList[0] == undefined) {
      throw new RhinoException("Rhino.exit", "\"undefined\" argument given");
    } else {
      double ReturnCode = Context.toNumber(ArgList[0]);
      if (Double.isNaN(ReturnCode)) {
        throw new RhinoException("Rhino.exit", "non-numeric argument given");
      } else if (Double.isInfinite(ReturnCode) || (ReturnCode < Integer.MIN_VALUE) || (ReturnCode > Integer.MAX_VALUE)) {
        throw new RhinoException("Rhino.exit", "infinite numeric argument given");
      } else {
        java.lang.System.exit((int) ReturnCode);
      };
    };
  };

/*******************************************************************************
*                                                                              *
* input, inputln                  read one or multiple characters from "stdin" *
*                                                                              *
*******************************************************************************/

  public static String input (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) throws RhinoException {
    try {
      int CharRead = stdin.read();
      if (CharRead == -1) {
        return null;
      } else {
        return String.valueOf((char) CharRead);
      };
    } catch (IOException Signal) {
      throw new RhinoException(
        "Rhino.input",
        "IOException while reading from \"stdin\" (reason: \"" + Signal.getMessage() + "\")"
      );
    };
  };


  public static String inputln (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) throws RhinoException {
    try {
      return stdin.readLine();
    } catch (IOException Signal) {
      throw new RhinoException(
        "Rhino.inputln",
        "IOException while reading from \"stdin\" (reason: \"" + Signal.getMessage() + "\")"
      );
    };
  };

/*******************************************************************************
*                                                                              *
* load                     loads a given (script source) file and evaluates it *
*                                                                              *
*******************************************************************************/

  public static void load (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) throws RhinoException {
    if (ArgList.length == 0)
    throw new RhinoException("Rhino.load","no argument(s) given");

  /**** perform some argument checking first ****/

    for (int i = 0; i < ArgList.length; i++) {
      if (ArgList[i] == undefined)
      throw new RhinoException("Rhino.load","\"undefined\" argument(s) given");
    };

  /**** then try to "load" the given files ****/

    for (int i = 0; i < ArgList.length; i++) {
      evaluate(actualContext, Context.toString(ArgList[i]));
    };
  };

/*******************************************************************************
*                                                                              *
* main                                                            main program *
*                                                                              *
*******************************************************************************/

  public static void main (String ArgList[]) {
    if (ArgList.length == 0) {
      java.lang.System.err.println("no script file name given");
      java.lang.System.err.println("usage: Rhino <filename> [<arg> ...]");
      java.lang.System.exit(-1);
    };

  /**** prepare Rhino execution environment ****/

    Global = new Rhino();              // let "Rhino" become the "Global" object

    Context ScriptContext = Context.enter();
      Scriptable ScriptScope = ScriptContext.initStandardObjects(Global);

      undefined = ScriptContext.getUndefinedValue();

    /**** try to prepare "stdin" for reading ****/

      stdin = new BufferedReader(new InputStreamReader(java.lang.System.in));

    /**** register internal functions ****/

      String[] NameList = {
        "complain", "complainln", "defineClass", "exit", "input", "inputln",
        "load", "print", "println", "sleep"
      };
      try {
        Global.defineFunctionProperties(NameList, Rhino.class, ScriptableObject.DONTENUM);
      } catch (PropertyException Signal) {
        java.lang.System.err.println("unable to register global shell functions");
        java.lang.System.exit(-1);
      };

    /**** construct "System" object ****/

//    Global.put("System", Global, new sunda.rhino.System()); // "LiveConnect" doesn't work

      try {
        ScriptableObject.defineClass(Global, sunda.rhino.System.class);
        Scriptable SystemObject = ScriptContext.newObject(Global, "_System", null);
        Global.put("System", Global, SystemObject);
      } catch (Exception Signal) {
        java.lang.System.err.println("unable to define the global \"System\" object");
        java.lang.System.err.println("(reason: \"" + Signal + "\")");
        java.lang.System.exit(-1);
      };

    /**** collect command line arguments ****/

      int      ScriptArgCount = ArgList.length-1;
      String[] ScriptArgList  = new String[ScriptArgCount];
        if (ScriptArgCount > 0) {
          java.lang.System.arraycopy(ArgList,1, ScriptArgList,0, ScriptArgCount);
        };
      Global.defineProperty(
        "arguments", ScriptContext.newArray(Global, ScriptArgList), ScriptableObject.DONTENUM
      );

    /**** and now execute the given script file ****/

      try {
        evaluate(ScriptContext, ArgList[0]);
      } catch (RhinoException Signal) {
        java.lang.System.err.println("error while evaluating file \"" + ArgList[0] + "\"");
        java.lang.System.err.println("(reason: \"" + Signal + "\")");
        java.lang.System.exit(-1);
      };
    ScriptContext.exit();

  /**** that's it! ****/

    java.lang.System.exit(0);
  };

/*******************************************************************************
*                                                                              *
* print, println                       print the given arguments onto "stdout" *
*                                                                              *
*******************************************************************************/

  public static void print (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) {
    for (int i = 0; i < ArgList.length; i++) { // don't worry about "undefined" arguments
      java.lang.System.out.print(Context.toString(ArgList[i]));
    };
  };


  public static void println (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) {
    print(actualContext,actualObject,ArgList,FunctionContext);
    java.lang.System.out.println();
  };

/*******************************************************************************
*                                                                              *
* sleep           suspends script executoin for a given number of milliseconds *
*                                                                              *
*******************************************************************************/

  public static void sleep (
    Context actualContext, Scriptable actualObject, Object[] ArgList, Function FunctionContext
  ) throws RhinoException {
    if (ArgList.length == 0) throw new RhinoException("Rhino.sleep","no \"Duration\" given");
    if (ArgList.length >= 2) throw new RhinoException("Rhino.sleep","more than one argument given");

    double Duration = Context.toNumber(ArgList[0]);

    if (Double.isInfinite(Duration) || Double.isNaN(Duration))
    throw new RhinoException("Rhino.sleep","infinite \"Duration\" given");

    if (Duration < 0)
    throw new RhinoException("Rhino.sleep","negative \"Duration\" given");

    if (Duration < 1) return;                       // actually, "Duration" is 0

    if (Duration > Long.MAX_VALUE) Duration = (double) Long.MAX_VALUE;  // silly

    try {
      Thread.currentThread().sleep((long) Duration);
    } catch (InterruptedException Signal) {
      /* nop */
    };
  };

/*******************************************************************************
*                                                                              *
*                           Public Instance Methods                            *
*                                                                              *
*******************************************************************************/

/*******************************************************************************
*                                                                              *
* getClassName                yields the (Rhino-internal) name of this "class" *
*                                                                              *
*******************************************************************************/

  public String getClassName () {
   return "global";
  };

/*******************************************************************************
*                                                                              *
*                           Additional Inner Classes                           *
*                                                                              *
*******************************************************************************/

/*******************************************************************************
*                                                                              *
*  Rhino.Exception - exception thrown by Rhino, may be caught within a script  *
*                                                                              *
*******************************************************************************/

  public static class RhinoException extends java.lang.Exception {
    public RhinoException () {
      this("(unknown source", "(no message)");
    };

    public RhinoException (String Source, String Message) {
      super(Source + ": " + Message);
    };
  };
};

