I want to have a Java program that can read a .CLASS file and run that code, using itself as the .CLASS file's library. Is this at all possible?
java.lang.ClassLoader
will help you to load external classes.
java.lang.reflect.Method
will help you to invoke methods of loaded external classes.
Tiny example:
ArrayList<URL> urls = new ArrayList<URL>();
urls.add(new File("/path/to/your.class").toURI().toURL()); //can add several..
ClassLoader cl = new URLClassLoader(urls.toArray(new URL[urls.size()]));
Class<?> c;
c = Class.forName("your.class.name", false, cl); //now you have your class
Method m = c.getMethod("main", String[].class); //now your have your method
m.invoke(null, new Object[] { "argument1", "argument2" }); //now you "run that code"
I did not run anything, i just wrote it to show you some tools that can help you.
Related
I have created a java FX app to decompile hundreds of class files in my project using procyon decompiler. As more and more files are processed, the memory usage of the app hits 1GB in a couple of minutes. I guess this has something to do with processing string and the objects created on the process not being garbage collected?
Here is a sample code to reproduce the issue.
File file1 = new File("file1.class");
File file2 = new File("file2.class");
ExecutorService pool = Executors.newFixedThreadPool(4);
DecompileUtils dcUtils = new DecompileUtils();
for(int i=0;i<500;i++){
Callable<Integer> task = () -> {
sourceCode1 = dcUtils.decompile(file1.getAbsolutePath());
sourceCode2 = dcUtils.decompile(file2.getAbsolutePath());
//do something with the result
return 1;
};
pool.submit(task);
The class containing the method to decompile the file :
public class DecompileUtils {
public String decompile(String source) throws IOException{
final DecompilerSettings settings = DecompilerSettings.javaDefaults();
PlainTextOutput pText = new PlainTextOutput();
Decompiler.decompile(source, pText, settings);
return pText.toString();
}
}
Edit : As I was going through the procyon source code, I've noticed that it was when creating the object of the class AstBuilder that the memory usage goes up abruptly. i.e when the buildAst method is called which resides in JavaLanguage.class
AstBuilder astBuilder = buildAst(type, options);
I have built a program, which takes in a provided ".class" file and parses it using the BCEL, I've learnt how to calculate the LCOM4 value now. Now I would like to know how to calculate the CBO(Coupling between object) value of the class file. I've scoured the whole web, trying to find a proper tutorial about it, but I've been unable so far (I've read the whole javadoc regarding the BCEL as well and there was a similar question on stackoverflow but it has been removed). So I would like some help with this issue, as in some detailed tutorials or code snippets that would help me understand on how to do it.
OK, here you must compute the CBO of the classes within a whole set of classes. The set can be the content of a directory, of a jar file, or all the classes in a classpath.
I would fill a Map<String,Set<String>> with the class name as the key, and the classes it refers to:
private void addClassReferees(File file, Map<String, Set<String>> refMap)
throws IOException {
try (InputStream in = new FileInputStream(file)) {
ClassParser parser = new ClassParser(in, file.getName());
JavaClass clazz = parser.parse();
String className = clazz.getClassName();
Set<String> referees = new HashSet<>();
ConstantPoolGen cp = new ConstantPoolGen(clazz.getConstantPool());
for (Method method: clazz.getMethods()) {
Code code = method.getCode();
InstructionList instrs = new InstructionList(code.getCode());
for (InstructionHandle ih: instrs) {
Instruction instr = ih.getInstruction();
if (instr instanceof FieldOrMethod) {
FieldOrMethod ref = (FieldInstruction)instr;
String cn = ref.getClassName(cp);
if (!cn.equals(className)) {
referees.add(cn);
}
}
}
}
refMap.put(className, referees);
}
}
When you've added all the classes in the map, you need to filter the referees of each class to limit them to the set of classes considered, and add the backward links:
Set<String> classes = new TreeSet<>(refMap.keySet());
for (String className: classes) {
Set<String> others = refMap.get(className);
others.retainAll(classes);
for (String other: others) {
refMap.get(other).add(className);
}
}
Runtime.getRuntime().exec("....")
and
ProcessBuilder pb = new ProcessBuilder("java", "-server", "-jar", "yourJar.jar");
Process p = pb.start();
The above 2 ways of executing a command create a new process for running the command.
Is there a way to execute the command in the same process, without creating a new one?
As #soong commented, you could manually load your JAR and the classes you need, and then call the main method by reflection. You can achieve this with something like this:
// load your JAR file as a File instance
String myJarPath = "C:\\somefolder\\someOtherFolder\\MyJar.jar";
File myJarFile = new File(myJarPath);
// create a new class loader based on your JAR's URL
URLClassLoader classLoader = new URLClassLoader(new URL[]{myJarFile.toURI().toURL()});
// load the class with the main method
Class<?> classToLoad = classLoader.loadClass("MyClass");
// get the main method
Method method = classToLoad.getMethod("main", String[].class);
// invoke it
String args[] = {"arg1", "arg2"}; // args to pass to the main method, it can be null
method.invoke(null, (Object) args); // first parameter is null because main is static
Maybe you could read classes into the Process with an ObjectInputStream
I have created a custom ClassLoader and want to load a class. I am using this code at the moment to load the class from the Jar:
ByteArrayInputStream byteIS = new ByteArrayInputStream(data);
JarInputStream jarIS = new JarInputStream(byteIS);
JarEntry je;
je = jarIS.getNextJarEntry();
byte[] classbytes = new byte[(int) je.getSize()];
jarIS.read(classbytes, 0, classbytes.length);
jarIS.close();
CustomClassLoader classLoader = new CustomClassLoader();
classLoader.setClassContent(classbytes);
Class c = classLoader.findClass("Main");
Object object = c.newInstance();
Method[] methods = object.getClass().getMethods();
Object returnValue = methods[0].invoke(null, new Object[]{new String[]{}});
In this sample above you can clearly see I am trying to load class Main. Now imagine that my friend also creates a Jar, I cannot know on beforehand what the name of the class is. How can I avoid the usage of a String as argument?
You might want to have a look at the ServiceLoader API. You can define a common interface for service implementations (classes) that you don't know a priori.
I have a code in Java that opens a excel template by aspose library (it runs perfectly):
import com.aspose.cells.*;
import java.io.*;
public class test
{
public static void main(String[] args) throws Exception
{
System.setProperty("java.awt.headless", "true");
FileInputStream fstream = new FileInputStream("/home/vmlellis/Testes/aspose-cells/template.xlsx");
Workbook workbook = new Workbook(fstream);
workbook.save("final.xlsx");
}
}
After I run this on Ruby with RJB (Ruby Java Bridge):
require 'rjb'
#RJM Loading
JARS = Dir.glob('./jars/*.jar').join(':')
print JARS
Rjb::load(JARS, ['-Xmx512M'])
system = Rjb::import('java.lang.System')
file_input = Rjb::import('java.io.File')
file_input_stream = Rjb::import('java.io.FileInputStream')
workbook = Rjb::import('com.aspose.cells.Workbook')
system.setProperty("java.awt.headless", "true")
file_path = "/home/vmlellis/Testes/aspose-cells/template.xlsx"
file = file_input.new(file_path)
fin = file_input_stream.new(file)
wb = workbook.new(fin)
I get this error:
test.rb:57:in `new': Can't find file: java.io.FileInputStream#693a317a. (FileNotFoundException)
from aspose-test.rb:57:in `<main>'
Why? I run the same code... but in Ruby is not working! How do I fix this?
Update:
In documentation there is the the initializer: Workbook(java.io.InputStreamstream)... but it's not working in RJB. (How is this possible?)
Your program should have worked, but I could not find any reason why it didn't and I am looking into it.
Now the alternate approaches.
Approach 1
Use Workbook(String) constructor instead of Workbook(FileInputStream). This worked flawlessly at my end. The sample code is
require 'rjb'
#RJM Loading
JARS = Dir.glob('/home/saqib/cellslib/*.jar').join(':')
print JARS
Rjb::load(JARS, ['-Xmx512M'])
system = Rjb::import('java.lang.System')
workbook = Rjb::import('com.aspose.cells.Workbook')
system.setProperty("java.awt.headless", "true")
file_path = "/home/saqib/rjb/template.xlsx"
save_path = "/home/saqib/rjb/final.xlsx"
wb = workbook.new(file_path)
wb.save(save_path)
Approach 2
Write a new Java class library. Write all your Aspose.Cells related code in it. Expose very simple and basic methods that needs to be called from Ruby (RJB).
Why?
It is easy to write program in native Java language. If you use RJB, you need to perform a lot of code conversions
It is easy to debug and test in Java.
Usage of RJB will only be limited to calling methods from your own Java library. The RJB code will be small and basic.
Similar Example using own library
Create a new Java project, lets say "cellstest". Add a new public class in it.
package cellstest;
import com.aspose.cells.Workbook;
public class AsposeCellsUtil
{
public String doSomeOpOnWorkbook(String inFile, String outFile)
{
String result = "";
try
{
// Load the workbook
Workbook wb = new Workbook(inFile);
// Do some operation with this workbook
// ..................
// Save the workbook
wb.save(outFile);
// everything ok.
result = "ok";
}
catch(Exception ex)
{
// Return the exception to calling program
result = ex.toString();
}
return result;
}
}
Like this, add as many methods as you like, for each operation.
Build the project and copy the "cellstest.jar" in same folder where you copied Aspose.Cells jar files. You can return a String from your methods and check the return value in Ruby program for success or error code. The Ruby program will now be like
require 'rjb'
#RJM Loading
JARS = Dir.glob('/home/saqib/cellslib/*.jar').join(':')
print JARS
Rjb::load(JARS, ['-Xmx512M'])
system = Rjb::import('java.lang.System')
AsposeCellsUtil = Rjb::import('cellstest.AsposeCellsUtil')
system.setProperty("java.awt.headless", "true")
file_path = "/home/saqib/rjb/template.xlsx"
save_path = "/home/saqib/rjb/final.xlsx"
# initialize instance
asposeCellsUtil = AsposeCellsUtil.new()
# call methods
result = asposeCellsUtil.doSomeOpOnWorkbook(file_path, save_path)
puts result
PS. I work for Aspose as Developer Evangelist.
In your Java code, you pass a file name string into FileInputStream() constructor:
FileInputStream fstream = new FileInputStream("/home/vmlellis/Testes/aspose-cells/template.xlsx");
In your Ruby code, you pass a file object:
file = file_input.new(file_path)
fin = file_input_stream.new(file)
Have you tried to do the same thing as in Java?
fin = file_input_stream.new(file_path)