Calling Java code snippet from JRuby? - java

How can I call a Java code snippet from JRuby code? My code snippet is really short, actually it's just a set of a few Java statements.

Explained here on how to call existing Java code from JRuby. The most basic usage:
require 'java'
java.lang.System.out.println("Hello, world!")
As a bit more complex example, if you want to import your arbitrary package (say, 'foo.bar.baz') from a JAR, you can do this:
require 'java'
require 'foobarbaz.jar'
def foo
Java::Foo
end
shiny_thingy = foo.bar.baz.Thingy.new("Shiny")
shiny_thingy.shine()
If you want to evaluate a string as if it was Java, you would need to compile it first; you can use the techniques in this question, but Java generally frowns on autogenerated code, and it is not trivial to do it. Or you can translate it into JRuby, calling Java classes as described above, and skip the compilation issue.
We might be able to help better if we knew what your snippet consisted of.
EDIT: Here is the adaptation of the linked code that will instantiate an arbitrary class. Be aware that it will create .class files, which is AFAIK inevitable when a compilation step is involved. The code assumes a subdirectory named tmp exists; adapt to your use case.
shiny_source = <<-EOF
package foo.bar.baz;
public class Shiny {
public Shiny() {
System.out.println("I'm shiny!");
}
}
EOF
require 'java'
java_import javax.tools.SimpleJavaFileObject
java_import java.net.URI
class JavaSourceFromString < SimpleJavaFileObject
def initialize(name, code)
uri = "string:///" + name.gsub('.', '/') + Kind::SOURCE.extension
super URI.create(uri), Kind::SOURCE
#code = code
end
def getCharContent(ignore_encoding_errors)
#code
end
end
java_import javax.tools.ToolProvider
java_import java.io.StringWriter
java_import java.net.URL
java_import java.net.URLClassLoader
compilation_path = java.nio.file.Paths.get('tmp').to_absolute_path.to_s
jc = ToolProvider.get_system_java_compiler
raise "Compiler unavailable" unless jc
jsfs = JavaSourceFromString.new('foo.bar.baz.Shiny', shiny_source)
file_objects = [jsfs]
ccl = java.lang.Thread.current_thread.get_context_class_loader
classpath = ccl.getURLs.to_a.join(java.io.File::pathSeparator)
options = ['-d', compilation_path, '-classpath', classpath]
output = StringWriter.new
success = jc.get_task(output, nil, nil, options, nil, file_objects).call
raise output unless success
url = URL.new("file:" + compilation_path + "/")
ucl = URLClassLoader.new_instance([url].to_java(URL))
shiny_class = ucl.load_class('foo.bar.baz.Shiny')
shiny_class.new_instance

Related

Kotlin replacement for javah

