1

I compile single groovy source module "in fly" using GroovyClassLoader.parseClass(src) and all is ok.

But problem is when this source module imports other classes, these are not compiled yet. Traditional compiling when I start compilation of one source but other are required and ready on source path, are compiled too.

How can I use GroovyClassLoader with target to compile all other required sources NOT FROM FILESYSYSTEM. My sources are for example in database, remote http via URI etc.

Jacek Cz
  • 1,872
  • 1
  • 15
  • 22
  • public void setResourceLoader(GroovyResourceLoader resLoader) and class GroovyResourceLoader is interesting for me? So good environment MUST HAVE option to reading sources from virtual file system, I think... – Jacek Cz Aug 16 '15 at 10:41
  • I do the same/similar thing. setResourceLoader is your answer. – lscoughlin Aug 19 '15 at 13:06
  • @lscoughlin I have checked GroovyResourceLoader in many ways, conceptually all is OK, event is fired, import is known etc, I can read source fragment in implemented GroovyResourceLoader but can't return referring URL. GroovyClassLoader uses URL in many places, but in philosophy java URLLoader, internal to project path. The other problem GroovyClassLoader has many private elements hard to "replace" (override). Do You successful realize Your idea? – Jacek Cz Aug 19 '15 at 14:50
  • Yup. I posted my solution below. Let me know how it works out for you, or if you have any questions. – lscoughlin Aug 20 '15 at 09:00

1 Answers1

4

The key is to make custom URL handling -- you have to implement a URLStreamHandler and a URLConnection.

If you google around, there's some good documentation on how to implement the stream handler and connection classes -- but for what you're doing, you really only need dummy implementations.

Here's some source code to bootstrap you -- it demonstrates how to connect the pieces up. If you provide some implementation of lookupScriptCodeWithJDBCorWhatever you'll be good to go.

import groovy.lang.GroovyResourceLoader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

public class CustomGroovyResourceLoader implements GroovyResourceLoader {

    private final GroovyResourceLoader parentLoader;

    public CustomGroovyResourceLoader(GroovyResourceLoader parentLoader) {
        this.parentLoader = parentLoader;
    }

    @Override
    public URL loadGroovySource(String filename) throws MalformedURLException {
        URL resourceURL = null;

        if (parentLoader != null) {
            resourceURL = parentLoader.loadGroovySource(filename);
        }

        if (resourceURL == null) {
            resourceURL = createURL(filename);
        }

        return resourceURL;
    }

    public URL createURL(String resourceName) throws MalformedURLException {

        String scriptSourceCode = lookupScriptCodeWithJDBCorWhatever(resourceName);

        return new URL(null, "groovy:///" + resourceName,
                new GroovyResourceStreamHandler(scriptSourceCode));

    }

    private String lookupScriptCodeWithJDBCorWhatever(String resourceName) {

        //TODO: blah blah blah
        return null;
    }

    class GroovyResourceConnection extends URLConnection {

        private final String urlData;

        protected GroovyResourceConnection(URL url, String logic) {
            super(url);
            this.urlData = logic;
        }

        @Override
        public void connect() throws IOException {}

        @Override
        public InputStream getInputStream() throws IOException {
            return new ByteArrayInputStream(urlData.getBytes());
        }
    }

    class GroovyResourceStreamHandler extends URLStreamHandler {

        private final String scriptSource;

        public GroovyResourceStreamHandler(String scriptSource) {
            this.scriptSource = scriptSource;
        }

        @Override
        protected URLConnection openConnection(URL u) throws IOException {
            GroovyResourceConnection connection = new GroovyResourceConnection(u, scriptSource);
            return connection;

        }
    }

}

You then install this thing with some code that looks like this:

GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
groovyClassLoader.setResourceLoader( new CustomGroovyResourceLoader( groovyClassLoader.getResourceLoader() ) );
lscoughlin
  • 2,327
  • 16
  • 23
  • Many, many thanks. I was near to solution, previously I had problem to effective register global url-handler (probably has some difficulties in many threads / many loaders server environments). Your constructor `new URL(null, "groovy:///" + resourceName, new GroovyResourceStreamHandler(scriptSourceCode));` is wanting piece of puzzle. As I understand creates one-time url handler (with url) – Jacek Cz Aug 20 '15 at 11:06
  • No problem, I am to please, and I'm glad it worked out for you. Yes, the custom stream handler and associated objects are attached to, and only live as long as, the URL that's constructed in that method. – lscoughlin Aug 20 '15 at 12:00
  • suplement. An interface `GroovyResourceLoader` hasn't document precise, how `loadGroovySource` returns in negative variant (source not found - null or exception). Few samples in uncle Google return null; – Jacek Cz Aug 25 '15 at 12:22
  • It's not documented in the API doc, but a quick look at the source code of GroovyClassLoader reveals that you return null for "source not found." – lscoughlin Aug 25 '15 at 15:07
  • 1
    perfect answer, saved me lots of time. btw, `URLConnection.getContent()` can be ommitted – injecteer Sep 23 '18 at 13:54