[TOC] ---------------------------------------------------------------------------------------------------- # Intermediate Java code introspection {#intermediate_java_code_introspection} Groovy docs look unfriendly in that they show us the generated intermediate Java class examples, like [, [exact point](http://groovy-lang.org/structure.html#_script_class)], but give us no easy way to see the same about our own Groovy scripts. Here are some possible reasons: - Scripts are meant to be "transparent" --- Groovy's design assumes that users care more about results than about how scripts get turned into classes --- especially for scripting use cases - The transformation is internal: The actual script-to-class conversion is done by the Groovy compiler behind the scenes. It creates a class that extends `groovy.lang.Script` and puts our code inside the `run()` method, but doesn't keep source artifacts unless asked to --- and only partially (e.g., in joint compilation) - No built-in option for "show me the generated class" --- Unlike Scala (`scalac -Xprint:typer`) or Kotlin (`-Xdump-declarations`), Groovy lacks an official `--emit-java` or `--emit-script-class` flag. That makes it harder to introspect Here's the workaround we are going to use: - Compile our Groovy script: `groovyc myscript.groovy` - Run CFR (other decompilers --- like [Fernflower](https://github.com/fesh0r/fernflower) and [JD](https://java-decompiler.github.io/) --- may be also tried) to decompile the `myscript.class` file - Delete the class file to clean up: `del myscript.class` CFR Java decompiler: - Home page: - GitHub: - Download the `cfr-0.152.jar` file that is ready to use Here's a short example. Groovy generated class file `methods.class` is going to be decompiled: ````shell >set CFR_HOME=directory\where\cfr-0.152.jar\is_located\ >java -jar %CFR_HOME%cfr-0.152.jar methods.class > methods.java > del methods.class ```` Here are the most interesting fragments of the result:
project1/methods.java
````wrapped-java /* * Decompiled with CFR 0.152. * * Could not load the following classes: * groovy.lang.Binding * groovy.lang.MetaClass * groovy.lang.Script ... */ import groovy.lang.Binding; import groovy.lang.MetaClass; import groovy.lang.Script; ... public class methods extends Script { ... public methods() { } public methods(Binding context) { super(context); } public static void main(String ... args) { IndyInterface.bootstrap("invoke", "runScript", 0, InvokerHelper.class, methods.class, args); } public Object run() { IndyInterface.bootstrap("invoke", "println", 2, this, IndyInterface.bootstrap("invoke", "sum", 2, this, 7, 4)); return IndyInterface.bootstrap("invoke", "println", 2, this, IndyInterface.bootstrap("invoke", "sum", 2, this, 7)); } public Object sum(int a, int b) { return IndyInterface.bootstrap("invoke", "plus", 0, a, b); } ... } ```` ## Additional considerations Our script may create multiple classes, like with closures: - `methods.class` - `methods$_run_closure1.class` - etc. In such cases we may want to decompile all of them in one go: ````shell java -jar cfr-0.152.jar *.class ```` See the output of: - `java -jar %CFR_HOME%cfr-0.152.jar --help` - `groovyc --help` to know how to specify the input and output directories for better code organization. Also see the following options to control the decompilation process: ```` --hidebridgemethods true --innerclasses false --decodelambdas false --comments false ```` Here are explanations given by Chat GPT []: - `--hidebridgemethods` --- suppresses synthetic bridge methods (Java/Groovy creates these for type compatibility) - `--innerclasses` --- prevents CFR from showing nested/inner classes inline in the parent class - `--decodelambdas` --- avoids reconstructing lambdas and closures as if they were written inline (useful if Groovy uses `invokedynamic`) - `--comments` --- suppresses extra helpful comments added by CFR, so it looks cleaner ---------------------------------------------------------------------------------------------------- # Running by Java runtime {#running_by_java_runtime} As it's shown [above](#groovyc_usage) Groovy compiler can compile a normal Java class file. Starting from Groovy 3.0+, compiled script classes include a generated `public static void main(String... args)` method, so it may be executed by a Java runtime. Still Groovy runtime JARs (`groovy-*.jar`) need to be added to the class path: ````shell >type hello_world.groovy println "Hello world!" >groovyc hello_world.groovy >dir /b | find "hello_world.class" hello_world.class >java -cp ".;%GROOVY_HOME%\lib\*" hello_world Hello world! >del hello_world.class ```` The `GROOVY_HOME` environment variable should point to the Groovy installation directory (or the certain path may be used).