javah has been deprecated since JDK 8 and will be/has been removed in JDK 10, and according to JEP 313 and the deprecation text, javac with the -h flag should be used instead:
Warning: The javah tool is planned to be removed in the next major JDK release. The tool has been superseded by the '-h' option added to javac in JDK 8. Users are recommended to migrate to using the javac '-h' option; see the javac man page for more information.
The problem is, javah operates on compiled .class files, while javac operates on source files (i.e. .java files.)
javah works fine with Kotlin and external functions, since everything ends up compiled as Java bytecode, but since there aren't any Java source files when using Kotlin, I don't see any way javac -h could work.
Is there a javah replacement, or a workaround, for Kotlin?
I recommend gjavap.
In the future I will also implement an easier-to-use command line tool which provides similar functionality to javap.
There is currently no built-in way to do this. There is an open issue for this on the Kotlin issue tracker that was raised in November 2019, but as of now it has not been prioritized and doesn't have a target version.
The only way to produce the header with JDK 10+ is to use javac -h, which only works for Java source code, not Kotlin. I tested the method in How to solve missing javah in Java 10 – ugly way linked by Oo.oO, and it works as a workaround for now. The steps are:
Use javap to decompile the bytecode back into Java
Edit the decompiled Java to make it suitable for producing the header file (reducing the file to just the native method definitions).
Run the decompiled Java code through javac -h to produce the header
I'm thinking of writing a gradle script to do this (or hoping somebody else beats me to it!). If I manage to get it done, I'll update this post.
I've wrote a simple gradle task for generating JNI headers, its similar approach as the one posted by #Oo.oO but has a better integration with gradle and hence can be run on Windows as well as Unix based OS.
val generateJniHeaders by tasks.creating {
group = "build"
dependsOn(tasks.getByName("compileKotlinJvm"))
// For caching
inputs.dir("src/jvmMain/kotlin")
outputs.dir("src/jvmMain/generated/jni")
doLast {
val javaHome = Jvm.current().javaHome
val javap = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javap") }?.absolutePath ?: error("javap not found")
val javac = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javac") }?.absolutePath ?: error("javac not found")
val buildDir = file("build/classes/kotlin/jvm/main")
val tmpDir = file("build/tmp/jvmJni").apply { mkdirs() }
val bodyExtractingRegex = """^.+\Rpublic \w* ?class ([^\s]+).*\{\R((?s:.+))\}\R$""".toRegex()
val nativeMethodExtractingRegex = """.*\bnative\b.*""".toRegex()
buildDir.walkTopDown()
.filter { "META" !in it.absolutePath }
.forEach { file ->
if (!file.isFile) return#forEach
val output = ByteArrayOutputStream().use {
project.exec {
commandLine(javap, "-private", "-cp", buildDir.absolutePath, file.absolutePath)
standardOutput = it
}.assertNormalExitValue()
it.toString()
}
val (qualifiedName, methodInfo) = bodyExtractingRegex.find(output)?.destructured ?: return#forEach
val lastDot = qualifiedName.lastIndexOf('.')
val packageName = qualifiedName.substring(0, lastDot)
val className = qualifiedName.substring(lastDot+1, qualifiedName.length)
val nativeMethods =
nativeMethodExtractingRegex.findAll(methodInfo).mapNotNull { it.groups }.flatMap { it.asSequence().mapNotNull { group -> group?.value } }.toList()
if (nativeMethods.isEmpty()) return#forEach
val source = buildString {
appendln("package $packageName;")
appendln("public class $className {")
for (method in nativeMethods) {
if ("()" in method) appendln(method)
else {
val updatedMethod = StringBuilder(method).apply {
var count = 0
var i = 0
while (i < length) {
if (this[i] == ',' || this[i] == ')') insert(i, " arg${count++}".also { i += it.length + 1 })
else i++
}
}
appendln(updatedMethod)
}
}
appendln("}")
}
val outputFile = tmpDir.resolve(packageName.replace(".", "/")).apply { mkdirs() }.resolve("$className.java").apply { delete() }.apply { createNewFile() }
outputFile.writeText(source)
project.exec {
commandLine(javac, "-h", jniHeaderDirectory.absolutePath, outputFile.absolutePath)
}.assertNormalExitValue()
}
}
}
Until Kotlin starts generating JDK10-specific bytecode, you can use javah tool from JDK 9 or lower on the compiled kotlin classes.
And even after that you can compile external functions with jvmTarget=1.8 and use javah on the resulting classes.
You can also make it using javap and creating facade classes based on class files.
Take a look here for a sample: http://www.owsiak.org/how-to-solve-missing-javah-ugly-way/

How can I set up Soot when using it as a library?

