I am trying to write an annotation Procssor to detect the methods that are annotated with the #PrintMethod annotation. For example in the test Class below, i want to print the codes within the test Method. Is there a way to do it?
From the AnnotationProcessor class stated below, i am only able get the method name but not the details of the method.
Test Class
public class test {
public static void main(String[] args) {
System.out.println("Args");
}
#PrintMethod
private boolean testMethod(String input) {
if(input!=null) {
return true;
}
return false;
}
}
Annotation Processor Class
public class AnnotationProcessor extends AbstractProcessor {
//......
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//retrieve test Anntoation
Set<? extends Element> ann =roundEnv.getElementsAnnotatedWith(PrintMethod.class);
//Print the Method Name
for(Element e: ann) {
String msg="Element ee :"+ee.getSimpleName().toString();
processingEnv.getMessager().printMessage( javax.tools.Diagnostic.Kind.ERROR, msg, e);
}
}
}
I was curious about this too so I decided to try and figure it out. Turns out to be easier than I expected. All you need to do is leverage the Trees api out of the proprietary tools.jar library. I've made a quick annotation processor along these lines here: https://github.com/johncarl81/printMethod
Here's the meat of it:
#SupportedSourceVersion(SourceVersion.RELEASE_6)
#SupportedAnnotationTypes("org.printMethod.PrintMethod")
public class PrintMethodAnnotationProcessor extends AbstractProcessor {
private Trees trees;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = Trees.instance(processingEnv); //initialize the Trees api.
}
#Override
public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnvironment) {
MethodPrintScanner visitor = new MethodPrintScanner();
for (Element e : roundEnvironment.getElementsAnnotatedWith(PrintMethod.class)) {
TreePath tp = trees.getPath(e);
// visit the annotated methods
visitor.scan(tp, trees);
}
return true;
}
#Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
And the MethodPrintScanner:
public class MethodPrintScanner extends TreePathScanner {
#Override
public Object visitMethod(MethodTree methodTree, Object o) {
System.out.println(methodTree);
return null;
}
}
You can see that we are able to visit the TreePath associated with the given annotated Element. For each method, we simply println() the methodTree which gives us the contents of the method.
Using your example, here's the output of the program during compilation:
#PrintMethod()
private boolean testMethod(String input) {
if (input != null) {
return true;
}
return false;
}
It's one thing to make it work in your IDE. But it's another to detect them once your code is packed inside jar files. The following code can manage both.
public static List<Class> getPackageClassListHavingAnnotation(String pPackageName,
Class<? extends Annotation> pAnnotation) throws Exception
{
try
{
List<Class> classList = getPackageClassList(pPackageName);
if ((pAnnotation == null) || (classList == null)) return classList;
List<Class> resultList = new ArrayList<Class>(classList.size());
outerLoop:
for (Class clazz : classList)
{
try
{
for (Method method : clazz.getMethods())
{
if (method.isAnnotationPresent(pAnnotation))
{
resultList.add(clazz);
continue outerLoop;
}
}
}
catch (Throwable e)
{
}
}
return (resultList.isEmpty()) ? null : resultList;
}
catch (Exception e)
{
return null;
}
}
It requires the following helper methods:
public static List<Class> getPackageClassList(String pPackageName) throws Exception
{
try
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = pPackageName.replace('.', '/');
List<File> dirs = new ArrayList<File>();
List<JarFile> jars = new ArrayList<JarFile>();
Enumeration<URL> resources = classLoader.getResources(path);
if (resources != null)
{
String fileName;
URL resource;
File file;
while (resources.hasMoreElements())
{
resource = resources.nextElement();
fileName = resource.getFile();
if (fileName.contains("!"))
{
// jar file
resource = new URL(StringUtil.getArrayFromString(fileName, "!")[0]);
file = urlToFile(resource);
if (!file.exists()) continue;
jars.add(new JarFile(file));
}
else
{
// class file that is not in a jar file
file = urlToFile(resource);
if (!file.exists()) continue;
dirs.add(file);
}
}
}
List<Class> resultList = new ArrayList<Class>(1000);
List<Class> tmpClassList;
for (File directory : dirs)
{
tmpClassList = getPckDirClassList(directory, pPackageName);
if (tmpClassList != null) resultList.addAll(tmpClassList);
}
for (JarFile jar : jars)
{
tmpClassList = getPckJarClassList(jar, pPackageName);
if (tmpClassList != null) resultList.addAll(tmpClassList);
}
return (resultList.isEmpty()) ? null : resultList;
}
catch (Exception e)
{
return null;
}
}
private static List<Class> getPckJarClassList(JarFile pJar, String pPackageName)
{
if ((pJar == null) || (pPackageName == null)) return null;
List<Class> resultList = new ArrayList<Class>(100);
Enumeration<JarEntry> jarEntries = (pJar.entries());
JarEntry jarEntry;
String fullClassName;
while (jarEntries.hasMoreElements())
{
jarEntry = jarEntries.nextElement();
fullClassName = jarEntry.getName().replaceAll("/", ".");
if (!fullClassName.startsWith(pPackageName)) continue;
if (!fullClassName.endsWith(".class")) continue;
// do not do a Class.forName for the following path, this can crash the server
try
{
resultList.add(Class.forName(fullClassName.substring(0, fullClassName.length() - 6)));
}
catch (Throwable e)
{
}
}
return (resultList.isEmpty()) ? null : resultList;
}
/**
* Recursive method to find all classes in a package directory tree.
*/
private static List<Class> getPckDirClassList(File pDirectory, String pPackageName) throws ClassNotFoundException
{
try
{
if ((pDirectory == null) || (pPackageName == null)) return null;
if (!pDirectory.exists()) return null;
File[] files = pDirectory.listFiles();
if ((files == null) || (files.length == 0)) return null;
List<Class> resultList = new ArrayList<Class>(100);
List<Class> tmpClassList;
for (File file : files)
{
if (file.isDirectory())
{
tmpClassList = getPckDirClassList(file, pPackageName + "." + file.getName());
if (tmpClassList != null) resultList.addAll(tmpClassList);
}
else if (file.getName().endsWith(".class"))
{
try
{
resultList.add(Class.forName(pPackageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
catch (Throwable e)
{
}
}
}
return (resultList.isEmpty()) ? null : resultList;
}
catch (Exception e)
{
return null;
}
}
This code has been tested with .jar files on both windows and unix systems. It has also been tested with .java files in IntelliJ on windows.
Related
I am migrating an app from java 8 to java 11. I did most of the upgrade but got stuck with the following exception.
class com.teamhub.plugins.sitemaps.jobs.SitemapUpdateJob cannot be cast to class org.quartz.Job (com...SitemapUpdateJob is in unnamed module of loader com...utils.ChildFirstClassLoader #bb39957; org.quartz.Job is in unnamed module of loader org.akhikhl.gretty.FilteringClassLoader #247667dd)
The application uses the normal context class loader but it loads plugins into the classpath using the ChildFirstClassLoader which on java 8 worked like a charm. In Java 11 though, it fails.
Looks like the issue is that the parent classloader loaded Job, and the child, ChildFirstClassLoader failes to use that to cast an implemented job.
public class ChildFirstClassLoader extends URLClassLoader {
private static final Set<String> FORCE_IN_PARENT = new HashSet<String>(); //ImmutableSet.of("");
private ClassLoader system;
public ChildFirstClassLoader(URL[] classpath, ClassLoader parent) {
super(classpath, parent);
system = getSystemClassLoader();
}
#Override
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// checking local
c = findClass(name);
} catch (ClassNotFoundException | SecurityException e) {
c = loadClassFromParent(name, resolve);
}
}
if (resolve)
resolveClass(c);
return c;
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
for (String pkg : FORCE_IN_PARENT){
if (name.startsWith(pkg)) {
throw new ClassNotFoundException();
}
}
return super.findClass(name);
}
private Class<?> loadClassFromParent(String name, boolean resolve) throws ClassNotFoundException {
// checking parent
// This call to loadClass may eventually call findClass
// again, in case the parent doesn't find anything.
Class<?> c;
try {
c = super.loadClass(name, resolve);
} catch (ClassNotFoundException | SecurityException e) {
c = loadClassFromSystem(name);
}
return c;
}
private Class<?> loadClassFromSystem(String name) throws ClassNotFoundException{
Class<?> c = null;
if (system != null) {
// checking system: jvm classes, endorsed, cmd classpath,
// etc.
c = system.loadClass(name);
}
return c;
}
#Override
public URL getResource(String name) {
URL url = findResource(name);
if (url == null)
url = super.getResource(name);
if (url == null && system != null)
url = system.getResource(name);
return url;
}
#Override
public Enumeration<URL> getResources(String name) throws IOException {
/**
* Similar to super, but local resources are enumerated before parent
* resources
*/
Enumeration<URL> systemUrls = null;
if (system != null) {
systemUrls = system.getResources(name);
}
Enumeration<URL> localUrls = findResources(name);
Enumeration<URL> parentUrls = null;
if (getParent() != null) {
parentUrls = getParent().getResources(name);
}
final List<URL> urls = new ArrayList<URL>();
if (localUrls != null) {
while (localUrls.hasMoreElements()) {
URL local = localUrls.nextElement();
urls.add(local);
}
}
if (systemUrls != null) {
while (systemUrls.hasMoreElements()) {
urls.add(systemUrls.nextElement());
}
}
if (parentUrls != null) {
while (parentUrls.hasMoreElements()) {
urls.add(parentUrls.nextElement());
}
}
return new Enumeration<URL>() {
Iterator<URL> iter = urls.iterator();
public boolean hasMoreElements() {
return iter.hasNext();
}
public URL nextElement() {
return iter.next();
}
};
}
public InputStream getResourceAsStream(String name) {
URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
}
return null;
}
#Override
public String toString() {
return String.format("ChildFirstClassLoader [system=%s, getURLs()=%s, getParent()=%s]",
system, Arrays.toString(getURLs()), getParent());
}
So, I have a custom classloader to load classes into memory from byte arrays, and I have a problem: When I attempt to see if a class is assignable from another(ClassLoader.isAssignableFrom), it returns false, even if the compiled class extends or implements it. I assume because it's loaded by a different and custom classloader rather than the system one, so how would I fix this? The reason that I need to do this is I want to check if class files in a jar file are ClassLoaders themselves, because I'm making a java virus scanner for jar files.
Custom ClassLoader:
public class CL extends ClassLoader {
byte[] jar = null;
private HashMap<String, Class<?>> classes = new HashMap<String, Class<?>>();
private HashMap<String, byte[]> resources = new HashMap<String, byte[]>();
public CL(byte[] jar) {
this.jar = jar;
}
private JarInputStream getStream() {
try {
return new JarInputStream(new ByteArrayInputStream(jar));
}catch (IOException e) {
e.printStackTrace();
}
return null;
}
public InputStream getResourceAsStream(String name) {
if (!resources.containsKey(name)) {
try {
JarInputStream stream = getStream();
JarEntry entry = stream.getNextJarEntry();
ArrayList<JarEntry> ent = new ArrayList<JarEntry>();
while (entry != null) {
String en = entry.getName().replace("/", ".");
if (en.contains(".")) {
en = en.substring(0, en.lastIndexOf("."));
}
if (en.equals(name)) {
break;
}
ent.add(entry);
entry = stream.getNextJarEntry();
}
if (entry == null) {
for (JarEntry e : ent) {
String en = e.getName().replace("/", ".");
if (en.contains(".")) {
en = en.substring(0, en.lastIndexOf("."));
}
if (en.lastIndexOf(".") > 0 && en.substring(en.lastIndexOf(".") + 1).equals(name)) {
entry = e;
break;
}
}
}
if (entry == null) {
return null;
}
ent = null;
ByteArrayOutputStream byt = new ByteArrayOutputStream();
while (true) {
int r = stream.read();
if (r < 0) break;
byt.write(r);
}
stream.close();
byte[] reqc = byt.toByteArray();
return new ByteArrayInputStream(reqc);
}catch (IOException e) {
e.printStackTrace();
}
}else {
return new ByteArrayInputStream(resources.get(name));
}
return null;
}
public Class<?> findClass(String name) {
if (!classes.containsKey(name)) {
try {
JarInputStream stream = getStream();
JarEntry entry = stream.getNextJarEntry();
while (entry != null) {
String en = entry.getName().replace("/", ".");
if (en.contains(".")) {
en = en.substring(0, en.lastIndexOf("."));
}
if (en.equals(name)) {
break;
}
entry = stream.getNextJarEntry();
}
if (entry == null) {
return null;
}
ByteArrayOutputStream byt = new ByteArrayOutputStream();
while (true) {
int r = stream.read();
if (r < 0) break;
byt.write(r);
}
stream.close();
byte[] reqc = byt.toByteArray();
Class<?> c = defineClass(name, reqc, 0, reqc.length, CL.class.getProtectionDomain());
classes.put(name, c);
return c;
}catch (IOException e) {
e.printStackTrace();
}
}else {
return classes.get(name);
}
return null;
}
}
My code for checking if something is assignable from a classloader(cl is an instance of my classloader):
Class<?> cls = cl.findClass(fname);
boolean isCL = false;
if (cls.isAssignableFrom(ClassLoader.class)) {
isCL = true;
}
boolean bCL = false;
for (Method m : cls.getMethods()) {
String mn = m.getName();
if (isCL) {
if (mn.contains("loadClass") || mn.contains("defineClass") || mn.contains("findClass")) {
bCL = true;
}
}
}
The problem: the isAssignableFrom returns false, even if it should be true.
So, is there a fix to this? I do not want to switch classloaders, as I am initially loading from a jar, but I want to be able to load jars inside jars and zips. Thanks!
Your problem is that you are using isAssignableFrom incorrectly (albeit, it is a very confusing method). this is what you want:
ClassLoader.class.isAssignableFrom(cls)
I ended up figuring out a hack of sorts, for superclasses(superinterfaces are different) use this:
cls.getSuperclass().getName().equals("java.lang.ClassLoader") Where the java.lang.ClassLoader is the fully qualified name you need to check. You do not need to have the class loaded in your main classloader.
I use spring framework to find the class and its methods and arguments dynamically.
these are the methods I use :
public List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
List<Class> candidates = new ArrayList<Class>();
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + "**/*.class";
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
if (isCandidate(metadataReader)) {
candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
}
}
}
return candidates;
}
public String resolveBasePackage(String basePackage) {
return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}
#SuppressWarnings({ "rawtypes", "unchecked" })
public boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
try {
Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
if (c.getAnnotation(Controller.class) != null) {
return true;
}
}
catch(Throwable e){
}
return false;
}
I load the class which has got annotation #Controller. It is working fine but I want to load only the class not interface also how do I get the methods and the arguments of the class loaded.
EDIT :
This is how I get all the class names and try to get the methods name :
List classNames = hexgenClassUtils.findMyTypes("com.hexgen.*");
Iterator<Class> it = classNames.iterator();
while(it.hasNext())
{
Class obj = it.next();
System.out.println("Class :"+obj.toString());
cls = Class.forName(obj.toString());
Method[] method = cls.getMethods();
for (Method method2 : method) {
System.out.println("Method name : "+method2.toGenericString());
}
// TODO something with obj
}
The problem I face is class com.hexgen.api.facade.HexgenWebAPI here class is coming because of which I am not able to load the class dynamically and get the following exception.
java.lang.ClassNotFoundException: class com.hexgen.api.facade.HexgenWebAPI so how to solve it.
Kindly help me to find the solution.
Best Regards
try
Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
if (!c.isInterface() && c.getAnnotation(Controller.class) != null) {
return true;
}
I want to have my persistence.xml in conf folder of my app. How can I tell Persistence.createEntityManagerFactory that it should read it from there?
If you are using EclipseLink you can set the persistence.xml location with the persistence unit property, "eclipselink.persistencexml".
properties.put("eclipselink.persistencexml", "/org/acme/acme-persistence.xml");
EntityManagerFactory factory = Persistence.createEntityManagerFactory("acme", properties);
This solution worked for me
Thread.currentThread().setContextClassLoader(new ClassLoader() {
#Override
public Enumeration<URL> getResources(String name) throws IOException {
if (name.equals("META-INF/persistence.xml")) {
return Collections.enumeration(Arrays.asList(new File("conf/persistence.xml")
.toURI().toURL()));
}
return super.getResources(name);
}
});
Persistence.createEntityManagerFactory("test");
The createEntityManagerFactory methods search for persistence.xml files within the META-INF directory of any CLASSPATH element.
if your CLASSPATH contains the conf directory, you could place an EntityManagerFactory definition in conf/META-INF/persistence.xml
The ClassLoader may be a URLClassLoader, so try it this way:
final URL alternativePersistenceXmlUrl = new File("conf/persistence.xml").toURI().toURL();
ClassLoader output;
ClassLoader current = Thread.currentThread().getContextClassLoader();
try{
URLClassLoader parent = (URLClassLoader)current;
output = new URLClassLoader(parent.getURLs(), parent){
#Override
public Enumeration<URL> getResources(String name) throws IOException {
if (name.equals("META-INF/persistence.xml")) {
return Collections.enumeration(Arrays.asList(alternativePersistenceXmlUrl));
}
return super.getResources(name);
}
};
}catch(ClassCastException ignored) {
output = new ClassLoader() {
#Override
public Enumeration<URL> getResources(String name) throws IOException {
if (name.equals("META-INF/persistence.xml")) {
return Collections.enumeration(Arrays.asList(alternativePersistenceXmlUrl));
}
return super.getResources(name);
}
};
}
It should work. Works for me under certain test etc conditions.
Please this is a hack and should not be used in production.
My solution is for EclipseLink 2.7.0 and Java 9 and it is modified and detailed version of #Evgeniy Dorofeev answer.
In org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor on line 236 we see the following code:
URL puRootUrl = computePURootURL(descUrl, descriptorPath);
This code is used by EclipseLink to compute root url of the persistence.xml path. That's very important because final path will be made by adding descriptorPath to puRootUrl.
So, let's suppose we have file on /home/Smith/program/some-folder/persistence.xml, then we have:
Thread currentThread = Thread.currentThread();
ClassLoader previousClassLoader = currentThread.getContextClassLoader();
Thread.currentThread().setContextClassLoader(new ClassLoader(previousClassLoader) {
#Override
public Enumeration<URL> getResources(String name) throws IOException {
if (name.equals("some-folder/persistence.xml")) {
URL url = new File("/home/Smith/program/some-folder/persistence.xml").toURI().toURL();
return Collections.enumeration(Arrays.asList(url));
}
return super.getResources(name);
}
});
Map<String, String> properties = new HashMap<>();
properties.put("eclipselink.persistencexml", "some-folder/persistence.xml");
try {
entityManagerFactory = Persistence.createEntityManagerFactory("unit-name", properties);
} catch (Exception ex) {
logger.error("Error occured creating EMF", ex);
} finally {
currentThread.setContextClassLoader(previousClassLoader);
}
Details:
Pay attention that when creating new class loader I pass there previous classloader otherwise it doesn't work.
We set property eclipselink.persistencexml. If we don't do that then default descriptorPath will be equal to META-INF/persistence.xml and we would need to keep our persistence.xml on /home/Smith/program/META-INF/persistence.xml to be found.
I tried these ways when the program is starting (at first line of main function):
Write your persistence.xml to the resources/META-INF/persistence.xml of the jar
I had problem with this way: Java write .txt file in resource folder
Create META-INF folder in the jar directory and put your persistence.xml into it, then execute this command:
jar uf $jarName META-INF/persistence.xml
This command will replace META-INF/persistence.xml (your file) in the jar
private fun persistence() {
val fileName = "META-INF/persistence.xml"
val jarName: String?
val done = try {
jarName = javaClass.protectionDomain.codeSource.location.path
if (File(fileName).exists() && !jarName.isNullOrBlank()
&& jarName.endsWith(".jar") && File(jarName).exists()) {
Command().exec("jar uf $jarName META-INF/persistence.xml", timeoutSec = 30)
true
} else false
} catch (e: Exception) {
false
}
if (done) {
logger.info { "$fileName exist and will be loaded :)" }
} else {
logger.info {
"$fileName not exist in current folder so it will be read from .jar :(" +
" you can run: jar uf jarName.jar META-INF/persistence.xml"
}
}
}
Running Command Line in Java
A solution by creating tweaked PersistenceUnitDescriptor.
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
import org.hibernate.jpa.boot.spi.Bootstrap;
import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder;
public class HibernateEntityManagerFactoryBuilder {
public static final EntityManagerFactory build(URL xmlUrl) {
final ParsedPersistenceXmlDescriptor xmlDescriptor = PersistenceXmlParser.locateIndividualPersistenceUnit(xmlUrl);
final HibernatePersistenceUnitDescriptor hibernateDescriptor = new HibernatePersistenceUnitDescriptor(xmlDescriptor);
final EntityManagerFactoryBuilder builder = Bootstrap.getEntityManagerFactoryBuilder(hibernateDescriptor, Collections.emptyMap(), (ClassLoader) null);
final EntityManagerFactory factory = builder.build();
return factory;
}
public static final EntityManagerFactory build(URL xmlUrl, final String name) {
final ParsedPersistenceXmlDescriptor xmlDescriptor = PersistenceXmlParser.locateNamedPersistenceUnit(xmlUrl, name);
if(xmlDescriptor == null) throw new RuntimeException("Persistence unit with name '"+name+ "' not found.");
final HibernatePersistenceUnitDescriptor hibernateDescriptor = new HibernatePersistenceUnitDescriptor(xmlDescriptor);
final EntityManagerFactoryBuilder builder = Bootstrap.getEntityManagerFactoryBuilder(hibernateDescriptor, Collections.emptyMap(), (ClassLoader) null);
final EntityManagerFactory factory = builder.build();
return factory;
}
public static void main(String[] args) {
try {
final EntityManagerFactory factory = build(new File("D:/ini/persistence.xml").toURI().toURL());
} catch (Exception e) {e.printStackTrace();}
}
}
public class HibernatePersistenceUnitDescriptor implements PersistenceUnitDescriptor {
private final PersistenceUnitDescriptor descriptor;
public HibernatePersistenceUnitDescriptor(PersistenceUnitDescriptor descriptor) {
this.descriptor = descriptor;
}
#Override
public URL getPersistenceUnitRootUrl() {
return null;
}
#Override
public String getName() {
return descriptor.getName();
}
#Override
public String getProviderClassName() {
return descriptor.getProviderClassName();
}
#Override
public boolean isUseQuotedIdentifiers() {
return descriptor.isUseQuotedIdentifiers();
}
#Override
public boolean isExcludeUnlistedClasses() {
return descriptor.isExcludeUnlistedClasses();
}
#Override
public PersistenceUnitTransactionType getTransactionType() {
return descriptor.getTransactionType();
}
#Override
public ValidationMode getValidationMode() {
return descriptor.getValidationMode();
}
#Override
public SharedCacheMode getSharedCacheMode() {
return descriptor.getSharedCacheMode();
}
#Override
public List<String> getManagedClassNames() {
return descriptor.getManagedClassNames();
}
#Override
public List<String> getMappingFileNames() {
return descriptor.getMappingFileNames();
}
#Override
public List<URL> getJarFileUrls() {
return descriptor.getJarFileUrls();
}
#Override
public Object getNonJtaDataSource() {
return descriptor.getNonJtaDataSource();
}
#Override
public Object getJtaDataSource() {
return descriptor.getJtaDataSource();
}
#Override
public Properties getProperties() {
return descriptor.getProperties();
}
#Override
public ClassLoader getClassLoader() {
return descriptor.getClassLoader();
}
#Override
public ClassLoader getTempClassLoader() {
return descriptor.getTempClassLoader();
}
#Override
public void pushClassTransformer(EnhancementContext enhancementContext) {
descriptor.pushClassTransformer(enhancementContext);
}
}
Despite warnings to drop my present course of action, I currently see no better way to solve my problem. I must generate Java code at runtime, then compile it, load it and reference it.
Problem is that the generated code imports code that has already been loaded by the system class loader (I suppose) - that is, code present in one of the jars on my classpath.
(I run inside a Tomcat 6 web container over Java 6.) You may ask yourselves why that is a problem - well I sure don't know - but fact is that I get compilation errors:
/W:/.../parser/v0.5/AssignELParser.java:6:
package com.xxx.yyy.zzz.configuration
does not exist
Following some examples off the internet I have defined the following classes:
class MemoryClassLoader extends ChainedAction {
private static final Logger LOG = Logger.getLogger(MemoryClassLoader.class);
private LoaderImpl impl;
private class LoaderImpl extends ClassLoader {
// The compiler tool
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// Compiler options
private final Iterable<String> options = Arrays.asList("-verbose");
// DiagnosticCollector, for collecting compilation problems
private final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
// Our FileManager
private final MemoryFileManager manager = new MemoryFileManager(this.compiler);
public LoaderImpl(File sourceDirectory) {
List<Source> list = new ArrayList<Source>();
File[] files = sourceDirectory.listFiles(new FilenameFilter() {
#Override
public boolean accept(File dir, String name) {
return name.endsWith(Kind.SOURCE.extension);
}
});
for (File file : files) {
list.add(new Source(file));
}
CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, list);
Boolean compilationSuccessful = task.call();
LOG.info("Compilation has " + ((compilationSuccessful) ? "concluded successfully" : "failed"));
// report on all errors to screen
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
LOG.warn(diagnostic.getMessage(null));
}
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
synchronized (this.manager) {
Output output = manager.map.remove(name);
if (output != null) {
byte[] array = output.toByteArray();
return defineClass(name, array, 0, array.length);
}
}
return super.findClass(name);
}
}
#Override
protected void run() {
impl = new LoaderImpl(new File(/* Some directory path */));
}
}
class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
final Map<String, Output> map = new HashMap<String, Output>();
MemoryFileManager(JavaCompiler compiler) {
super(compiler.getStandardFileManager(null, null, null));
}
#Override
public Output getJavaFileForOutput(Location location, String name, Kind kind, FileObject source) {
Output output = new Output(name, kind);
map.put(name, output);
return output;
}
}
class Output extends SimpleJavaFileObject {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output(String name, Kind kind) {
super(URI.create("memo:///" + name.replace('.', '/') + kind.extension), kind);
}
byte[] toByteArray() {
return this.baos.toByteArray();
}
#Override
public ByteArrayOutputStream openOutputStream() {
return this.baos;
}
}
class Source extends SimpleJavaFileObject {
public Source(File file) {
super(file.toURI(), Kind.SOURCE);
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
StringBuilder sb = new StringBuilder("");
try {
File file = new File(uri);
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
sb = new StringBuilder((int) file.length());
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sb.toString();
}
}
It seems that the inner class LoaderImpl by extending the ClassLoader class and by not calling an explicit super constructor should reference as its parent class loader the system class loader.
If it does so then why do I get the "runtime" compilation error - above? Why does it not find the code for the imported class?
Not sure if it can help, but have you tried to specify classpath explicitly?
getClassPath()
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL[] urls = ((URLClassLoader) classLoader).getURLs();
StringBuilder buf = new StringBuilder(1000);
buf.append(".");
String separator = System.getProperty("path.separator");
for (URL url : urls) {
buf.append(separator).append(url.getFile());
}
}
classPath = buf.toString();
and then
options.add("-classpath");
options.add(getClassPath());
I also can't see where do you pass LoaderImpl instance to the compiler. Shouldn't it be done explicitly?