Compile Java code in-memory [duplicate] - java

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中文指南

Related

Library for processing Annotations

I've been trying to search the internet, but it seems I cannot find a library for helping processing of Annotations in a POJO. Is there any that exist?
Currently we can process this through code like this:
// Get id
Object id = null;
for (Field field : obj.getClass().getDeclaredFields()){
String fieldName = field.getName();
Object fieldValue = field.get(obj);
if (field.isAnnotationPresent(Id.class)){
id = fieldValue;
}
}
Is there a library to help quickly process annotation and with the associated value.
Take a look at How do I read all classes from a Java package in the classpath?
I was using https://code.google.com/p/reflections/, and now switched to this
package com.clemble.test.reflection;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class AnnotationReflectionUtils {
/** URL prefix for loading from the file system: "file:" */
public static final String FILE_URL_PREFIX = "file:";
/** URL protocol for an entry from a jar file: "jar" */
public static final String URL_PROTOCOL_JAR = "jar";
/** URL protocol for an entry from a zip file: "zip" */
public static final String URL_PROTOCOL_ZIP = "zip";
/** URL protocol for an entry from a JBoss jar file: "vfszip" */
public static final String URL_PROTOCOL_VFSZIP = "vfszip";
/** URL protocol for a JBoss VFS resource: "vfs" */
public static final String URL_PROTOCOL_VFS = "vfs";
/** URL protocol for an entry from a WebSphere jar file: "wsjar" */
public static final String URL_PROTOCOL_WSJAR = "wsjar";
/** URL protocol for an entry from an OC4J jar file: "code-source" */
public static final String URL_PROTOCOL_CODE_SOURCE = "code-source";
/** Separator between JAR URL and file path within the JAR */
public static final String JAR_URL_SEPARATOR = "!/";
// Taken from https://stackoverflow.com/questions/1456930/how-do-i-read-all-classes-from-a-java-package-in-the-classpath
public static <T extends Annotation> List<Class<?>> findCandidates(String basePackage, Class<T> searchedAnnotation) {
ArrayList<Class<?>> candidates = new ArrayList<Class<?>>();
Enumeration<URL> urls;
String basePath = basePackage.replaceAll("\\.", File.separator);
try {
urls = Thread.currentThread().getContextClassLoader().getResources(basePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (isJarURL(url)) {
try {
candidates.addAll(doFindPathMatchingJarResources(url, basePath, searchedAnnotation));
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
File directory = new File(url.getFile());
if (directory.exists() && directory.isDirectory()) {
for (File file : new File(url.getFile()).listFiles())
fetchCandidates(basePackage, file, searchedAnnotation, candidates);
}
}
}
return candidates;
}
private static <T extends Annotation> void fetchCandidates(String basePackage, File candidate, Class<T> searchedAnnotation, List<Class<?>> candidates) {
if (candidate.isDirectory()) {
for (File file : candidate.listFiles())
fetchCandidates(basePackage + "." + candidate.getName(), file, searchedAnnotation, candidates);
} else {
String fileName = candidate.getName();
if (fileName.endsWith(".class")) {
String className = fileName.substring(0, fileName.length() - 6);
Class<?> foundClass = checkCandidate(basePackage + "." + className, searchedAnnotation);
if (foundClass != null)
candidates.add(foundClass);
}
}
}
public static boolean isJarURL(URL url) {
String protocol = url.getProtocol();
return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol) || (URL_PROTOCOL_CODE_SOURCE
.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR)));
}
public static <T extends Annotation> Class<?> checkCandidate(String className, Class<T> searchedAnnotation) {
try {
Class<?> candidateClass = Class.forName(className);
Target target = searchedAnnotation.getAnnotation(Target.class);
for(ElementType elementType: target.value()) {
switch(elementType) {
case TYPE:
if (candidateClass.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case CONSTRUCTOR:
for(Constructor<?> constructor: candidateClass.getConstructors())
if(constructor.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case METHOD:
for(Method method: candidateClass.getMethods())
if(method.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case FIELD:
for(Field field: candidateClass.getFields())
if(field.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
default:
break;
}
}
} catch (ClassNotFoundException e) {
} catch (NoClassDefFoundError e) {
}
return null;
}
/**
* Find all resources in jar files that match the given location pattern
* via the Ant-style PathMatcher.
*
* #param rootDirResource the root directory as Resource
* #param subPattern the sub pattern to match (below the root directory)
* #return the Set of matching Resource instances
* #throws IOException in case of I/O errors
* #see java.net.JarURLConnection
* #see org.springframework.util.PathMatcher
*/
protected static <T extends Annotation> Set<Class<?>> doFindPathMatchingJarResources(URL sourceUrl, String basePackage, Class<T> searchedAnnotation)
throws IOException {
URLConnection con = sourceUrl.openConnection();
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
boolean newJarFile = false;
if (con instanceof JarURLConnection) {
// Should usually be the case for traditional JAR files.
JarURLConnection jarCon = (JarURLConnection) con;
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
} else {
// No JarURLConnection -> need to resort to URL file parsing.
// We'll assume URLs of the format "jar:path!/entry", with the protocol
// being arbitrary as long as following the entry format.
// We'll also handle paths with and without leading "file:" prefix.
String urlFile = sourceUrl.getFile();
int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR);
if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + JAR_URL_SEPARATOR.length());
jarFile = getJarFile(jarFileUrl);
} else {
jarFile = new JarFile(urlFile);
jarFileUrl = urlFile;
rootEntryPath = "";
}
newJarFile = true;
}
try {
if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
// Root entry path must end with slash to allow for proper matching.
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
Set<Class<?>> result = new LinkedHashSet<Class<?>>(8);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
if (entryPath.startsWith(rootEntryPath) && entryPath.endsWith(".class")) {
int entryLength = entryPath.length();
String className = entryPath.replaceAll(File.separator, ".").substring(0, entryLength - 6);
Class<?> foundClass = checkCandidate(className, searchedAnnotation);
if (foundClass != null)
result.add(foundClass);
}
}
return result;
} finally {
// Close jar file, but only if freshly obtained -
// not from JarURLConnection, which might cache the file reference.
if (newJarFile) {
jarFile.close();
}
}
}
/**
* Resolve the given jar file URL into a JarFile object.
*/
protected static JarFile getJarFile(String jarFileUrl) throws IOException {
if (jarFileUrl.startsWith(FILE_URL_PREFIX)) {
try {
return new JarFile(new URI(jarFileUrl.replaceAll(" ", "%20")).getSchemeSpecificPart());
} catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever happen).
return new JarFile(jarFileUrl.substring(FILE_URL_PREFIX.length()));
}
} else {
return new JarFile(jarFileUrl);
}
}
}
This is based on some Spring utility, class which I could not use directly in my application, but I forgot which one was it.

