JTCL with Apache BSF

I am sure there are issues with this, but I thought it worth sharing, if only to get corrections from people who know better. I can be contacted at gongoputch65 at gmail dot com

I have put this up at https://chiselapp.com/user/gongoputch/repository/jtcl_bsf_engine

1. Unpack a recent commons-bsf as a peer to this directory. i used
        https://github.com/apache/commons-bsf. 

2. add this:
        "jtcl = org.apache.bsf.engines.jtcl.JtclEngine, jtcl | tcl"
   to language.properties in src/main/java/org/apache/bsf/
   
3. build commons-bsf "by the numbers" (you only need go as far as 'jar') 

4. in this dir :
        ./build.sh
   I exclude user libs tomake sure I don't pick up past junk.
   I hope I made the build.xml portable (enough)
   The jtcl-2.8.0.jar in lib is built from source, you can put your own in
   too.
   
5. copy ../commons-bsf/build/lib/bsf.jar to ~/.ant/lib/
   now:
   ant test_ext
   and 
   ant test_script
   should work (I hope)
   
BSFCommand.java
 and
JtclEngine.java
 Are pretty much what came from JACL, whith very minimal changes, there are more
API points to BSF but this makes a baseline.

 This gets me started using ant to do interesting things with jtcl like:
* remote admin with an RMI based ant system called (surprisingly) remoteant :)
* producing itclproxy wrappers with ant (-contrib) tasks
        I have jdom2 and lwjgl in progress
* manipulating ant tasks from jtcl
* calling beans via the "bsf" command
... it fires the imagination ...

Directory structure for ant project :

./test1.jtcl
./src/main/java/org/apache/bsf/engines/jtcl/BSFCommand.java
./src/main/java/org/apache/bsf/engines/jtcl/JtclEngine.java
./build.xml
./lib/jtcl-2.8.0.jar
./build.sh

Contents of BSFCommand.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.bsf.engines.jtcl;

import org.apache.bsf.BSFEngine;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.bsf.util.EngineUtils;

import tcl.lang.Command;
import tcl.lang.Interp;
// import tcl.lang.ReflectObject;
import tcl.pkg.java.ReflectObject;
import tcl.lang.TCL;
import tcl.lang.TclException;
import tcl.lang.TclObject;

// class used to add "bsf" command to the Jacl runtime
class BSFCommand implements Command {
  BSFManager mgr;
  BSFEngine jengine;

  BSFCommand (BSFManager mgr, BSFEngine jengine) {
    this.mgr = mgr;
    this.jengine = jengine;
  }
  public void cmdProc (Interp interp, 
               TclObject argv[]) throws TclException {
    if (argv.length < 2) {
      interp.setResult ("invalid # of args; usage: bsf " +
        "lookupBean|registerBean|unregisterBean|addEventListener args");
      throw new TclException (TCL.ERROR);
    }

    String op = argv[1].toString ();

    if (op.equals ("lookupBean")) {
      if (argv.length != 3) {
    interp.setResult ("invalid # of args; usage: bsf " +
              "lookupBean name-of-bean");
    throw new TclException (TCL.ERROR);
      }

      String beanName = argv[2].toString ();
      Object bean = mgr.lookupBean (beanName);
      if (bean == null) {
    interp.setResult ("unknown object: " + beanName);
    throw new TclException (TCL.ERROR);
      }
      interp.setResult (ReflectObject.newInstance (interp, bean.getClass (), 
                           bean));

    } else if (op.equals ("registerBean")) {
      if (argv.length != 4) {
    interp.setResult ("invalid # of args; usage: bsf " +
              "registerBean name-of-bean bean");
    throw new TclException (TCL.ERROR);
      }
      mgr.registerBean (argv[2].toString (), 
            ReflectObject.get (interp, argv[3]));
      interp.setResult ("");

    } else if (op.equals ("unregisterBean")) {
      if (argv.length != 3) {
    interp.setResult ("invalid # of args; usage: bsf " +
              "unregisterBean name-of-bean");
    throw new TclException (TCL.ERROR);
      }
      mgr.unregisterBean (argv[2].toString ());
      interp.setResult ("");

    } else if (op.equals ("addEventListener")) {
      if (argv.length != 6) {
    interp.setResult ("invalid # of args; usage: bsf " +
              "addEventListener object event-set-name filter " +
              "script-to-run");
    throw new TclException (TCL.ERROR);
      }
      try {
    // usage: bsf addEventListener object event-set filter script
    String filter = argv[4].toString ();
    filter = filter.equals ("") ? null : filter;
    EngineUtils.addEventListener (ReflectObject.get (interp, argv[2]),
                      argv[3].toString (), filter,
                      jengine, mgr, "<event-script>", 0, 0,
                      argv[5].toString ());
      } catch (BSFException e) {
    e.printStackTrace ();
    interp.setResult ("got BSF exception: " + e.getMessage ());
    throw new TclException (TCL.ERROR);
      }
    }
  }
}

