clear the class loaded through class.forName in java - java

I have created program which loads a java(JPanel) file which user chooses.User basically chooses a Java file which gets compiled by JavaCompiler and next generated class file is loaded.
But problem is coming when any changes are done in the java file(JPanel) through some text editor ,since any new changes are not reflected in the class file even after closing the program and re-running the project.
I think same class file is loaded again and again from memory.
is there any way to clear the loaded class from memory?
Compilation:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler != null) {
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(diagnostics, null, null);
Iterable<? extends JavaFileObject> fileObjects = stdFileManager.getJavaFileObjectsFromFiles(filesToCompile);
List<String> optionList = new ArrayList<String>();
// set compiler's classpath to be same as the runtime's
rootDir=Utility.createRootDir();
optionList.addAll(Arrays.asList("-d", rootDir.getAbsolutePath(), "-classpath", System.getProperty("java.class.path")));
// optionList.add(()
try {
stdFileManager.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
CompilationTask task = compiler.getTask(null, stdFileManager,null, optionList, null, fileObjects);
Boolean result = task.call();
try {
stdFileManager.flush();
stdFileManager.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Loading:
loader = new URLClassLoader(new URL[] { rootDir.toURI().toURL() });
cls = Class.forName(Utility.extractFQDN(sourceFile)+"."+Utility.extractClassName(sourceFile),true, loader);
panel= (JPanel) cls.newInstance();
I have checked the compiled class file with decompiler it has updated code but I don't know why previous class file is being loaded from memory by class loader.

Edit:
Here's an SSCCE compiling strings repeatedly to the same class name and demonstrating new behavior. To avoid the whole mess with files it does everything in memory. I think this should be easily adaptable to your application.
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
public class Compile {
static class OutFile extends SimpleJavaFileObject {
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
OutFile(String name) {
super(URI.create("memory:///" + name.replace('.','/') + Kind.CLASS.extension), Kind.CLASS);
}
#Override
public OutputStream openOutputStream() throws IOException {
return out;
}
}
static class InFile extends SimpleJavaFileObject {
final String code;
InFile(String name, String code) {
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
static class Loader extends ClassLoader {
private final Map<String, OutFile> files = new HashMap<String, OutFile>();
public OutFile add(String className) {
OutFile file = new OutFile(className);
files.put(className, file);
return file;
}
#Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if(c == null) {
OutFile file = files.get(name);
if(file == null) {
return super.loadClass(name, resolve);
}
c = defineClass(name, file.out.toByteArray(), 0, file.out.size());
}
if(resolve) {
resolveClass(c);
}
return c;
}
}
static class FileManager extends ForwardingJavaFileManager<JavaFileManager> {
private final Loader loader = new Loader();
protected FileManager(JavaFileManager fileManager) {
super(fileManager);
}
public Class<?> getClass(String name) throws ClassNotFoundException {
return loader.loadClass(name);
}
#Override
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
return loader.add(className);
}
}
public static void compileAndRun(String source) throws Exception {
InFile in = new InFile("Main", "class Main {\npublic static void main(String[] args) {\n" + source + "\n}\n}");
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
FileManager manager = new FileManager(compiler.getStandardFileManager(null, null, null));
compiler.getTask(null, manager, null, null, null, Collections.singletonList(in)).call();
Method method = manager.getClass("Main").getMethod("main", String[].class);
method.setAccessible(true);
method.invoke(null, (Object)new String[0]);
}
public static void main(String[] args) throws Exception {
compileAndRun("System.out.println(\"Hello\");");
compileAndRun("System.out.println(\"World\");");
}
}
Original:
ClassLoader (and subclasses like URLClassLoader) will always ask the parent class loader to load the class, if there is a parent. If you don't explicitly set a parent when constructing it, the parent is set to the system class loader. So, all the new class loaders you create are deferring back to the system class loader, which already has the class defined.
To get the behavior that you want, set the parent to null:
loader = new URLClassLoader(new URL[] { rootDir.toURI().toURL() }, null);
Edit: Note that is only a problem because you are compiling your classes to the root directory, which is also on the classpath of the system class loader. If you compiled to some temp directory, the system class loader would not be able to find the class, and the URL loader would load the class itself.

Related

Compile a java source file containing multiple classes programmatically using Java SDK

I have a java source file containing two classes:
package com.example;
public class Test {
public void sayHello() {
String hello = new HelloProducer().getHello();
System.out.println(hello);
}
public static void main(String[] args) {
new Test().sayHello();
}
}
class HelloProducer {
public String getHello() {
return "hello";
}
}
I want to compile this java source file programmatically using Java SDK, here is what I've tried.
package com.example;
import java.io.IOException;
import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
public class Compiler {
public static void main(String[] args) throws IOException {
byte[] classBytes = compile();
// how to write classBytes to two class files: HelloProducer.class and Test.class?
}
public static byte[] compile() throws IOException {
String javaSource = "package com.example;\n" +
"\n" +
"public class Test {\n" +
" public void sayHello() {\n" +
" String hello = new HelloProducer().getHello();\n" +
" System.out.println(hello);\n" +
" }\n" +
" public static void main(String[] args) {\n" +
" new Test().sayHello();\n" +
" }\n" +
"}\n" +
"class HelloProducer {\n" +
" public String getHello() {\n" +
" return \"hello\";\n" +
" }\n" +
"}\n";
GeneratedClassFile gcf = new GeneratedClassFile();
DiagnosticCollector<JavaFileObject> dc = new DiagnosticCollector<>();
GeneratedJavaSourceFile gjsf = new GeneratedJavaSourceFile("Test.java", javaSource);
JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
GeneratingJavaFileManager fileManager = new GeneratingJavaFileManager(
jc.getStandardFileManager(dc, null, null),
gcf
);
JavaCompiler.CompilationTask task = jc.getTask(null, fileManager, dc,
new ArrayList<>(), null,
Collections.singletonList(gjsf));
boolean success = task.call();
fileManager.close();
return gcf.getClassAsBytes();
}
}
class GeneratingJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private final GeneratedClassFile gcf;
public GeneratingJavaFileManager(
StandardJavaFileManager sjfm,
GeneratedClassFile gcf) {
super(sjfm);
this.gcf = gcf;
}
public JavaFileObject getJavaFileForOutput(
Location location, String className,
JavaFileObject.Kind kind, FileObject sibling) {
return gcf;
}
}
class GeneratedClassFile extends SimpleJavaFileObject {
private final ByteArrayOutputStream outputStream =new ByteArrayOutputStream();
public GeneratedClassFile() {
super(URI.create("generated.class"), Kind.CLASS);
}
public OutputStream openOutputStream() {
return outputStream;
}
public byte[] getClassAsBytes() {
return outputStream.toByteArray();
}
}
class GeneratedJavaSourceFile extends SimpleJavaFileObject {
private CharSequence javaSource;
public GeneratedJavaSourceFile(String fileName,
CharSequence javaSource) {
super(URI.create(fileName), Kind.SOURCE);
this.javaSource = javaSource;
}
public CharSequence getCharContent(boolean ignoreEncodeErrors) {
return javaSource;
}
}
It worked. I successfully compiled this java source code and got a byte array classBytes. But here comes the problem: When you compile Test.java using javac, you will get two class files: Test.class and HelloProducer.class, but how now I only have a byte array classBytes, how can I write the byte array into two files(Test.class and HelloProducer.class) correctly just like javac?
Finally, I figured out a way to do this: just use File instead of OutputStream when generating classes, and JavaCompiler will create two class files for you by default. Here is a sample:
package com.example;
import java.io.File;
import java.io.IOException;
import javax.tools.*;
import java.util.Collections;
public class Compiler {
public static void main(String[] args) throws IOException {
// source file
String[] filesToCompile = { "/path/to/input/Test.java" };
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
// output path
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(new File("/tmp")));
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(filesToCompile);
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null,null, null, compilationUnits);
boolean success = task.call();
System.out.println(success);
}
}
Run it, then you will find two classes in /tmp/com/example: HelloProducer.class and Test.class, both of which are defined in Test.java

