Extended GroovyClassloader and override loadclass method
If I make lookupScriptFiles "true" in the loadClass() method the script run and doesn't require an import statement referencing a groovy class in a different package
i have extended GroovyClassloader, and override the loadclass method, in the loadclass the argument lookupScriptFiles =true
When this is true, it sucessfully compiles even first.groovy don't have import statement
when lookupScriptFiles=false it throws compilation error as expected.
my source code snippet
C:\>cat first.groovy
def cos=new Second()
==============================================================
C:>cat Second.groovy
package com.test
class Second
{
Second()
{
println "Anish"
}
}
=========================================================
C:\bin>echo %CLASSPATH%
C:\zGroovy\bin;C:\vsexclude\opt\groovy-1.7.2\embeddable\groovy-all-1.7.2.jar
===============================================
C:\vsexclude\trees\bac-4.2\workspace\zGroovy\bin>java GCtest
path------>>C:\first.groovy
Anish
=================================
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilerConfiguration;
/**
* #author Anish
*/
public class GCloader extends GroovyClassLoader {
public GCloader(ClassLoader parent) {
super(parent, new CompilerConfiguration());
}
#Override
public Class<?> loadClass(final String name, boolean lookupScriptFiles,
boolean preferClassOverScript, boolean resolve)
throws ClassNotFoundException, CompilationFailedException {
//return loadFiles(name, true, preferClassOverScript, resolve);
return super.loadClass(name, true,
preferClassOverScript, resolve);
}
}
Assuming your question is:
If I set lookupScriptFiles to true, can I remove the import statements from my groovy scripts?
Then the answer is no. The classloader will try to lookup scripts it doesn't know about, but you will still need to imports to tell it in which packages to look for each class
Update
So, you have two groovy files in the same directory, one of which you have arbitrarily added a package statement to.
I assume you are loading the classes straight from scripts (yet another thing you don't say in your question)
If this is the case, then you will need to tell the classloader to lookup the other scripts to compile to classes.
If you don't -- as you have seen -- it will not work (imports or no imports)
However, putting two groovy files in the same folder, and just adding a package line to one of them is awful coding practice, and I'm surprised you got anything working
Related
I am writing a node app in typescript. I have written a class decorator #myDecorator, and the purpose of #myDecorator is such that I need to keep track of all the classes to which it's applied.
My question: how do I make sure all of those decorated classes are loaded before making use of that behavior? Some example code will help to make this more concrete:
Declaration of the decorator in file myDecorator.ts:
type ConstructorType = { new (...args: any[]): any };
// keep track of decorated classes
const registeredClasses: Map<string, ConstructorType> = new Map();
// class decorator
export function myDecorator<T extends ConstructorType>(targetConstructor: T) {
// create the modified class
const newClass = class extends targetConstructor { /* ... */ }
// register the modified class
registeredClasses.set(targetConstructor.name, newClass);
// and return it
return newClass;
}
export function doSomethingWithMyDecoratedClasses() {
//... some behavior that relies on `registeredClasses`
}
Declaration of a decorated class in file someClass.ts
import {myDecorator} from 'myDecorator.ts'
#myDecorator
class SomeClass { /* ... */ }
Making use of doSomethingWithMyDecoratedClasses in anotherFile.ts:
import { doSomethingWithMyDecoratedClasses } from 'myDecorator.ts`
//...
doSomethingWithMyDecoratedClasses()
//...
The problem is that I need to make sure that SomeClass has been added to registeredClasses before I make this call to doSomethingWithMyDecoratedClasses. And, in fact, I've written a number of such classes in my app, and I need to make sure they are all registered.
My current best understanding is that I need to call import 'someClass.ts' in anotherFile.ts (and, in fact, import all files where decorated classes are declared), so really I need to import someClass1.ts, import someClass2.ts, ...
Is that the best/only approach? Is there a recommended pattern for doing so?
Most applications have an index file that is responsible for importing the top level things. If you import doSomethingWithMyDecoratedClasses there, you'll guarantee that everything else is imported first.
An alternative would be to not call it in the root level of a module, and instead wait for an event.
Just starting a new Gradle project.
This test passes:
def 'Launcher.main should call App.launch'(){
given:
GroovyMock(Application, global: true)
when:
Launcher.main()
then:
1 * Application.launch( App, null ) >> null
}
... until, to get another test using a (Java) Mock to work, I have to add these dependencies:
testImplementation 'net.bytebuddy:byte-buddy:1.10.8'
testImplementation 'org.objenesis:objenesis:3.1'
(NB I assume these versions are OK for Groovy 3.+, which I'm now using ... both are the most up-to-date available at Maven Repo).
With these dependencies the above test fails:
java.lang.InstantiationError: javafx.application.Application
at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:48)
at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:73)
at org.objenesis.ObjenesisHelper.newInstance(ObjenesisHelper.java:44)
at org.spockframework.mock.runtime.MockInstantiator$ObjenesisInstantiator.instantiate(MockInstantiator.java:45)
at org.spockframework.mock.runtime.MockInstantiator.instantiate(MockInstantiator.java:31)
at org.spockframework.mock.runtime.GroovyMockFactory.create(GroovyMockFactory.java:57)
at org.spockframework.mock.runtime.CompositeMockFactory.create(CompositeMockFactory.java:42)
at org.spockframework.lang.SpecInternals.createMock(SpecInternals.java:47)
at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:298)
at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:288)
at org.spockframework.lang.SpecInternals.GroovyMockImpl(SpecInternals.java:215)
at core.AppSpec.Launcher.main should call App.launch(first_tests.groovy:30)
I confess that I have only the sketchiest notion of what "bytebuddy" and "objenesis" actually do, although I assume it is fiendishly clever. Edit: having just visited their respective home pages my notion is now slightly less sketchy, and yes, it is fiendishly clever.
If an orthodox solution to this is not available, is it by any chance possible to turn off the use of these dependencies for an individual feature (i.e. test)? Possibly using some annotation maybe?
Edit
This is an MCVE:
Specs: Java 11.0.5, OS Linux Mint 18.3.
build.gradle:
plugins {
id 'groovy'
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
repositories { mavenCentral() }
javafx {
version = "11.0.2"
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
dependencies {
implementation 'org.codehaus.groovy:groovy:3.+'
testImplementation 'junit:junit:4.12'
testImplementation 'org.spockframework:spock-core:2.0-M2-groovy-3.0'
testImplementation 'net.bytebuddy:byte-buddy:1.10.8'
testImplementation 'org.objenesis:objenesis:3.1'
// in light of kriegaex's comments:
implementation group: 'cglib', name: 'cglib', version: '3.3.0'
}
test { useJUnitPlatform() }
application {
mainClassName = 'core.Launcher'
}
installDist{}
main.groovy:
class Launcher {
static void main(String[] args) {
Application.launch(App, null )
}
}
class App extends Application {
void start(Stage primaryStage) {
}
}
first_tests.groovy:
class AppSpec extends Specification {
def 'Launcher.main should call App.launch'(){
given:
GroovyMock(Application, global: true)
when:
Launcher.main()
then:
1 * Application.launch( App, null ) >> null
}
}
The reason why this project needs something to call the Application subclass is explained here: it's so that it is possible to do an installDist which bundles in JavaFX.
Don't we have to use a global GroovyMock?
If you want to check the interaction, yes. But actually you are testing the JavaFX launcher rather than your application. So I doubt that there is any benefit. I would focus on testing the App class instead. Also imagine for a moment that you would write the classes with main methods in Java instead of Groovy. Groovy mocks would not work when called from Java code, especially not global ones. Then you would end up testing via Powermockito from Spock, which would also work but still you would test the JavaFX launcher rather than your application.
Also isn't it slightly extreme to say any use of Groovy mocks is wrong?
I did not say that. I said: "probably something is wrong with your application design". The reason I said that is because the use of Groovy mocks and things like mocking static methods are test code smells. You can check the smell and then decide it is okay, which IMO in most cases it is not. Besides, instead of application design the problem can also be in the test itself, which in this case I would say it is. But that is arguable, so I am going to present a solution to you further below.
In this case technically the global Application mock is your only way if you do insist to test the JavaFX launcher because even a global mock on App would not work as the launcher uses reflection in order to call the App constructor and that is not intercepted by the mock framework.
you say that Spock spock-core:2.0-M2-groovy-3.0 is a "pre-release". I can't see anything on this page (...) which says that. How do you know?
You found out already by checking out the GitHub repository, but I was just seeing it in the unusual version number containing "M2" like "milestone 2" which is similar to "RC" (or "CR") for release candidates (or candidate releases).
As for the technical problem, you can either not declare Objenesis in your Gradle script because it is an optional dependency, then the test compiles and runs fine, as you already noticed yourself. But assuming you need optional dependencies like Objenesis, CGLIB (actually cglib-nodep), Bytebuddy and ASM for other tests in your suite, you can just tell Spock not to use Objenesis in this case. So assuming you have a Gradle build file like this:
plugins {
id 'groovy'
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
repositories { mavenCentral() }
javafx {
version = "11.0.2"
modules = ['javafx.controls', 'javafx.fxml']
}
dependencies {
implementation 'org.codehaus.groovy:groovy:3.+'
testImplementation 'org.spockframework:spock-core:2.0-M2-groovy-3.0'
// Optional Spock dependencies, versions matching the ones listed at
// https://mvnrepository.com/artifact/org.spockframework/spock-core/2.0-M2-groovy-3.0
testImplementation 'net.bytebuddy:byte-buddy:1.9.11'
testImplementation 'org.objenesis:objenesis:3.0.1'
testImplementation 'cglib:cglib-nodep:3.2.10'
testImplementation 'org.ow2.asm:asm:7.1'
}
test { useJUnitPlatform() }
application {
mainClassName = 'de.scrum_master.app.Launcher'
}
installDist {}
My version of your MCVE would looks like this (sorry, I added my own package names and also imports because otherwise it is not really an MCVE):
package de.scrum_master.app
import javafx.application.Application
import javafx.scene.Scene
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
import javafx.stage.Stage
class App extends Application {
#Override
void start(Stage stage) {
def javaVersion = System.getProperty("java.version")
def javafxVersion = System.getProperty("javafx.version")
Label l = new Label("Hello, JavaFX $javafxVersion, running on Java $javaVersion.")
Scene scene = new Scene(new StackPane(l), 640, 480)
stage.setScene(scene)
stage.show()
}
}
package de.scrum_master.app
import javafx.application.Application
class Launcher {
static void main(String[] args) {
Application.launch(App, null)
}
}
package de.scrum_master.app
import javafx.application.Application
import spock.lang.Specification
class AppSpec extends Specification {
def 'Launcher.main should call App.launch'() {
given:
GroovyMock(Application, global: true, useObjenesis: false)
when:
Launcher.main()
then:
1 * Application.launch(App, null)
}
}
The decisive detail here is the useObjenesis: false parameter.
Update: Just for reference, this is how you would do it with a launcher class implemented in Java using PowerMockito.
Attention, this solution needs the Sputnik runner from Spock 1.x which was removed in 2.x. So in Spock 2 this currently does not work because it is based on JUnit 5 and can no longer use #RunWith(PowerMockRunner) and #PowerMockRunnerDelegate(Sputnik) because PowerMock currently does not support JUnit 5. But I tested it with Spock 1.3-groovy-2.5 and Groovy 2.5.8.
package de.scrum_master.app
import javafx.application.Application
import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.spockframework.runtime.Sputnik
import spock.lang.Specification
import static org.mockito.Mockito.*
import static org.powermock.api.mockito.PowerMockito.*
#RunWith(PowerMockRunner)
#PowerMockRunnerDelegate(Sputnik)
#PrepareForTest(Application)
class JavaAppSpec extends Specification {
def 'JavaLauncher.main should launch JavaApp'() {
given:
mockStatic(Application)
when:
JavaLauncher.main()
then:
verifyStatic(Application, times(1))
Application.launch(JavaApp)
}
}
The following two classes in the same package:
Imported.groovy
class Imported {
static class Inner {
}
}
Main.groovy
import Imported
class Main {
static main(args) {
new Imported.Inner()
}
}
When run:
$ groovy Main.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/tmp/Main.groovy: 5: unable to resolve class Imported.Inner
# line 5, column 5.
new Imported.Inner()
^
1 error
Any reason this is happening? How to properly import static nested classes?
Just compile Imported.groovy: groovyc Imported.groovy so that you have Imported.class and Imported$Inner.class.
Then simply invoke groovy Main.groovy and it should work.
If you want to have some "import/include" functionality, check Including a groovy script in another groovy and how to simply import a groovy file in another groovy script.
EDIT: GROOVY-6932 was logged and has now been closed - the problem is marked as fixed in Groovy v2.4.8.
I use the #Slf4j annotation to add a logging to my groovy classes.
I like it because of the AST transformation that wraps my log calls inside an "enablement" check, documented here
What I've found though, is that the guard clause doesn't work if the log method is called from within a closure.
Running on Groovy 2.2.0, this code logs only one message, but prints "called" twice.
#Grapes([
#Grab(group='org.slf4j', module='slf4j-api', version='1.7+'),
#Grab(group='ch.qos.logback', module='logback-classic', version='1.+')])
import groovy.util.logging.Slf4j
new TestCode().doSomethingThatLogs()
#Slf4j
class TestCode {
void doSomethingThatLogs(){
log.info createLogString(1)
log.trace createLogString(2)
Closure c = { log.trace createLogString(3) }
c()
}
String createLogString(int p){
println "called with $p"
return "blah: $p"
}
}
I've tried adding "this", "owner" and "delegate" specifiers to the log statement but same result.
The context I found this in was when I was trying to log some nodes using XmlUtil.serialize while parsing XML with XmlSlurper.
I'm working around the problem by wrapping the NodeChild object in a lightweight object with a toString() method that calls the XmlUtil class. That'll work fine and the overhead of the extra wrapper classes is not worth worrying about. I'm more interested in finding a simple way around this problem so that I can go back to not having to think about the cost of construction of logging messages when they aren't being logged anyway.
The question I'm asking: is there any way to have the logging level guard clause work properly within closures, instead of having to add the "log.traceEnabled" calls myself?
Just a quick thought. How will you know what log.traceEnabled will be on closure execution? See the modified example ...
#Grapes([
#Grab(group='org.slf4j', module='slf4j-api', version='1.7+'),
#Grab(group='ch.qos.logback', module='logback-classic', version='1.+')])
import groovy.util.logging.Slf4j
import jline.internal.Log
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
new TestCode().doSomethingThatLogs()
#Slf4j
class TestCode {
void doSomethingThatLogs(){
log.info createLogString(1)
log.trace createLogString(2)
Closure c = { log.trace createLogString(3) }
Logger root = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.setLevel(Level.TRACE);
c()
}
String createLogString(int p){
println "called with $p"
return "blah: $p"
}
}
Now at the time the closure is created log.traceEnabled is false, but when the closure is executed this had changed.
The output is:
called with 1
16:53:15.408 [main] INFO TestCode - blah: 1
called with 3
16:53:15.414 [main] TRACE TestCode - blah: 3
Note the second TRACE is still not printed :-)
As new to groovy...
I'm trying to replace the java idiom for event listeners, filters, etc.
My working code in groovy is the following:
def find() {
ODB odb = ODBFactory.open(files.nodupes); // data nucleus object database
Objects<Prospect> src = odb.getObjects(new QProspect());
src.each { println it };
odb.close();
}
class QProspect extends SimpleNativeQuery {
public boolean match(Prospect p) {
if (p.url) {
return p.url.endsWith(".biz");
}
return false;
}
}
Now, this is far from what I'm used to in java, where the implementation of the Query interface is done right inside the odb.getObjects() method. If I where to code "java" I'd probably do something like the following, yet it's not working:
Objects<Prospect> src = odb.getObjects( {
boolean match(p) {
if (p.url) {
return p.url.endsWith(".biz");
}
return false;
}
} as SimpleNativeQuery);
Or better, I'd like it to be like this:
Objects<Prospect> src = odb.getObjects(
{ it.url.endsWith(".biz") } as SimpleNativeQuery
);
However, what groovy does it to associate the "match" method with the outer script context and fail me.
I find groovy... groovy anyways so I'll stick to learning more about it. Thanks.
What I should've asked was how do we do the "anonymous" class in groovy. Here's the java idiom:
void defReadAFile() {
File[] files = new File(".").listFiles(new FileFilter() {
public boolean accept(File file) {
return file.getPath().endsWith(".biz");
}
});
}
Can groovy be as concise with no additional class declaration?
I think it would have helped you to get answers if you'd abstracted the problem so that it didn't rely on the Neodatis DB interface -- that threw me for a loop, as I've never used it. What I've written below about it is based on a very cursory analysis.
For that matter, I've never used Groovy either, though I like what I've seen of it. But seeing as no one else has answered yet, you're stuck with me :-)
I think the problem (or at least part of it) may be that you're expecting too much of the SimpleNativeQuery class from Neodatis. It doesn't look like it even tries to filter the objects before it adds them to the returned collection. I think instead you want to use org.neodatis.odb.impl.core.query.criteria.CriteriaQuery. (Note the "impl" in the package path. This has me a bit nervous, as I don't know for sure if this class is meant to be used by callers. But I don't see any other classes in Neodatis that allow for query criteria to be specified.)
But instead of using CriteriaQuery directly, I think you'd rather wrap it inside of a Groovy class so that you can use it with closures. So, I think a Groovy version of your code with closures might look something like this:
// Create a class that wraps CriteriaQuery and allows you
// to pass closures. This is wordy too, but at least it's
// reusable.
import org.neodatis.odb.impl.core.query.criteria;
class GroovyCriteriaQuery extends CriteriaQuery {
private final c;
QProspect(theClosure) {
// I prefer to check for null here, instead of in match()
if (theClosure == null) {
throw new InvalidArgumentException("theClosure can't be null!");
}
c = theClosure;
}
public boolean match(AbstractObjectInfo aoi){
//!! I'm assuming here that 'aoi' can be used as the actual
//!! object instance (or at least as proxy for it.)
//!! (You may have to extract the actual object from aoi before calling c.)
return c(aoi);
}
}
// Now use the query class in some random code.
Objects<Prospect> src = odb.getObjects(
new GroovyCriteriaQuery(
{ it.url.endsWith(".biz") }
)
)
I hope this helps!
I believe your real question is "Can I use closures instead of anonymous classes when calling Java APIs that do not use closures". And the answer is a definite "yes". This:
Objects<Prospect> src = odb.getObjects(
{ it.url.endsWith(".biz") } as SimpleNativeQuery
);
should work. You write "However, what groovy does it to associate the "match" method with the outer script context and fail me". How exactly does it fail? It seems to me like you're having a simple technical problem to get the solution that is both "the groovy way" and exactly what you desire to work.
Yep, thanks y'all, it works.
I also found out why SimpleNativeQuery does not work (per Dan Breslau).
I tried the following and it worked wonderfully. So the idiom does work as expected.
new File("c:\\temp").listFiles({ it.path.endsWith(".html") } as FileFilter);
This next one does not work because of the neodatis interface. The interface does not enforce a match() method! It only mentions it in the documentation yet it's not present in the class file:
public class SimpleNativeQuery extends AbstactQuery{
}
Objects<Prospect> src = odb.getObjects(
{ it.url.endsWith(".biz") } as SimpleNativeQuery
);
In the above, as the SimpleNativeQuery does not have a match() method, it makes it impossible for the groovy compiler to identify which method in the SimpleNativeQuery should the closure be attached to; it then defaults to the outer groovy script.
It's my third day with groovy and I'm loving it.
Both books are great:
- Groovy Recipes (Scott Davis)
- Programming Groovy (Venkat Subramaniam)