Contents of JtclEngine.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.bsf.engines.jtcl;

import java.util.Vector;

import org.apache.bsf.BSFDeclaredBean;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.bsf.util.BSFEngineImpl;

import tcl.lang.Interp;
//import tcl.lang.ReflectObject;
import tcl.pkg.java.ReflectObject;
import tcl.lang.TclDouble;
import tcl.lang.TclException;
import tcl.lang.TclInteger;
import tcl.lang.TclObject;
import tcl.lang.TclString;

/**
 * This is the interface to Scriptics's Jacl (Tcl) from the
 * Bean Scripting Framework.
 * <p>
 *
 * @author   Sanjiva Weerawarana
 */

public class JtclEngine extends BSFEngineImpl {
  /* the Jacl interpretor object */
  private Interp interp;

  /**
   * 
   * @param method The name of the method to call.
   * @param args an array of arguments to be
   * passed to the extension, which may be either
   * Vectors of Nodes, or Strings.
   */
  public Object call (Object obj, String method, Object[] args) 
                                                        throws BSFException {
    StringBuffer tclScript = new StringBuffer (method);
    if (args != null) {
      for( int i = 0 ; i < args.length ; i++ ) {
    tclScript.append (" ");
    tclScript.append (args[i].toString ());
      }
    }
    return eval ("<function call>", 0, 0, tclScript.toString ());
  }
  /**
   * Declare a bean
   */
  public void declareBean (BSFDeclaredBean bean) throws BSFException {
    String expr = "set " + bean.name + " [bsf lookupBean \"" + bean.name +
          "\"]";
    eval ("<declare bean>", 0, 0, expr);
  }
  /**
   * This is used by an application to evaluate a string containing
   * some expression.
   */
  public Object eval (String source, int lineNo, int columnNo, 
              Object oscript) throws BSFException {
    String script = oscript.toString ();
    try {
      interp.eval (script);
      TclObject result = interp.getResult();
      Object internalRep = result.getInternalRep();

      // if the object has a corresponding Java type, unwrap it
      if (internalRep instanceof ReflectObject)
        return ReflectObject.get(interp,result);
      if (internalRep instanceof TclString)
        return result.toString();
      if (internalRep instanceof TclDouble)
        return new Double(TclDouble.get(interp,result));
      if (internalRep instanceof TclInteger)
        return new Integer(TclInteger.get(interp,result));

      return result;
    } catch (TclException e) { 
      throw new BSFException (BSFException.REASON_EXECUTION_ERROR,
                  "error while eval'ing Jtcl expression: " + 
                  interp.getResult (), e);
    }
  }
  /**
   * Initialize the engine.
   */
  public void initialize (BSFManager mgr, String lang,
              Vector declaredBeans) throws BSFException {
    super.initialize (mgr, lang, declaredBeans);

    // create interpreter
    interp = new Interp();

    // register the extension that user's can use to get at objects
    // registered by the app
    interp.createCommand ("bsf", new BSFCommand (mgr, this));

    // Make java functions be available to Jacl
        try {
        interp.eval("jaclloadjava");
    } catch (TclException e) {
        throw new BSFException (BSFException.REASON_OTHER_ERROR,
                    "error while loading java package: " +
                    interp.getResult (), e);
    }

    int size = declaredBeans.size ();
    for (int i = 0; i < size; i++) {
      declareBean ((BSFDeclaredBean) declaredBeans.elementAt (i));
    }
  }

  /**
   * Undeclare a previously declared bean.
   */
  public void undeclareBean (BSFDeclaredBean bean) throws BSFException {
    eval ("<undeclare bean>", 0, 0, "set " + bean.name + " \"\"");
  }
}

Contents of build.xml :

<?xml version="1.0"?>
<project name="jtcl-engine" default="jar">
<!-- ======================================================================= -->
<!-- Compile JTCL BSF engine -->
<target name="compile">
        <mkdir dir="./build/classes"/>
        <javac 
                debug="true" 
                includes="**/**.java"
                destdir="./build/classes/" 
                srcdir="./src/"
        >
                <classpath>
                        <fileset dir="./lib/" includes="*.jar"></fileset>
                        <fileset dir="../commons-bsf/build/lib/" includes="bsf.jar"></fileset>
                </classpath>
        </javac>