Compile Java code in-memory [duplicate]

This question already has answers here:
Compile code fully in memory with javax.tools.JavaCompiler [duplicate]
(7 answers)
Closed 6 years ago.
I want to treat a String as a Java file then compile and run it. In other words, use Java as a script language.
To get better performance, we should avoid writing .class files to disk.
This answer is from one of my blogs, Compile and Run Java Source Code in Memory.
Here are the three source code files.
MemoryJavaCompiler.java
package me.soulmachine.compiler;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.tools.*;
/**
* Simple interface to Java compiler using JSR 199 Compiler API.
*/
public class MemoryJavaCompiler {
private javax.tools.JavaCompiler tool;
private StandardJavaFileManager stdManager;
public MemoryJavaCompiler() {
tool = ToolProvider.getSystemJavaCompiler();
if (tool == null) {
throw new RuntimeException("Could not get Java compiler. Please, ensure that JDK is used instead of JRE.");
}
stdManager = tool.getStandardFileManager(null, null, null);
}
/**
* Compile a single static method.
*/
public Method compileStaticMethod(final String methodName, final String className,
final String source)
throws ClassNotFoundException {
final Map<String, byte[]> classBytes = compile(className + ".java", source);
final MemoryClassLoader classLoader = new MemoryClassLoader(classBytes);
final Class clazz = classLoader.loadClass(className);
final Method[] methods = clazz.getDeclaredMethods();
for (final Method method : methods) {
if (method.getName().equals(methodName)) {
if (!method.isAccessible()) method.setAccessible(true);
return method;
}
}
throw new NoSuchMethodError(methodName);
}
public Map<String, byte[]> compile(String fileName, String source) {
return compile(fileName, source, new PrintWriter(System.err), null, null);
}
/**
* compile given String source and return bytecodes as a Map.
*
* #param fileName source fileName to be used for error messages etc.
* #param source Java source as String
* #param err error writer where diagnostic messages are written
* #param sourcePath location of additional .java source files
* #param classPath location of additional .class files
*/
private Map<String, byte[]> compile(String fileName, String source,
Writer err, String sourcePath, String classPath) {
// to collect errors, warnings etc.
DiagnosticCollector<JavaFileObject> diagnostics =
new DiagnosticCollector<JavaFileObject>();
// create a new memory JavaFileManager
MemoryJavaFileManager fileManager = new MemoryJavaFileManager(stdManager);
// prepare the compilation unit
List<JavaFileObject> compUnits = new ArrayList<JavaFileObject>(1);
compUnits.add(fileManager.makeStringSource(fileName, source));
return compile(compUnits, fileManager, err, sourcePath, classPath);
}
private Map<String, byte[]> compile(final List<JavaFileObject> compUnits,
final MemoryJavaFileManager fileManager,
Writer err, String sourcePath, String classPath) {
// to collect errors, warnings etc.
DiagnosticCollector<JavaFileObject> diagnostics =
new DiagnosticCollector<JavaFileObject>();
// javac options
List<String> options = new ArrayList<String>();
options.add("-Xlint:all");
// options.add("-g:none");
options.add("-deprecation");
if (sourcePath != null) {
options.add("-sourcepath");
options.add(sourcePath);
}
if (classPath != null) {
options.add("-classpath");
options.add(classPath);
}
// create a compilation task
javax.tools.JavaCompiler.CompilationTask task =
tool.getTask(err, fileManager, diagnostics,
options, null, compUnits);
if (task.call() == false) {
PrintWriter perr = new PrintWriter(err);
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
perr.println(diagnostic);
}
perr.flush();
return null;
}
Map<String, byte[]> classBytes = fileManager.getClassBytes();
try {
fileManager.close();
} catch (IOException exp) {
}
return classBytes;
}
}
MemoryJavaFileManager.java
package me.soulmachine.compiler;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
/**
* JavaFileManager that keeps compiled .class bytes in memory.
*/
#SuppressWarnings("unchecked")
final class MemoryJavaFileManager extends ForwardingJavaFileManager {
/** Java source file extension. */
private final static String EXT = ".java";
private Map<String, byte[]> classBytes;
public MemoryJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
classBytes = new HashMap<>();
}
public Map<String, byte[]> getClassBytes() {
return classBytes;
}
public void close() throws IOException {
classBytes = null;
}
public void flush() throws IOException {
}
/**
* A file object used to represent Java source coming from a string.
*/
private static class StringInputBuffer extends SimpleJavaFileObject {
final String code;
StringInputBuffer(String fileName, String code) {
super(toURI(fileName), Kind.SOURCE);
this.code = code;
}
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
}
/**
* A file object that stores Java bytecode into the classBytes map.
*/
private class ClassOutputBuffer extends SimpleJavaFileObject {
private String name;
ClassOutputBuffer(String name) {
super(toURI(name), Kind.CLASS);
this.name = name;
}
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream)out;
classBytes.put(name, bos.toByteArray());
}
};
}
}
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
String className,
Kind kind,
FileObject sibling) throws IOException {
if (kind == Kind.CLASS) {
return new ClassOutputBuffer(className);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
static JavaFileObject makeStringSource(String fileName, String code) {
return new StringInputBuffer(fileName, code);
}
static URI toURI(String name) {
File file = new File(name);
if (file.exists()) {
return file.toURI();
} else {
try {
final StringBuilder newUri = new StringBuilder();
newUri.append("mfm:///");
newUri.append(name.replace('.', '/'));
if(name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
return URI.create(newUri.toString());
} catch (Exception exp) {
return URI.create("mfm:///com/sun/script/java/java_source");
}
}
}
}
MemoryClassLoader.java
package me.soulmachine.compiler;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* ClassLoader that loads .class bytes from memory.
*/
final class MemoryClassLoader extends URLClassLoader {
private Map<String, byte[]> classBytes;
public MemoryClassLoader(Map<String, byte[]> classBytes,
String classPath, ClassLoader parent) {
super(toURLs(classPath), parent);
this.classBytes = classBytes;
}
public MemoryClassLoader(Map<String, byte[]> classBytes, String classPath) {
this(classBytes, classPath, ClassLoader.getSystemClassLoader());
}
public MemoryClassLoader(Map<String, byte[]> classBytes) {
this(classBytes, null, ClassLoader.getSystemClassLoader());
}
public Class load(String className) throws ClassNotFoundException {
return loadClass(className);
}
public Iterable<Class> loadAll() throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>(classBytes.size());
for (String name : classBytes.keySet()) {
classes.add(loadClass(name));
}
return classes;
}
protected Class findClass(String className) throws ClassNotFoundException {
byte[] buf = classBytes.get(className);
if (buf != null) {
// clear the bytes in map -- we don't need it anymore
classBytes.put(className, null);
return defineClass(className, buf, 0, buf.length);
} else {
return super.findClass(className);
}
}
private static URL[] toURLs(String classPath) {
if (classPath == null) {
return new URL[0];
}
List<URL> list = new ArrayList<URL>();
StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator);
while (st.hasMoreTokens()) {
String token = st.nextToken();
File file = new File(token);
if (file.exists()) {
try {
list.add(file.toURI().toURL());
} catch (MalformedURLException mue) {}
} else {
try {
list.add(new URL(token));
} catch (MalformedURLException mue) {}
}
}
URL[] res = new URL[list.size()];
list.toArray(res);
return res;
}
}
Explanations:
In order to represent a Java source file in memory instead of disk, I defined a StringInputBuffer class in the MemoryJavaFileManager.java.
To save the compiled .class files in memory, I implemented a class MemoryJavaFileManager. The main idea is to override the function getJavaFileForOutput() to store bytecodes into a map.
To load the bytecodes in memory, I have to implement a customized classloader MemoryClassLoader, which reads bytecodes in the map and turn them into classes.
Here is a unite test.
package me.soulmachine.compiler;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
public class MemoryJavaCompilerTest {
private final static MemoryJavaCompiler compiler = new MemoryJavaCompiler();
#Test public void compileStaticMethodTest()
throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
final String source = "public final class Solution {\n"
+ "public static String greeting(String name) {\n"
+ "\treturn \"Hello \" + name;\n" + "}\n}\n";
final Method greeting = compiler.compileStaticMethod("greeting", "Solution", source);
final Object result = greeting.invoke(null, "soulmachine");
assertEquals("Hello soulmachine", result.toString());
}
}
Reference
JavaCompiler.java from Cloudera Morphlines
How to create an object from a string in Java (how to eval a string)?
InMemoryJavaCompiler
Java-Runtime-Compiler
动态的Java - 无废话JavaCompilerAPI中文指南

