#2534 Wot no Console?

SlimerDude Thu 21 Apr 2016

Take this tiny example that waits for the user to enter something:

class Example {
    static Void main(Str[] args) {
        name := Env.cur.prompt("Enter Name: ")
        echo(name)
    }
}

And try to run it from Fantom:

class CallExample {
    static Void main(Str[] args) {
        process := Process()
        process.command = [Env.cur.homeDir.plus(`bin/fan.bat`).osPath, "Example.fan"]
        process.in      = "Slim Shady\n".toBuf.in
        process.run.join
    }
}

Then you receive an NPE:

C:\>fan CallExample.fan

sys::NullErr: java.lang.NullPointerException
  fan.sys.BootEnv.prompt (BootEnv.java:154)
  Example_0::Example.main (/C:/Example.fan:4)
  java.lang.reflect.Method.invoke (Method.java:597)
  fan.sys.Method.invoke (Method.java:559)
  ...

The reason is that Env.prompt() tries to read from Java's System.console() - but if the program is not run from a Cmd Prompt or Terminal, then there is no Console!

As these posts show, this can also be true when run from an IDE:

The suggested fix is to revert back to using System.in should System.console() be null, which works!

C:\>fan CallExample.fan

Enter Name: Slim Shady

The patch to BootEnv.fan is given below:

diff -r f5e659e8f7c7 src/sys/java/fan/sys/BootEnv.java
--- a/src/sys/java/fan/sys/BootEnv.java	Wed Apr 20 14:01:49 2016 -0400
+++ b/src/sys/java/fan/sys/BootEnv.java	Thu Apr 21 12:41:40 2016 +0100
@@ -11,6 +11,8 @@
 import java.net.*;
 import java.util.Iterator;
 import java.util.HashMap;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
 import fanx.emit.*;
 import fanx.fcode.*;
 import fanx.util.*;
@@ -148,15 +150,20 @@
 
   public String prompt(String msg)
   {
-    // attempt to initilize JLine and if we can't fallback to Java API
-    if (!jlineInit())
-    {
-      return System.console().readLine(msg);
-    }
-
-    // use reflection to call JLine ConsoleReader.readLine
     try
     {
+      // attempt to initilize JLine and if we can't fallback to Java API
+      if (!jlineInit())
+      {
+        if (System.console() != null)
+          return System.console().readLine(msg);
+
+        // revert to Std In when there is no console
+        out().print(msg).flush();
+        return new BufferedReader(new InputStreamReader(System.in)).readLine();
+      }
+
+      // use reflection to call JLine ConsoleReader.readLine
       return (String)jline.getClass()
         .getMethod("readLine", new Class[] { String.class })
         .invoke(jline, new Object[] { msg });
@@ -169,17 +176,24 @@
 
   public String promptPassword(String msg)
   {
-    // attempt to initilize JLine and if we can't fallback to Java API
-    if (!jlineInit())
-    {
-      char[] pass = System.console().readPassword(msg);
-      if (pass == null) return null;
-      return new String(pass);
-    }
-
-    // use reflection to call JLine ConsoleReader.readLine
     try
     {
+      // attempt to initilize JLine and if we can't fallback to Java API
+      if (!jlineInit())
+      {
+        if (System.console() != null)
+        {
+          char[] pass = System.console().readPassword(msg);
+          if (pass == null) return null;
+          return new String(pass);
+        }
+
+        // revert to Std In when there is no console
+        out().print(msg).flush();
+        return new BufferedReader(new InputStreamReader(System.in)).readLine();
+      }
+
+      // use reflection to call JLine ConsoleReader.readLine
       return (String)jline.getClass()
         .getMethod("readLine", new Class[] { String.class, Character.class })
         .invoke(jline, new Object[] { msg, Character.valueOf('#') });

Note that the patch doesn't interfere with JLine either.

brian Thu 21 Apr 2016

Thanks, I pushed a fix

Login or Signup to reply.