</target>
<!-- ======================================================================= -->
<!-- Make JTCL BSF engine jar -->
<target name="jar" depends="compile">
        <jar 
                destfile="build/jtcl-engine.jar" 
                basedir="build/classes"
        />
</target>
<!-- ======================================================================= -->
<!-- -->
<target name="clean">
        <delete file="build/jtcl-engine.jar"></delete>
        <delete dir="./build/classes"></delete>
</target>
<!-- ======================================================================= -->
<!-- -->
<target name="test_ext">
        <java classname="org.apache.bsf.Main">
                <classpath>
                        <path path="../commons-bsf/build/lib/bsf.jar"></path>
                        <path path="./build/jtcl-engine.jar"></path>
                        <path path="./lib/jtcl-2.8.0.jar"></path>
                </classpath>
                <arg line="-in"/>
                <arg line="./test1.jtcl"/>
        </java>
</target>
<!-- ======================================================================= -->
<!-- copy ../commons-bsf/build/lib/bsf.jar to ~/.ant/lib/ first -->
<target name="test_script">
        <script language="jtcl">
                <classpath>
                        <path path="./build/jtcl-engine.jar"></path>
                        <path path="./lib/jtcl-2.8.0.jar"></path>
                </classpath>
                package require java
                puts "from ant"
                puts "Ant tasks : "
                foreach TASK [lsort [java::info methods $project]] \
                {
                        puts "\t $TASK"
                }
                puts "Packages : "
                foreach PKG [lsort [package names]] \
                {
                        puts "\t $PKG"
                }
        </script>
</target>
<!-- ======================================================================= -->
<!-- -->
</project>

Contents of build.sh :

#!/bin/sh

ant \
        -nouserlib \
        "$@"

I made a BSF utility script to play with loading jtcl and jacl (and janino, and rhino). Keeping jtcl and jacl from stepping on each other.

#!/bin/sh
# ******************************************************************************
D=$(dirname `readlink -f $0`)
LOC=$(readlink -f $D)
export LOC

LOC_LIB=${LOC}/lib/
export LOC_LIB
# -------------------------------------
# JAVA set up
unset JAVA_TOOL_OPTIONS

JAVA_HOME=/usr/local/openjdk8/
export JAVA_HOME

JAVA=${JAVA_HOME}/bin/java
export JAVA
# -------------------------------------
# Beanscript 2/0 b6 (from ports)
CLASSPATH=${CLASSPATH}:/usr/local/share/java/classes/bsh.jar
# -------------------------------------
# Rhino
CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/rhino/rhino,jar
# -------------------------------------
# BSF jars
CLASSPATH=${CLASSPATH}:${LOC}/lib/jdom-b9.jar
CLASSPATH=${CLASSPATH}:${LOC}/lib/commons-logging-1.0.4.jar
CLASSPATH=${CLASSPATH}:${LOC}/lib/velocity-1.4.jar
CLASSPATH=${CLASSPATH}:${LOC}/lib/xalan-2.7.0.jar
CLASSPATH=${CLASSPATH}:${LOC}/lib/commons-collections-3.1.jar
CLASSPATH=${CLASSPATH}:${LOC}/lib/junit-3.8.2.jar
CLASSPATH=${CLASSPATH}:${LOC}/lib/xerces-2.4.0.jar
CLASSPATH=${CLASSPATH}:${LOC}/lib/log4j-1.2.12.jar
CLASSPATH=${CLASSPATH}:${LOC}/build/lib/bsf.jar
export CLASSPATH
# -------------------------------------
# TCL library path
TCLLIBPATH="${LOC}/library/"
# TCLLIBPATH="${TCLLIBPATH} /opt/local/lib/ "
export TCLLIBPATH
# ******************************************************************************
# Parse the pre command options
#
args=`getopt j: $*`
set -- $args
while true; 
do
        case "$1" in
        -j)
                CLASSPATH=${CLASSPATH}:$2
                export CLASSPATH
                shift; shift
                ;;
        --)
                shift; break
                ;;
        esac