Why can reflection package scanning work in standard JVM and not work in Android

I've written simple ReflectionUtils, to find all classes with specific Annotation, I am using it successfully on my server, but for some reason it does not perform as expected on Android. (I specifically use it to find all classes annotated with #JsonTypeName, and add them to ObjectMapper context)
What might be the problem?
package com.acme.reflection.utils;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ReflectionUtils {
/** URL prefix for loading from the file system: "file:" */
public static final String FILE_URL_PREFIX = "file:";
/** URL protocol for an entry from a jar file: "jar" */
public static final String URL_PROTOCOL_JAR = "jar";
/** URL protocol for an entry from a zip file: "zip" */
public static final String URL_PROTOCOL_ZIP = "zip";
/** URL protocol for an entry from a JBoss jar file: "vfszip" */
public static final String URL_PROTOCOL_VFSZIP = "vfszip";
/** URL protocol for a JBoss VFS resource: "vfs" */
public static final String URL_PROTOCOL_VFS = "vfs";
/** URL protocol for an entry from a WebSphere jar file: "wsjar" */
public static final String URL_PROTOCOL_WSJAR = "wsjar";
/** URL protocol for an entry from an OC4J jar file: "code-source" */
public static final String URL_PROTOCOL_CODE_SOURCE = "code-source";
/** Separator between JAR URL and file path within the JAR */
public static final String JAR_URL_SEPARATOR = "!/";
// Taken from http://stackoverflow.com/questions/1456930/how-do-i-read-all-classes-from-a-java-package-in-the-classpath
public static <T extends Annotation> List<Class<?>> findCandidates(String basePackage, Class<T> searchedAnnotation) {
ArrayList<Class<?>> candidates = new ArrayList<Class<?>>();
Enumeration<URL> urls;
String basePath = basePackage.replaceAll("\\.", File.separator);
try {
urls = Thread.currentThread().getContextClassLoader().getResources(basePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (isJarURL(url)) {
try {
candidates.addAll(doFindPathMatchingJarResources(url, basePath, searchedAnnotation));
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
File directory = new File(url.getFile());
if (directory.exists() && directory.isDirectory()) {
for (File file : new File(url.getFile()).listFiles())
fetchCandidates(basePackage, file, searchedAnnotation, candidates);
}
}
}
return candidates;
}
private static <T extends Annotation> void fetchCandidates(String basePackage, File candidate, Class<T> searchedAnnotation, List<Class<?>> candidates) {
if (candidate.isDirectory()) {
for (File file : candidate.listFiles())
fetchCandidates(basePackage + "." + candidate.getName(), file, searchedAnnotation, candidates);
} else {
String fileName = candidate.getName();
if (fileName.endsWith(".class")) {
String className = fileName.substring(0, fileName.length() - 6);
Class<?> foundClass = checkCandidate(basePackage + "." + className, searchedAnnotation);
if (foundClass != null)
candidates.add(foundClass);
}
}
}
public static boolean isJarURL(URL url) {
String protocol = url.getProtocol();
return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol) || (URL_PROTOCOL_CODE_SOURCE
.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR)));
}
public static <T extends Annotation> Class<?> checkCandidate(String className, Class<T> searchedAnnotation) {
try {
Class<?> candidateClass = Class.forName(className);
Target target = searchedAnnotation.getAnnotation(Target.class);
for(ElementType elementType: target.value()) {
switch(elementType) {
case TYPE:
if (candidateClass.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case CONSTRUCTOR:
for(Constructor<?> constructor: candidateClass.getConstructors())
if(constructor.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case METHOD:
for(Method method: candidateClass.getMethods())
if(method.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case FIELD:
for(Field field: candidateClass.getFields())
if(field.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
default:
break;
}
}
} catch (ClassNotFoundException | NoClassDefFoundError e) {
;
}
return null;
}
/**
* Find all resources in jar files that match the given location pattern
* via the Ant-style PathMatcher.
*
* #param rootDirResource the root directory as Resource
* #param subPattern the sub pattern to match (below the root directory)
* #return the Set of matching Resource instances
* #throws IOException in case of I/O errors
* #see java.net.JarURLConnection
* #see org.springframework.util.PathMatcher
*/
protected static <T extends Annotation> Set<Class<?>> doFindPathMatchingJarResources(URL sourceUrl, String basePackage, Class<T> searchedAnnotation)
throws IOException {
URLConnection con = sourceUrl.openConnection();
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
boolean newJarFile = false;
if (con instanceof JarURLConnection) {
// Should usually be the case for traditional JAR files.
JarURLConnection jarCon = (JarURLConnection) con;
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
} else {
// No JarURLConnection -> need to resort to URL file parsing.
// We'll assume URLs of the format "jar:path!/entry", with the protocol
// being arbitrary as long as following the entry format.
// We'll also handle paths with and without leading "file:" prefix.
String urlFile = sourceUrl.getFile();
int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR);
if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + JAR_URL_SEPARATOR.length());
jarFile = getJarFile(jarFileUrl);
} else {
jarFile = new JarFile(urlFile);
jarFileUrl = urlFile;
rootEntryPath = "";
}
newJarFile = true;
}
try {
if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
// Root entry path must end with slash to allow for proper matching.
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
Set<Class<?>> result = new LinkedHashSet<>(8);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
if (entryPath.startsWith(rootEntryPath) && entryPath.endsWith(".class")) {
int entryLength = entryPath.length();
String className = entryPath.replaceAll(File.separator, ".").substring(0, entryLength - 6);
Class<?> foundClass = checkCandidate(className, searchedAnnotation);
if (foundClass != null)
result.add(foundClass);
}
}
return result;
} finally {
// Close jar file, but only if freshly obtained -
// not from JarURLConnection, which might cache the file reference.
if (newJarFile) {
jarFile.close();
}
}
}
/**
* Resolve the given jar file URL into a JarFile object.
*/
protected static JarFile getJarFile(String jarFileUrl) throws IOException {
if (jarFileUrl.startsWith(FILE_URL_PREFIX)) {
try {
return new JarFile(new URI(jarFileUrl.replaceAll(" ", "%20")).getSchemeSpecificPart());
} catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever happen).
return new JarFile(jarFileUrl.substring(FILE_URL_PREFIX.length()));
}
} else {
return new JarFile(jarFileUrl);
}
}
}
Found a simmilar question on:
Implementing Spring-like package scanning in Android
After some consideration, I decided to change the approach for ObjectManager.
I keep module configurations in predefined package xxx.yyy.zzz.json.AAAJsonModule and on ObjectMapper construction try to load module configurations in xxx.yyy.zzz.json.{AAA}JsonModule modules, if module is missing, I ignore it. So that way I can dynamically change ObjectMapper mapping, based on the present jars in classpath.

clear the class loaded through class.forName in 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.

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