Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java classloader and runtime compilation

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?

like image 811
Yaneeve Avatar asked Oct 29 '09 09:10

Yaneeve


People also ask

What is the use of ClassLoader in Java?

The Java ClassLoader is a part of the Java Runtime Environment that dynamically loads Java classes into the Java Virtual Machine. The Java run time system does not need to know about files and file systems because of classloaders. Java classes aren't loaded into memory all at once, but when required by an application.

What is Java Lang ClassLoader?

The java. lang. ClassLoader class is an object that is responsible for loading classes. This class is an abstract class. It may be used by security managers to indicate security domains.

How does JVM ClassLoader work?

ClassLoader in Java is a class that is used to load class files in Java. Java code is compiled into a class file by javac compiler and JVM executes the Java program, by executing byte codes written in the class file. ClassLoader is responsible for loading class files from file systems, networks, or any other source.


1 Answers

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?

like image 185
Niki Tonsky Avatar answered Oct 24 '22 19:10

Niki Tonsky