JEXL-333: fixed namespace resolution on static only (private ctor) classes
Task #JEXL-333 - Allow declaration of namespace within script
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
index 78e274c..e54985e 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -40,7 +40,7 @@
  * @since 3.2
  */
 public final class JexlOptions {
-    /** The shared isntance bit. */
+    /** The shared instance bit. */
     private static final int SHARED = 7;
     /** The local shade bit. */
     private static final int SHADE = 6;
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index ec8484f..ec2e58b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -398,11 +398,15 @@
                         // jexl.namespace.***
                         String nsname = key.substring(PRAGMA_JEXLNS.length());
                         if (nsname != null && !nsname.isEmpty()) {
-                            String nsclass = value.toString();
                             if (ns == null) {
                                 ns = new HashMap<>(functions);
                             }
-                            ns.put(nsname, nsclass);
+                            String nsclass = value.toString();
+                            try {
+                                ns.put(nsname, uberspect.getClassLoader().loadClass(nsclass));
+                            } catch (ClassNotFoundException e) {
+                                ns.put(nsname, nsclass);
+                            }
                         }
                     }
                 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index b56245a..557ca19 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -200,6 +200,7 @@
                     }
                 }
                 if (functor == null) {
+                    // find a ctor with that context class
                     JexlMethod ctor = uberspect.getConstructor(namespace, context);
                     if (ctor != null) {
                         try {
@@ -211,13 +212,38 @@
                             throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
                         }
                     }
+                    // find a ctor with no arg
+                    if (functor == null) {
+                        ctor = uberspect.getConstructor(namespace);
+                        if (ctor != null) {
+                            try {
+                                functor = ctor.invoke(namespace);
+                            } catch (Exception xinst) {
+                                throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
+                            }
+                        }
+                        // use a class, namespace of static methods
+                        if (functor == null) {
+                            // try to find a class with that name
+                            if (namespace instanceof String) {
+                                try {
+                                    functor = uberspect.getClassLoader().loadClass((String) namespace);
+                                } catch (ClassNotFoundException xignore) {
+                                    // not a class
+                                    namespace = null;
+                                }
+                            } else { // we know its a class
+                                functor = (Class<?>) namespace;
+                            }
+                        }
+                    }
                 }
             }
             // got a functor, store it and return it
             if (functor != null) {
                 synchronized (this) {
                     if (functors == null) {
-                        functors = new HashMap<String, Object>();
+                        functors = new HashMap<>();
                     }
                     functors.put(prefix, functor);
                 }
diff --git a/src/test/java/org/apache/commons/jexl3/PragmaTest.java b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
index 713acf7..03a8bd2 100644
--- a/src/test/java/org/apache/commons/jexl3/PragmaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.jexl3;
 
+import java.util.Collections;
 import java.util.Map;
 import org.junit.Assert;
 import org.junit.Test;
@@ -77,6 +78,14 @@
                 }
             }
         }
+
+        public void sleep(long ms) {
+            try {
+                Thread.sleep(ms);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
     }
 
     @Test
@@ -98,4 +107,79 @@
             // ok, expected
         }
     }
+
+        
+    public static class StaticSleeper {
+        // precludes instantiation
+        private StaticSleeper() {}
+        
+        public static void sleep(long ms) {
+            try {
+                Thread.sleep(ms);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+    }
+    
+    public static class Sleeper {
+        public void sleep(long ms) {
+            try {
+                Thread.sleep(ms);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+    }
+
+    @Test
+    @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
+    public void testStaticNamespacePragma() throws Exception {
+        SafeContext jc = new SafeContext();
+        JexlScript script = JEXL.createScript(
+                "#pragma jexl.namespace.sleeper " + StaticSleeper.class.getName() + "\n"
+                + "sleeper:sleep(100);"
+                + "42");
+        Object result = script.execute(jc);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
+    public void testStatictNamespacePragmaCtl() throws Exception {
+        Map<String, Object> ns = Collections.singletonMap("sleeper", StaticSleeper.class.getName());
+        JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
+        SafeContext jc = new SafeContext();
+        JexlScript script = jexl.createScript(
+                "sleeper:sleep(100);"
+                + "42");
+        Object result = script.execute(jc);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
+    public void testNamespacePragma() throws Exception {
+        SafeContext jc = new SafeContext();
+        JexlScript script = JEXL.createScript(
+                "#pragma jexl.namespace.sleeper " + Sleeper.class.getName() + "\n"
+                + "sleeper:sleep(100);"
+                + "42");
+        Object result = script.execute(jc);
+        Assert.assertEquals(42, result);
+    }
+
+    @Test
+    @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
+    public void testNamespacePragmaCtl() throws Exception {
+        Map<String, Object> ns = Collections.singletonMap("sleeper", Sleeper.class.getName());
+        JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
+        SafeContext jc = new SafeContext();
+        JexlScript script = jexl.createScript(
+                "sleeper:sleep(100);"
+                + "42");
+        Object result = script.execute(jc);
+        Assert.assertEquals(42, result);
+    }
+
 }