5

I'm trying to run some Java code at run-time through a JShell instance I created using the JShell API. To demonstrate my problem, I'm going to share my simple code.

With my current setup I have a directory called lib that has the MySQL Java driver: mysql-connector-java-5.1.35.jar.

Launching JShell via command tool and adding the needed module as:

jshell --module-path lib --add-modules mysql.connector.java

and then loading the mysql driver works for me :

jshell> Class.forName("com.mysql.jdbc.Driver").newInstance();
$1 ==> com.mysql.jdbc.Driver@42f93a98

I've created a similar Java 9 module with module-info.java as:

module example.loadmysql {
    requires java.sql;
    requires mysql.connector.java;
    requires jdk.jshell;
}

src/example/loadmysql/Runner.java as :

package example.loadmysql;

import jdk.jshell.*;
import java.sql.*;

public class Runner {
    public static void main(String[] args) throws Exception {
        // this works because this module requires mysql.connector.java
        System.out.println(Class.forName("com.mysql.jdbc.Driver").newInstance());

        JShell js = JShell.create();
        String code = ""
            + "try {"
            + "    Class.forName(\"com.mysql.jdbc.Driver\").newInstance();"
            + "} catch (Exception e) {"
            + "    System.out.println(e.toString());"
            + "}";
        js.eval(code);
    }
}

After building/packaging:

java -p lib -m example.loadmysql
com.mysql.jdbc.Driver@6a4f787b
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

It's clear that even though the example.loadmysql module requires the mysql connector, the created JShell instance doesn't. So it can't find the class.

Any ideas on how to programmatically add modules to a JShell instance, so it works like the direct JShell coding example?

UPDATE - I've figured out how to set the module path:

String modulePath = System.getProperty("jdk.module.path");
js.eval("System.setProperty(\"jdk.module.path\", \""
    + modulePath + "\");");

But that's not quite enough. I still have add the needed module somehow.

Omid
  • 5,823
  • 4
  • 41
  • 50
  • 1
    Did you try putting [`addToClassPath`](http://download.java.net/java/jdk9/docs/api/jdk/jshell/JShell.html#addToClasspath-java.lang.String-) before `eval`? – Naman Sep 25 '17 at 08:06
  • I could add to classpath and that seems to work. Though it's not in the spirit Java 9 and using modules! I'm not sure that if the mysql connector is eventually converted into a module if the classpath method will still work. I might do it this way if there's no other solution though! Thanks a lot. – Dylan Johnson Sep 25 '17 at 08:16

2 Answers2

3

You can probably use addToClassPath before eval in your code as:

JShell js = JShell.create();
js.addToClasspath("path/to/add/to/the/classpath");
String code = ""
        + "try {"
        + "    Class.forName(\"com.mysql.jdbc.Driver\").newInstance();"
        + "} catch (Exception e) {"
        + "    System.out.println(e.toString());"
        + "}";
js.eval(code);

The specified path is added to the end of the classpath used in eval(). Note that the unnamed package is not accessible from the package in which eval(String) code is placed.

It seems from the documentation that the state of JShell returned post eval executes the code based on the classpath, hence in order to add any further dependencies to it, you would need to add it to the classpath using the same method.


In your case I am guessing here though as you do so, the mysql-connector-java-5.1.35.jar would ideally be treated to as an automatic module present on the classpath and hence the class com.mysql.jdbc.Driver would be accessible.


Update :- Exploring further I think a better way to achieve this could be though trying to use Jshell.Builder and its option compilerOptions to create an instance with default compiling options somewhat like(not tested) -

JShell js = JShell.builder()
                 .compilerOptions("--module-path lib","--add-modules mysql.connector.java").build();
String code = ""
    + "try {"
    + "    Class.forName(\"com.mysql.jdbc.Driver\").newInstance();"
    + "} catch (Exception e) {"
    + "    System.out.println(e.toString());"
    + "}";
js.eval(code);
Naman
  • 27,789
  • 26
  • 218
  • 353
  • 2
    Thank you! This method will work for now. I just have to keep track of both module path and class path. I would prefer handling it the Java 9 way with modules, but if that's not possible I can just stick to this. I'll accept your answer for now I suppose! – Dylan Johnson Sep 25 '17 at 08:20
  • 2
    @DylanJohnson ya doesn't seem to be an ideal way but seems like the `eval` by itself uses a classpath to evaluate the code provided. – Naman Sep 25 '17 at 08:24
  • 1
    @DylanJohnson Though not tested. But probably the edit should be a cleaner way to help here. – Naman Sep 25 '17 at 17:05
  • 1
    Good idea! Unfortunately I get IllegalArgumentExceptions. I can't seem to find much documentation on this compilerOptions method either. Like which flags are legal! Thank you though; this gives me some more ideas to experiment with. – Dylan Johnson Sep 25 '17 at 18:43
0

You can use the /env command to add modules, see help:

/env [-class-path <path>] [-module-path <path>] [-add-modules <modules>] ...
|   view or change the evaluation context

Details:

jshell> /help context
|  
|  context
|  
|  These options configure the evaluation context, they can be specified when
|  jshell is started: on the command-line, or restarted with the commands /env,
|  /reload, or /reset.
|  
|  They are:
|   --class-path <class search path of directories and zip/jar files>
|       A list of directories, JAR archives,
|       and ZIP archives to search for class files.
|       The list is separated with the path separator
|       (a : on unix/linux/mac, and ; on windows).
|   --module-path <module path>...
|       A list of directories, each directory
|       is a directory of modules.
|       The list is separated with the path separator
|       (a : on unix/linux/mac, and ; on windows).
|   --add-modules <modulename>[,<modulename>...]
|       root modules to resolve in addition to the initial module.
|       <modulename> can also be ALL-DEFAULT, ALL-SYSTEM,
|       ALL-MODULE-PATH.
|   --add-exports <module>/<package>=<target-module>(,<target-module>)*
|       updates <module> to export <package> to <target-module>,
|       regardless of module declaration.
|       <target-module> can be ALL-UNNAMED to export to all
|       unnamed modules. In jshell, if the <target-module> is not
|       specified (no =) then ALL-UNNAMED is used.
|  
|  On the command-line these options must have two dashes, e.g.: --module-path
|  On jshell commands they can have one or two dashes, e.g.: -module-path
rmuller
  • 12,062
  • 4
  • 64
  • 92
  • 1
    I believe OP is trying to achieve the same using java code. Otherwise the `--module-path` and `--add-modules` works fine as mentioned in the question as well. – Naman Sep 25 '17 at 08:10
  • 2
    Unfortunately I've found through my testing that you can't evaluate a /env command programmatically. Only from directly working inside JShell. Good idea though! – Dylan Johnson Sep 25 '17 at 08:13
  • 1
    @DylanJohnson Yes, this does not work indeed :(. If i read the documentation carefully, my impression is however that it should work (see update). No mention that `JShell` does not support this functionality. Needs some more investigation ... – rmuller Sep 25 '17 at 12:45