Parse Jar file and find relationships between classes?

How to detect whether the class from the jar file is extending other class or if there are method calls to other class objects or other class objects are created ?
and then system out which class extend which class and which class called methods from which class .
Im using Classparser to parser the jar . here is part of my code :
String jarfile = "C:\\Users\\OOOO\\Desktop\\Sample.Jar";
jar = new JarFile(jarfile);
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.getName().endsWith(".class")) {
continue;
}
ClassParser parser = new ClassParser(jarfile, entry.getName());
JavaClass javaClass = parser.parse();
Someone voted to close this question as "too broad". I'm not sure whether this is the appropriate close reason here, but it might be, because one could consider this question (which is a follow up to your previous question) as just asking others to do some work for you.
However, to answer the basic question of how to detect references between classes in a single JAR file with BCEL:
You can obtain the list of JavaClass objects from the JarFile. For each of these JavaClass objects, you can inspect the Method objects and their InstructionList. Out of these instructions, you can select the InvokeInstruction objects and examine them further to find out which method on which class is actually invoked there.
The following program opens a JAR file (for obvious reasons, it's the bcel-5.2.jar - you'll need it anyhow...) and processes it in the way described above. For each JavaClass of the JAR file, it creates a map from all referenced JavaClass objects to the list of the Methods that are invoked on these classes, and prints the information accordingly:
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;
public class BCELRelationships
{
public static void main(String[] args) throws Exception
{
JarFile jarFile = null;
try
{
String jarName = "bcel-5.2.jar";
jarFile = new JarFile(jarName);
findReferences(jarName, jarFile);
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
if (jarFile != null)
{
try
{
jarFile.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
private static void findReferences(String jarName, JarFile jarFile)
throws ClassFormatException, IOException, ClassNotFoundException
{
Map<String, JavaClass> javaClasses =
collectJavaClasses(jarName, jarFile);
for (JavaClass javaClass : javaClasses.values())
{
System.out.println("Class "+javaClass.getClassName());
Map<JavaClass, Set<Method>> references =
computeReferences(javaClass, javaClasses);
for (Entry<JavaClass, Set<Method>> entry : references.entrySet())
{
JavaClass referencedJavaClass = entry.getKey();
Set<Method> methods = entry.getValue();
System.out.println(
" is referencing class "+
referencedJavaClass.getClassName()+" by calling");
for (Method method : methods)
{
System.out.println(
" "+method.getName()+" with arguments "+
Arrays.toString(method.getArgumentTypes()));
}
}
}
}
private static Map<String, JavaClass> collectJavaClasses(
String jarName, JarFile jarFile)
throws ClassFormatException, IOException
{
Map<String, JavaClass> javaClasses =
new LinkedHashMap<String, JavaClass>();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements())
{
JarEntry entry = entries.nextElement();
if (!entry.getName().endsWith(".class"))
{
continue;
}
ClassParser parser =
new ClassParser(jarName, entry.getName());
JavaClass javaClass = parser.parse();
javaClasses.put(javaClass.getClassName(), javaClass);
}
return javaClasses;
}
public static Map<JavaClass, Set<Method>> computeReferences(
JavaClass javaClass, Map<String, JavaClass> knownJavaClasses)
throws ClassNotFoundException
{
Map<JavaClass, Set<Method>> references =
new LinkedHashMap<JavaClass, Set<Method>>();
ConstantPool cp = javaClass.getConstantPool();
ConstantPoolGen cpg = new ConstantPoolGen(cp);
for (Method m : javaClass.getMethods())
{
String fullClassName = javaClass.getClassName();
String className =
fullClassName.substring(0, fullClassName.length()-6);
MethodGen mg = new MethodGen(m, className, cpg);
InstructionList il = mg.getInstructionList();
if (il == null)
{
continue;
}
InstructionHandle[] ihs = il.getInstructionHandles();
for(int i=0; i < ihs.length; i++)
{
InstructionHandle ih = ihs[i];
Instruction instruction = ih.getInstruction();
if (!(instruction instanceof InvokeInstruction))
{
continue;
}
InvokeInstruction ii = (InvokeInstruction)instruction;
ReferenceType referenceType = ii.getReferenceType(cpg);
if (!(referenceType instanceof ObjectType))
{
continue;
}
ObjectType objectType = (ObjectType)referenceType;
String referencedClassName = objectType.getClassName();
JavaClass referencedJavaClass =
knownJavaClasses.get(referencedClassName);
if (referencedJavaClass == null)
{
continue;
}
String methodName = ii.getMethodName(cpg);
Type[] argumentTypes = ii.getArgumentTypes(cpg);
Method method =
findMethod(referencedJavaClass, methodName, argumentTypes);
Set<Method> methods = references.get(referencedJavaClass);
if (methods == null)
{
methods = new LinkedHashSet<Method>();
references.put(referencedJavaClass, methods);
}
methods.add(method);
}
}
return references;
}
private static Method findMethod(
JavaClass javaClass, String methodName, Type argumentTypes[])
throws ClassNotFoundException
{
for (Method method : javaClass.getMethods())
{
if (method.getName().equals(methodName))
{
if (Arrays.equals(argumentTypes, method.getArgumentTypes()))
{
return method;
}
}
}
for (JavaClass superClass : javaClass.getSuperClasses())
{
Method method = findMethod(superClass, methodName, argumentTypes);
if (method != null)
{
return method;
}
}
return null;
}
}
Note, however, that this information might not be complete in every sense. For example, due to polymorphism, you might not always detect that a method is called on an object of a certain class, because it is "hidden" behind the polymorphic abstraction. For example, in a code snippet like
void call() {
MyClass m = new MyClass();
callToString(m);
}
void callToString(Object object) {
object.toString();
}
the call to toString actually happens on an instance of MyClass. But due to polymorphism, it can only be recognized as a call to this method on "some Object".
Disclaimer: This is, strictly speaking, not an answer to your question because it uses not BCEL but Javassist. Nevertheless you may find my experiences and code useful.
Few years ago I've written e Maven plugin (I called it Storyteller Maven Plugin) for this very purpose - to analyse JARs files for dependencies which are unnecessary or nor required.
Please see this question:
How to find unneccesary dependencies in a maven multi-project?
And my answer to it.
Although the plugin worked I have never released it back then. Now I've moved it to GitHub just to make it accessible for others.
You ask about parsing a JAR to analyze the code in .class files. Below are a couple of Javassist code snippets.
Search a JAR file for classes and create a CtClass per entry:
final JarFile artifactJarFile = new JarFile(artifactFile);
final Enumeration<JarEntry> jarEntries = artifactJarFile
.entries();
while (jarEntries.hasMoreElements()) {
final JarEntry jarEntry = jarEntries.nextElement();
if (jarEntry.getName().endsWith(".class")) {
InputStream is = null;
CtClass ctClass = null;
try {
is = artifactJarFile.getInputStream(jarEntry);
ctClass = classPool.makeClass(is);
} catch (IOException ioex1) {
throw new MojoExecutionException(
"Could not load class from JAR entry ["
+ artifactFile.getAbsolutePath()
+ "/" + jarEntry.getName() + "].");
} finally {
try {
if (is != null)
is.close();
} catch (IOException ignored) {
// Ignore
}
}
// ...
}
}
Finding out referenced classes is then just:
final Collection<String> referencedClassNames = ctClass.getRefClasses();
Overall my experience with Javassist for the very similar task was very positive. I hope this helps.

Dynamic Compiling Without Create Physical File

I follow the tutorial from Generating Java classes dynamically through Java compiler API, the code is work but what I see is the program will create a class file after compiling it.
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.*;
public class Compiler {
static final Logger logger = Logger.getLogger(Compiler.class.getName());
static String sourceCode = "class HelloWorld{"
+ "public static void main (String args[]){"
+ "System.out.println (\"Hello, dynamic compilation world!\");"
+ "}"
+ "}";
public void doCompilation() {
SimpleJavaFileObject fileObject = new DynamicJavaSourceCodeObject("HelloWorld", sourceCode);
JavaFileObject javaFileObjects[] = new JavaFileObject[]{fileObject};
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, Locale.getDefault(), null);
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObjects);
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
CompilationTask compilerTask = compiler.getTask(null, stdFileManager, diagnostics, null, null, compilationUnits);
boolean status = compilerTask.call();
if (!status) {
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
System.out.format("Error on line %d in %s\n", diagnostic.getLineNumber(), diagnostic);
}
}
try {
stdFileManager.close();
} catch (IOException ex) {
Logger.getLogger(Compiler.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String args[]) {
new Compiler().doCompilation();
}
}
class DynamicJavaSourceCodeObject extends SimpleJavaFileObject {
private String qualifiedName;
private String sourceCode;
protected DynamicJavaSourceCodeObject(String name, String code) {
super(URI.create("string:///" + name.replaceAll("\\.", "/") + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE);
this.qualifiedName = name;
this.sourceCode = code;
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException {
return sourceCode;
}
public String getQualifiedName() {
return qualifiedName;
}
public void setQualifiedName(String qualifiedName) {
this.qualifiedName = qualifiedName;
}
public String getSourceCode() {
return sourceCode;
}
public void setSourceCode(String sourceCode) {
this.sourceCode = sourceCode;
}
}
Is it possible that after call compilerTask.call(); to not create a class file? If yes how to do that?
For what your doing, I would use Janino. It appears doable using just the JavaCompiler, but not well documented. See the comment I added withe linked question for an example of going about it with the JavaCompiler.
EDIT:
I found an easy to understand example using the JavaCompiler.
To avoid creation of class file by the JavaCompiler use the argument: "-proc:only"

Run a simple text file as Java

I have a simple .txt file which has pure Java code inside it like
public class C {
public static void main(String[] args ) {
System.out.println("This is executed");
}
}
The file is named C.txt. Now I want to write Java code that will read the code in C.txt and will compile and run the read code as a pure Java file. Note, I can easily rename C.txt to C.java and compile and run the code manually. However, this is not my intention. I want to read the .txt file as is and execute the code directly. Is this possible somehow?
You can use the javax.tools api form Java 6 to compile the code on the fly. However since your extension is illegal it will complain with a error: C.txt Class names are only accepted if annotation processing is explicitly requested.
To get around this (as mentioned in the comments) you must first load the code into a String and then execute it:
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyCompiler2 {
public static void main(String[] args) throws Exception {
String program = "";
try {
BufferedReader in = new BufferedReader(new FileReader("C.txt"));
String str;
while ((str = in.readLine()) != null) {
program += str;
}
in.close();
} catch (IOException e) {
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
Iterable<? extends JavaFileObject> fileObjects;
fileObjects = getJavaSourceFromString(program);
compiler.getTask(null, null, null, null, null, fileObjects).call();
Class<?> clazz = Class.forName("C");
Method m = clazz.getMethod("main", new Class[]{String[].class});
Object[] _args = new Object[]{new String[0]};
m.invoke(null, _args);
}
static Iterable<JavaSourceFromString> getJavaSourceFromString(String code) {
final JavaSourceFromString jsfs;
jsfs = new JavaSourceFromString("code", code);
return new Iterable<JavaSourceFromString>() {
public Iterator<JavaSourceFromString> iterator() {
return new Iterator<JavaSourceFromString>() {
boolean isNext = true;
public boolean hasNext() {
return isNext;
}
public JavaSourceFromString next() {
if (!isNext)
throw new NoSuchElementException();
isNext = false;
return jsfs;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
}
class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
Notice how you need to explicitly provide the method and class name in order for reflection to execute your code.
I think I'd start with BeanShell, which allows you to compile and execute Java source held in a string.
Check out this thread for how to start the compile from within Java...
How to set the source for compilation by a CompilationTask

Categories

Resources