jeval
CONTENT

Overview

jeval - command line Java code evaluator. It is similar to JShell except it provides you additional functionality and better integration with command-line. jeval allows you to declare dependencies in Java scripts to any libraries from Maven repository so that it will automatically resolve them and add to the Java script class path.

jeval allows you to execute Java code straight from the command line (same as you would do with perl -e, bash -c):

jeval -e 'out.println("Hello world")'
Hello world

In JShell to achieve that you need to use additional command like echo for example:

echo 'System.out.println("Hello world")' | jshell -

Additionally with jeval you can execute complete Java shell scripts and see compilation errors if any:

cat test.java
System.out.println("Hello world");
jeval test.java
Hello world

With JShell you would have to use "/exit" in the end (otherwise it will start interactive mode):

cat test.java
System.out.println("Hello world");
/exit
jshell test.java
Hello world

And JShell will not print you any errors:

cat test.java
System.out.println("Hello world");
error here
/exit
jshell test.java
Hello world

Another nice feature of jeval is that you can use pipes to pass data to your Java code (it binds all standard streams to support lazy reading from stdin predefined variable which is described later):

echo -e "ab\ncd\nef" | jeval -e "stdin.lines().collect(joining(\",\"))"
"ab,cd,ef"

For JShell there is no default support of that.

With jeval you can pass arguments to your Java scripts and handle them in same way as you do it in Java (through global args variable similar as in main() function):

jeval -e 'format("args %s, %s", args[0], args[1])' "arg1" "arg2"
"args arg1, arg2"

jeval does not require you to write class body with main method and all boilerplate code (but you still can do it if you want). You just write Java as you would do it with JShell.

Download

You can download jeval from here

Requirements

Java 17

Install

Linux

Run following command if you use bash:

echo "export PATH=$PATH:<JEVAL_INSTALL_DIR>" >> ~/.bashrc

Or in case you use zsh:

echo "export PATH=$PATH:<JEVAL_INSTALL_DIR>" >> ~/.zshrc

Windows

Open cmd and execute following command:

setx PATH "%PATH%;<JEVAL_INSTALL_DIR>"

Usage

jeval [ <JAVA_SCRIPT> | -e <JAVA_SNIPPET> | -i ] [ARGS]

Where:

Options:

Class path

To add new JAR files into class path use CLASSPATH env variable:

CLASSPATH=/opt/javafx-sdk-11.0.2/lib/* jeval script.java

JVM arguments

To pass arguments to the JVM use JAVA_ARGS env variable:

JAVA_ARGS="-Dtest=hello -Xmx50m" jeval -e 'System.getProperty("test")' "hello"

Commands

jeval provides you with some additional commands which you can put to your Java script files. All of them can be called from Java comments only. This helps to avoid adding any arbitrary changes to the Java language, which would prevent code from Java scripts, later be easily compilable with javac, or within IDEs.

//dependency

This command allows you to include dependencies to any artifacts from Maven repository into the Java script files. Without need to create pom.xml or build.gradle for that.

Format of this command is:

//dependency ARTIFACT_NAME

Where ARTIFACT_NAME is a full name of the artifact in the format: <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>

Here is an example of simple script yaml.java which declares dependency on snakeyaml library and uses it:

//dependency org.yaml:snakeyaml:1.21
import org.yaml.snakeyaml.*;
Yaml yaml = new Yaml();
String document = "\n- Hesperiidae\n- Papilionidae\n- Apatelodidae\n- Epiplemidae";
List list = (List) yaml.load(document);
System.out.println(list);

To run:

jeval yaml.java
[Hesperiidae, Papilionidae, Apatelodidae, Epiplemidae]

To provide this functionality jeval integrates with depresolve tool which depends on maven-resolver library

//open

jeval supports "//open" command similar to "/open" command which JShell provides. For example using this command you can extract some common logic into separate script file so jeval will read that.

For example here we keep parsing functions in script called parsers.java:

String parseToHex(int num) {
    return Integer.toHexString(num);
}

And use them from script called script.java:

//open parsers.java
printf(parseToHex(123) + "\n");

Now run:

jeval script.java
7b

Here in script.java we include parsers.java and then call parseToHex method defined in parsers.java.

Default imports

jeval by default imports following packages and static methods to the global space so you don't need to worry to import them each time manually:

java.util.stream.IntStream.*
java.util.stream.Collectors.*
java.lang.System.*
java.nio.file.Files.*
java.lang.Math.*
java.util.Arrays.*
javax.script.*
java.lang.String.*

java.util.*
java.util.stream.*
java.util.concurrent.*
java.util.function.*
java.util.regex.*
java.io.*
java.nio.*
java.nio.file.*
javax.xml.parsers.*
javax.xml.xpath.*
java.net.*
java.net.http.*
java.net.http.HttpResponse.*
org.w3c.dom.*
org.xml.sax.*
java.time.*
java.time.format.*

jeval comes with xfunction library and exports most of it classes and methods to global space as well.

Predefined variables

String[] args

Arguments (if any) passed to the Java script


BufferedReader stdin

This is an instance of java.io.BufferedReader connected to System.in which allows you to operate with System.in as with stream of lines which is often useful when writing scripts (see Snippets section).


Optional<Path> scriptPath

Contains path to currently executing script. It is empty when used in Java snippets (jeval with -e option)


In addition to them jeval exports following variables from xfunction library:

Predefined functions

void sleep(long msec)

Standard way to sleep in Java is pretty verbose because it throws checked exception which you need to handle:

 try {
     Thread.sleep(1000);
 } catch (InterruptedException e) {
     throw new RuntimeException(e);
 }

When you write scripts in Java you probably want it to fit in one line and wrap any thrown exception to unchecked. With this function now you can sleep like that:

sleep(1000);

void error(String msg)

Throws RuntimeException with a given message


void isTrue(boolean expr)

If expr is false throw PreconditionException


void isTrue(boolean expr, String message)

If expr is false throw PreconditionException with message


Stream<String> findMatches(String regexp, String str)

Search string for substrings which satisfy the regexp and return them in a stream

Predefined classes

Netcat

Implements netcat operations:


static void listen(int port)

static void connect(String host, int port)


All input/output goes through stdin/stdout.

Shebang (#!)

jeval supports "#!" interpreter directive which is available in Unix-like operating systems.

It allows instead of calling jeval each time to execute Java script:

jeval script.java

To run it directly as ordinary executable file:

./script.java

For that to work you need to put following line as a first line of script.java:

#!/usr/bin/env jeval

And make file executable:

chmod u+x script.java

Snippets

Here are some examples of calling Java standard classes with jeval right from the command line.

Say hello to the world

jeval -e 'out.println("Hello world")'
Hello world

Print sequence of numbers

jeval -e "range(1,10).forEach(out::println)"
1
2
3
4
5
6
7
8
9

Read XML and print value of the element using its XPath

cat << EOF > /tmp/r.xml <notes> <note> <to test="ggg1">Tove</to> </note> <note> <to test="ggg2">Bove</to> </note> </notes> EOF jeval -e 'out.println(XPathFactory.newInstance().newXPath().evaluate("//note/to", DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File("/tmp/r.xml"))))'
Tove

Return integer in binary format:

echo 14 | jeval -e 'Integer.toBinaryString(new Scanner(in).nextInt())'
"1110"

Create temporary file and return its name

jeval -e 'Files.createTempFile(null, "tmp")'
/tmp/11873450107364399793tmp

Join lines using "," as a delimeter

echo -e "ab\ncd\nef" | jeval -e "stdin.lines().collect(joining(\",\"))"
"ab,cd,ef"

Use Netcat and listen for incoming connection on port 12345

jeval -e "Netcat.listen(12345)"

Use commandline arguments

jeval -e 'format("args %s, %s", args[0], args[1])' "arg1" "arg2"
"args arg1, arg2"

Measure execution real time

jeval -e "new Microprofiler().measureRealTime(() -> sleep(1000));"

Run command

jeval -e 'new XExec("curl -L -G -vvv http://google.com").start().stderrAsStream().forEach(out::println)'

Query XML using XPath

jeval -e 'out.println(Xml.query("<notes><note><to test=\"ggg1\">Tove</to></note><note><to test=\"ggg2\">Bove</to></note></notes>", "//note/to/@test"))'
[ggg1, ggg2]

Search substrings using regexp

jeval -e 'findMatches("\\d.jpg", "1.jpg 2.png 3.jpg 4.txt 5.txt").forEach(out::println)'
1.jpg
3.jpg

Run commands in parallel

Here is an example which creates 20 files with random names doing this in parallel on different threads:

jeval -e 'try (var c = new ParallelConsumer(s -> new XExec("touch /tmp/test-" + s).start().await())) {XStream.infiniteRandomStream(12).limit(20).forEach(c);}'

Generate string with length N

jeval -e 'out.println("x".repeat(1000))'

FAQ

Why should this be chosen over Beanshell

With jeval we try not to modify/extend Java syntax for lambdas, variable type inference, method pointers or anything else like this is done in Beanshell. It means that your write plain Java code which later can be compiled with javac.

Why jeval -e 'out.format("args")' prints java.io.PrintStream@ at the end

With -e option jeval evaluates the expression and prints its result. In this case method PrintStream::format returns reference to PrintStream so that is why it is printed.

To overcome this you can use printf method defined by jeval.

Why I get SPIResolutionException

Those are generated by JShell. Most likely you run multiple threads and some of them try to execute unresolved snippets. Wait until jeval finishes execution then search for its diagnostics in stderr.

ClassNotFoundException

In JShell you cannot access classes from default (unnamed) packages.

Another issue is that Class.forName does not support classes which were declared inside JShell:

jshell> class X {}
|  created class X

jshell> Class.forName("X")
|  Exception java.lang.ClassNotFoundException: X
|        at URLClassLoader.findClass (URLClassLoader.java:471)
|        at DefaultLoaderDelegate$RemoteClassLoader.findClass (DefaultLoaderDelegate.java:154)
|        at ClassLoader.loadClass (ClassLoader.java:588)
|        at ClassLoader.loadClass (ClassLoader.java:521)
|        at Class.forName0 (Native Method)
|        at Class.forName (Class.java:315)
|        at (#4:1)

Since jeval depends on JShell, same constraints applies to it.

Links