If you want to declare a class in Zeppelin and create instance of it, you might be surprised:
1 |
class K |
1 |
defined class K |
1 |
classOf[K].newInstance() |
1 2 3 4 5 6 7 |
java.lang.InstantiationException: K at java.lang.Class.newInstance(Class.java:427) ... 52 elided Caused by: java.lang.NoSuchMethodException: K.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.newInstance(Class.java:412) ... 52 more |
What happened? Whatever you declare in Zeppelin notebook is a part of some internal class so the newly declared class K
doesn’t have parameterless constructor because it expects the instance of enclosing type. There are two simple ways to handle this.
Quasiquotes
Just generate a class with scala’s quaisquotes:
1 2 3 4 5 6 |
import reflect.runtime._ import universe._ import tools.reflect.ToolBox val tb = currentMirro.mkToolBox() val l = tb.compile(q"""class L; classOf[L].newInstance()""")() |
1 |
l: Any = __wrapper$8$bb3239e978f24dc98e740075eecad313.__wrapper$8$bb3239e978f24dc98e740075eeacad313$L$1@7e2b9e13 |
Javax.tools
Use the following method to dynamically compile java code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
def generateClass(className: String, source: String): (Class[_], Any) = { val byteArrayOutputStream = new java.io.ByteArrayOutputStream() val simpleJavaFileObject = new javax.tools.SimpleJavaFileObject(java.net.URI.create(s"$className.java"), javax.tools.JavaFileObject.Kind.SOURCE) { override def getCharContent(ignoreEncodingErrors: Boolean):CharSequence = { return source; } override def openOutputStream(): java.io.OutputStream = { return byteArrayOutputStream; } }; val standardManager = javax.tools.ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null); val customForwardingManager = new javax.tools.JavaFileManager { override def close() = standardManager.close() override def flush() = standardManager.flush() override def getClassLoader(location: javax.tools.JavaFileManager.Location) = standardManager.getClassLoader(location) override def getFileForInput(location: javax.tools.JavaFileManager.Location, packageName: String, relativeName: String) = standardManager.getFileForInput(location, packageName, relativeName) override def getFileForOutput(location: javax.tools.JavaFileManager.Location, packageName: String, relativeName: String, sibling: javax.tools.FileObject) = standardManager.getFileForOutput(location, packageName, relativeName, sibling) override def getJavaFileForInput(location: javax.tools.JavaFileManager.Location, className: String, kind: javax.tools.JavaFileObject.Kind) = standardManager.getJavaFileForInput(location, className, kind) override def getJavaFileForOutput(location: javax.tools.JavaFileManager.Location, className: String, kind: javax.tools.JavaFileObject.Kind, sibling: javax.tools.FileObject): javax.tools.JavaFileObject = { return simpleJavaFileObject; } override def handleOption(current: String, remaining: java.util.Iterator[String]) = standardManager.handleOption(current, remaining) override def hasLocation(location: javax.tools.JavaFileManager.Location) = standardManager.hasLocation(location) override def inferBinaryName(location: javax.tools.JavaFileManager.Location, file: javax.tools.JavaFileObject) = standardManager.inferBinaryName(location, file) override def isSameFile(a: javax.tools.FileObject, b: javax.tools.FileObject) = standardManager.isSameFile(a, b) override def isSupportedOption(option: String) = standardManager.isSupportedOption(option) override def list(location: javax.tools.JavaFileManager.Location, packageName: String, kinds: java.util.Set[javax.tools.JavaFileObject.Kind], recurse: Boolean) = standardManager.list(location, packageName, kinds, recurse) } val list = new java.util.ArrayList[javax.tools.JavaFileObject]() list.add(simpleJavaFileObject) javax.tools.ToolProvider.getSystemJavaCompiler().getTask(null, customForwardingManager, null, null, null, list).call(); val bytes = byteArrayOutputStream.toByteArray(); val f = classOf[sun.misc.Unsafe].getDeclaredField("theUnsafe"); f.setAccessible(true); val unsafe: sun.misc.Unsafe = f.get(null).asInstanceOf[sun.misc.Unsafe]; val aClass = unsafe.defineClass(className, bytes, 0, bytes.length, null, null); val o = aClass.newInstance(); (aClass, o) } |
Invoke it like this:
1 2 3 4 5 |
val (kClass, kInstance) = generateClass("K", """ public class K{ public K(){} } """) |
1 2 |
kClass: Class[_] = class K kInstance: Any = K@adfd330 |
And you are done.