Developers Club geek daily blog

1 year, 2 months ago
Suddenly it turns out that in the project scripts are necessary and there is a question what evolution or revolution is better?
But even attempt to implement gruv can fail in legas the project with conservative collective. And the manual can find ten more of the reasons not to pass a gruva in the project. Though groovy is much simpler and closer to the programmer to aware java, than the same scala.

Java instead of Groovy

But even in this case it is possible to use dynamically compiled scripts in the project. Let's learn to compile java a code dynamically in memory and to start it in jvm, to use in it dynamically loaded libraries from maven. It would be desirable to write as little as possible code for this purpose and that process of use was simplest. And still our pogramma would not like to hope for availability of tools.jar.

Warning indignation from Groovy of specialists, I am recognized that I also love and I use this dynamic programming language and entered the modest contribution to Groovy Grape. Without belittling Groovy advantages, nevertheless we will try to apply java in that area where gruv on jvm out of competition — dynamic compilation, interaction with the existing java a code and dynamic import of dependences (what does Grape).

About compilation in Java. JSR 199


The JSR 199 standard — java Compiler API, exists for a long time. API interfaces are present at java javax.tools packets. *. But to compile java a code from memory in memory and then to start it, it is necessary to write fairly a code and to beat in a tambourine. Implementation of the compiler does not go as a part of JRE and there is no tools.jar in maven repositories.

How to write less with JSR 199


It would be desirable something ready, not to velosipedit every time and the colleague prompted the Janino project. janino contains the compiler of a subset of java and well is suitable only for calculation by a vyrazheniya. There is org.codehaus.janino:commons-compiler-jdk which uses JSR 199, but here only strongly depends on oracle/openjdk tools.jar. After evening of work as a file janino-commons-compiler-ecj (2,3 MB) which was born includes eclipse java compiler and commons-compiler-jdk finished under it. It is self-sufficient and allows to compile and load a code even into JRE. If to add to it mvn-classloader, then in scripts it is possible to do the same magic with dynamic dependences, as well as in Groovy Grape.

For comparison the library for the dynamic mvel2 language (989 CBs) takes all to few times less places, but does not allow to do such simple things as implementation of the interface, determination internal and instantiation of anonymous class, is absent similarity of construction of try/catch/finally and debugging of scripts on it can seem hell.

Example


For compilation of a script on java only dependence com.github.igor-suhorukov:janino-commons-compiler-ecj:1.0 and only 3 lines of a code is necessary:
        SimpleClassPathCompiler simpleCompiler = new SimpleClassPathCompiler(dependenciesUrls);
        simpleCompiler.cook(SCRIPT_NAME+".java", scriptSourceText);
        Class<?> clazz   = simpleCompiler.getClassLoader().loadClass(SCRIPT_NAME);


Not to be unfounded, there is an example of everything about what I tell on github. For its start we will need JVM which supports java8 as in an example of a script there will be Stream API.

So, we will begin:
git clone https://github.com/igor-suhorukov/janino-commons-compiler-ecj-example.git
cd janino-commons-compiler-ecj-example
mvn test


To import the class PhantomJsDowloader from dependence maven com.github.igor-suhorukov:phantomjs-runner:1.1 we will cause MavenClassLoader and we will create classpath to the compiler on the basis of this maven of an artifact:
List<URL> urlsCollection = MavenClassLoader.usingCentralRepo().getArtifactUrlsCollection("com.github.igor-suhorukov:phantomjs-runner:1.1", null);
new SimpleClassPathCompiler(urlsCollection);


Further I provide the text of the main java of the program which loads dependences from a repository, compiles a script and executes it.
janino-commons-compiler-ecj-example/src/test/java/org.codehaus.commons.compiler.jdk/SimpleClassPathCompilerTest.java
package org.codehaus.commons.compiler.jdk;

import com.github.igorsuhorukov.codehaus.plexus.util.IOUtil;
import com.github.igorsuhorukov.smreed.dropship.MavenClassLoader;
import org.junit.Test;