done
# -------------------------------------
# Run the command
case $1 in
janino)
        shift
        CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/jtcl/jtcl-2.8.0.jar
        CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/jtcl/jtcl-engine.jar
        export CLASSPATH
        # ------------------------------------
        # Mains :
        # org/codehaus/janino/tools/Disassembler
        # org/codehaus/janino/tools/HprofScrubber
        # org/codehaus/janino/CachingJavaSourceClassLoader
        # org/codehaus/janino/samples/DeclarationCounter
        # org/codehaus/janino/samples/ExpressionDemo
        # org/codehaus/janino/samples/ShippingCost
        # org/codehaus/janino/samples/ScriptDemo
        # org/codehaus/janino/samples/ClassBodyDemo
        # org/codehaus/janino/UnicodeUnescapeReader
        # org/codehaus/janino/UnparseVisitor
        #
        case $1 in 
        jload)
                shift
                ${JAVA} \
                        -classpath ${CLASSPATH} \
                        org.codehaus.janino.JavaSourceClassLoader \
                        "$@"
                ;;
        jgrep)
                shift
                ${JAVA} \
                        -classpath ${CLASSPATH} \
                        org.codehaus.janino.tools.JGrep \
                        "$@"
                ;;
        cc)
                shift
                ${JAVA} \
                        -classpath ${CLASSPATH} \
                        org.codehaus.janino.Compiler "$@"
                ;;
        simple)
                shift
                ${JAVA} \
                        -classpath ${CLASSPATH} \
                        org.codehaus.janino.SimpleCompiler "$@"
                ;;
        dis)
                shift
                ${JAVA} \
                        -classpath ${CLASSPATH} \
                        org.codehaus.janino.tools.Disassembler "$@"
                ;;
        script_demo)
                shift
                ${JAVA} \
                        -classpath ${CLASSPATH} \
                        org.codehaus.janino.samples.ScriptDemo "$@"
                ;;
        *)
                echo
                echo "Janino tools help:"
                echo
                echo "jload       : org.codehaus.janino.JavaSourceClassLoader (-help)"
                echo "jgrep       : org.codehaus.janino.tools.JGrep (-help)"
                echo "cc          : org.codehaus.janino.Compiler (-help)"
                echo "simple      : org.codehaus.janino.SimpleCompiler (-help)"
                echo "dis         : org.codehaus.janino.tools.Disassembler (-help)"
                echo "script_demo : org.codehaus.janino.samples.ScriptDemo (-help)"
                echo
        esac
        ;;
        # ------------------------------------
jtcl)
        shift
        CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/jtcl/jtcl-2.8.0.jar
        CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/jtcl/jtcl-engine.jar
        export CLASSPATH
        ${JAVA} \
                -classpath ${CLASSPATH} \
                org.apache.bsf.Main \
                "$@"
        ;;
jacl)
        shift
        CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/jacl/itcl.jar
        CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/jacl/jacl.jar
        CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/jacl/janino.jar
        CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/jacl/tcljava.jar
        CLASSPATH=${CLASSPATH}:${LOC}/lib/engines/jacl/tjc.jar
        export CLASSPATH
        ${JAVA} \
                -classpath ${CLASSPATH} \
                org.apache.bsf.Main \
                "$@"
        ;;
help)
        echo
        echo "Help :"
        echo
        echo "$0 jtcl ..."
        echo "           set up jtcl and run org.apache.bsf.Main"
        echo "$0 janino ..."
        echo "           set up janino utils"
        echo "$0 rhino ..."
        echo "           set up rhino and run org.apache.bsf.Main"
        echo
        ;;
*)
        ${JAVA} \
                -classpath ${CLASSPATH} \
                org.apache.bsf.Main \
                "$@"
        ;;
esac

This is how the directory structure for the script lays out :

./lib
./lib/jdom-b9.jar
./lib/commons-logging-1.0.4.jar
./lib/velocity-1.4.jar
./lib/xalan-2.7.0.jar
./lib/commons-collections-3.1.jar
./lib/junit-3.8.2.jar
./lib/xerces-2.4.0.jar
./lib/log4j-1.2.12.jar
./lib/engines
./lib/engines/rhino
./lib/engines/rhino/rhino.jar
./lib/engines/jtcl
./lib/engines/jtcl/javahelp-2.0.05.jar
./lib/engines/jtcl/jtcl-2.8.0.jar
./lib/engines/jtcl/swank-3.0.0.jar
./lib/engines/jtcl/jtcl-engine.jar
./lib/engines/jtcl/jfreechart-1.0.13.jar
./lib/engines/jtcl/jcommon-1.0.16.jar
./build
./build/lib
./build/lib/bsf.jar
./bsf.sh

Things I want to investigate/do :

* A jline/jline2 command line processor

* Get Swank to run in BSF

* Do this all again in JSR 223 (javax.script) as this is the future facing java scripting interface