I want to use Soot library to build an SSA from *.java file. But all the examples I found use Soot as standalone tool, not library. Can anyone give me example hot to do it in program?
For a start I am just trying to load my class from the source file and print it (TestClass.class is in the directory A/home/abdullin/workspace/test):
import soot.G
import soot.Scene
import soot.options.Options
fun main(args: Array<String>) {
G.reset();
Options.v().set_whole_program(true)
Scene.v().loadBasicClasses()
Scene.v().sootClassPath = "${Scene.v().defaultClassPath()}:/home/abdullin/workspace/test"
val sc = Scene.v().getSootClass("TestClass")
Scene.v().loadNecessaryClasses()
sc.setApplicationClass()
println(sc.name)
sc.methods.forEach {
println(it)
}
}
But when I run this, I get runtime exception Aborting: can't find classfile TestClass. If I change Scene.v().getSootClass("TestClass") to Scene.v().loadClassAndSupport("TestClass") as they do in some of their tutorials, soot finds my class, but it is not complete. It prints me signatures of class methods, but can't find their bodies, activeBody field is null.
TestClass
<TestClass: void <init>()>
<TestClass: void main(java.lang.String[])>
<TestClass: void f1()>
First, make sure that the Soot jar is in the classpath.
Then, set up Soot using the classes soot.G and soot.options.Options (G.reset() and Options.v().parse() are methods of interest, also see command line options).
Using soot.Scene.v().setSootClassPath() and similar you can tell Soot where to find the class files of the code you want to analyze.
You can then use Scene.v().getSootClass() to obtain SootClass objects. Make sure that Soot loads all classes after setting the class you want to analyze as main class:
mySootClass.setApplicationClass();
Scene.v().loadNecessaryClasses();
After this, you can use Soot to obtain various types of graphs and run you analyses, as described in the Survivor's guide
You can read this post (https://o2lab.github.io/710/p/a1.html). But if you try to analyze a jar file, you should unzip it and get a set of class files. Then you should add your classes directory into the soot_class_path.
Demo:
public static void main(String[] args) {
//spotbugs -- testing
String classesDir = "D:\\wkspace\\seed8\\dir\\spotbugs";
String mainClass = "edu.umd.cs.findbugs.LaunchAppropriateUI";
//set classpath
String jreDir = System.getProperty("java.home") + "\\lib\\jce.jar";
String jceDir = System.getProperty("java.home") + "\\lib\\rt.jar";
String path = jreDir + File.pathSeparator + jceDir + File.pathSeparator + classesDir;
Scene.v().setSootClassPath(path);
//add an intra-procedural analysis phase to Soot
TestCallGraphSootJar_3 analysis = new TestCallGraphSootJar_3();
PackManager.v().getPack("wjtp").add(new Transform("wjtp.TestSootCallGraph", analysis));
excludeJDKLibrary();
Options.v().set_process_dir(Arrays.asList(classesDir));
Options.v().set_whole_program(true);
//Options.v().set_app(true);
SootClass appClass = Scene.v().loadClassAndSupport(mainClass);
Scene.v().setMainClass(appClass);
Scene.v().loadNecessaryClasses();
//enableCHACallGraph();
enableSparkCallGraph();
PackManager.v().runPacks();
}
If you replace
SootClass appclass = Scene.v().loadClassAndSupport(mainclass);
Scene.v().setMainClass(appclass);
Scene.v().loadNecessaryClasses();
by
Scene.v().loadNecessaryClasses();
SootClass appclass = Scene.v().getSootClass(mainclass);
Scene.v().setMainClass(appclass);
, the program also works.

How to use Java Compile API to compile recursively? [duplicate]

I use the class javax.tools.JavaCompiler (jdk6) to compile a source file, but the source file depends on some jar file. How to set the classpath of the javax.tools.JavaCompiler?
The javax.tools.JavaCompiler#getTask() method takes an options parameter that allows to set compiler options. The following message describes an easy way to set them in order to access the calling program's classpath:
You need to configure the standard
java file manager to know about the
jar files(s) - you use the compiler
options argument to do that.
By default the java compiler object
only seems to know about the default
locations for bootclasspath, extdirs
and endorseddirs directories in terms
of its classpath.
You need to add the calling program's
current classpath to the java compiler
instance's which gets passed on the
the standard file manager, which will
then find classes in the jar files.
Here's how I do it in the compiler
wrapper I wrote
List<String> optionList = new ArrayList<String>();
// set compiler's classpath to be same as the runtime's
optionList.addAll(Arrays.asList("-classpath",System.getProperty("java.class.path")));
// any other options you want
optionList.addAll(Arrays.asList(options));
JavaCompiler.CompilationTask task = compiler.getTask(out,jfm,diagnostics,optionList,null,jfos);
All you'll need then is to get the proper classpath set when running the calling program.
The same problem occurred to me recently, finally I found two workarounds. You can set the class path either by invoke StandardJavaFileManager.setLocation(StandardLocation.CLASS_PATH, "YOUR_CLASS_PATH") or Compiler.getTask(ARG_0, ARG_1, ARG_2, CLASS_PATH_OPTIONS, just as the first answer posted here says.
I needed something simpler than the examples above.
The following is a self-contained example of using the built-in Java compiler, and setting the classpath for the compiler to use.
It is equivalent to creating a source file called HelloPrinter.java and then compiling it as follows:
javac -classpath C:\Users\dab\Testing\a.jar;c:\path\etc org\abc\another\HelloPrinter.java
Note how the classpath can be set using a String[] of options. This should be familiar if you're already used to running javac on the command line (as above).
This code is compatible with Java 6. You will need a JDK, not a JRE, for this to run. This example doesn't actually use the classpath. It all does is print "Hello". You can add an import statement to the generated source and call a method in an external Jar file to test this properly.
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class JavaCompilerExample {
public static void main(String[] args) throws Exception {
String className = "HelloPrinter";
String directoryName = "org/abc/another";
new File(directoryName).mkdirs();
FileOutputStream fos = new FileOutputStream(directoryName+"/"+className+".java");
PrintStream ps = new PrintStream(fos);
ps.println(
"package "+directoryName.replace("/", ".") + " ; "
+ "public class " +className +
"{ public static void main(String[] args){System.out.println(\"Hello\");} }");
ps.close();
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
String javacOpts[] = {"-classpath",
"C:\\Users\\dab\\Testing\\a.jar;c:\\path\\etc;",
directoryName+"/"+className + ".java"};
if ( javac.run(null, null, null, javacOpts)!=0 ) {
System.err.println("Error");
System.exit(1);
}
}
}

java_import already initialized constant

I just started using JRuby and I create a small test file:
require 'java'
java_import java.io.File
f = File.new ARGV[0]
When I run the program like so: jruby test.rb file.txt
I get the following warning:
/Library/Frameworks/JRuby.framework/Versions/1.6.5/lib/ruby/site_ruby/shared/builtin/javasupport/core_ext/object.rb:99 warning: already initialized constant File
The class of f is in fact the java File class, but I still get the warning, any help??
I found out this is related to the following JRuby ticket by looking in object.rb:
http://jira.codehaus.org/browse/JRUBY-3453
Seems like a reasonable warning to me, since Ruby already has a File class (i.e. the constant "File" was already initialized to refer to the Ruby File class).
Myself, I would probably skip the import and just do
require 'java'
f = java.io.File.new ARGV[0]
which should work and would eliminate name clashes.
You can also do
require 'java'
java_file = java.io.File
f = java_file.new ARGV[0]
or
module JavaIO
include_package "java.io"
end
f = JavaIO::File.new ARGV[0]

How to set classpath when I use javax.tools.JavaCompiler compile the source?

I use the class javax.tools.JavaCompiler (jdk6) to compile a source file, but the source file depends on some jar file. How to set the classpath of the javax.tools.JavaCompiler?
The javax.tools.JavaCompiler#getTask() method takes an options parameter that allows to set compiler options. The following message describes an easy way to set them in order to access the calling program's classpath:
You need to configure the standard
java file manager to know about the
jar files(s) - you use the compiler
options argument to do that.
By default the java compiler object
only seems to know about the default
locations for bootclasspath, extdirs
and endorseddirs directories in terms
of its classpath.
You need to add the calling program's
current classpath to the java compiler
instance's which gets passed on the
the standard file manager, which will
then find classes in the jar files.
Here's how I do it in the compiler
wrapper I wrote
List<String> optionList = new ArrayList<String>();
// set compiler's classpath to be same as the runtime's
optionList.addAll(Arrays.asList("-classpath",System.getProperty("java.class.path")));
// any other options you want
optionList.addAll(Arrays.asList(options));
JavaCompiler.CompilationTask task = compiler.getTask(out,jfm,diagnostics,optionList,null,jfos);
All you'll need then is to get the proper classpath set when running the calling program.
The same problem occurred to me recently, finally I found two workarounds. You can set the class path either by invoke StandardJavaFileManager.setLocation(StandardLocation.CLASS_PATH, "YOUR_CLASS_PATH") or Compiler.getTask(ARG_0, ARG_1, ARG_2, CLASS_PATH_OPTIONS, just as the first answer posted here says.
I needed something simpler than the examples above.
The following is a self-contained example of using the built-in Java compiler, and setting the classpath for the compiler to use.
It is equivalent to creating a source file called HelloPrinter.java and then compiling it as follows:
javac -classpath C:\Users\dab\Testing\a.jar;c:\path\etc org\abc\another\HelloPrinter.java
Note how the classpath can be set using a String[] of options. This should be familiar if you're already used to running javac on the command line (as above).
This code is compatible with Java 6. You will need a JDK, not a JRE, for this to run. This example doesn't actually use the classpath. It all does is print "Hello". You can add an import statement to the generated source and call a method in an external Jar file to test this properly.
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class JavaCompilerExample {
public static void main(String[] args) throws Exception {
String className = "HelloPrinter";
String directoryName = "org/abc/another";
new File(directoryName).mkdirs();
FileOutputStream fos = new FileOutputStream(directoryName+"/"+className+".java");
PrintStream ps = new PrintStream(fos);
ps.println(
"package "+directoryName.replace("/", ".") + " ; "
+ "public class " +className +
"{ public static void main(String[] args){System.out.println(\"Hello\");} }");
ps.close();
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
String javacOpts[] = {"-classpath",
"C:\\Users\\dab\\Testing\\a.jar;c:\\path\\etc;",
directoryName+"/"+className + ".java"};
if ( javac.run(null, null, null, javacOpts)!=0 ) {
System.err.println("Error");
System.exit(1);
}
}
}

Categories