import java.lang.reflect.Method;
import java.net.URL;
import java.util.List;

public class SimpleClassPathCompilerTest {

    @Test
    public void testClassloader() throws Exception {

        final String SCRIPT_NAME = "MyScript";
        List<URL> urlsCollection = MavenClassLoader.usingCentralRepo().getArtifactUrlsCollection("com.github.igor-suhorukov:phantomjs-runner:1.1", null);

        SimpleClassPathCompiler simpleCompiler = new SimpleClassPathCompiler(urlsCollection);
        simpleCompiler.setCompilerOptions("-8");
        simpleCompiler.setDebuggingInformation(true,true,true);

        String src = IOUtil.toString(getClass().getResourceAsStream(String.format("/%s.java", SCRIPT_NAME)));
        simpleCompiler.cook(SCRIPT_NAME+".java", src);

        Class<?> clazz   = simpleCompiler.getClassLoader().loadClass(SCRIPT_NAME);
        Method main = clazz.getMethod("main", String[].class);
        main.invoke(null, (Object) null);
    }

    public static void runIt(){
        System.out.println("DONE!");
    }
}


And it is a script which uses external library from maven and also calls the runIt method of a class which compiled it.
janino-commons-compiler-ecj-example/src/test/resources/MyScript.java
import com.github.igorsuhorukov.phantomjs.PhantomJsDowloader;
import com.github.igorsuhorukov.smreed.dropship.MavenClassLoader;
import org.codehaus.commons.compiler.jdk.SimpleClassPathCompilerTest;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MyScript{

    public static void main(String[] args)  throws Exception{

        class Wrapper{
            private String value;

            public Wrapper(String value) { this.value = value; }

            public String getValue() { return value; }
        }

        SimpleClassPathCompilerTest.runIt();

        List<String> res = Arrays.asList(new Wrapper("do"), new Wrapper("something"), new Wrapper("wrong")).stream().
                                            map(Wrapper::getValue).collect(Collectors.toList());
        System.out.println(String.join(" ",res));

        System.out.println("Classes from project classpath. For example "+MavenClassLoader.class.getName());

        System.out.println(PhantomJsDowloader.getPhantomJsPath());
    }
}


For work of an example the following dependences are necessary
  • com.github.igor-suhorukov:janino-commons-compiler-ecj:1.0 for compilation of java of a code and its dynamic loading.
  • com.github.igor-suhorukov:mvn-classloader:1.3 for dynamic loading of libraries from maven and forming of classpath of the compiler.
  • junit for the test.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.github.igor-suhorukov</groupId>
    <artifactId>janino-commons-compiler-ecj-example</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.github.igor-suhorukov</groupId>
            <artifactId>janino-commons-compiler-ecj</artifactId>
            <version>1.0</version>
            <exclusions><exclusion><groupId>*</groupId><artifactId>*</artifactId></exclusion></exclusions>
        </dependency>
        <dependency>
            <groupId>com.github.igor-suhorukov</groupId>
            <artifactId>mvn-classloader</artifactId>
            <version>1.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>


Script Debug on java


Debugging of a script as normal debugging of java of the program. We place break points and we do not forget to include the debug information at compilation of a script:
simpleCompiler.setDebuggingInformation(true,true,true);



Outputs


We learned to compile java a code from program java, adding of only several lines. Also we are able to include in this script of dependence from maven of repositories and to carry out debugging of a code to IDE.

Approach from article can replace groovy for scripts in the project if requirements do not allow to use anything except java or colleagues hostilely perceive gruv and with it nothing can be made. You can object about AST/metaprogrammirovaniye that you ahead and will be right groovy, in java with it not everything is simple. About work with AST java of the program told in the article "Analysis of Java of the Program by means of Program Java ". We will try to solve a situation with metroprogramming in the following publications.

In spite of the fact that article describes approach on "pure" java and political reasons could influence the choice of such approach in the project, I consider that Java together with Groovy, than "Java instead of Groovy" is better.

This article is a translation of the original post at habrahabr.ru/post/273683/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus