I really like the groovy language but I believe my java experience is leading me to a path where I don't use groovys fully potential.
I have a scenario where a groovy object (Master.groovy) sends some json-data to some view that creates a form out of this data. Then the user can submit this form and Master.groovy receives it as json-data again.
Master.groovy can access a pool of different form descriptions (formDesc1, formDesc2, etc.) which it choses from when sending the json to the view. When the user returns the json data - Master.groovy will then pass it over to e.g. formDesc1 to validate it.
This is how it looks with code:
Master.groovy
class Master {
FormDesc[] formDescs = [new FormDesc1(), new FormDesc2(), new FormDesc2()]
def getJSONform(Integer formId){
return formDescs[formId].getForm()
}
def validateForm(Integer formId, def data) {
return formDescs[formId].validate(data)
}
}
FormDesc1.groovy
class FormDesc1 extends FormDesc {
static String name = "alias"
FormDesc1 (){
form.add(new InputElement(name:name, type:"text"))
form.add(new TextArea(name:"msg", rows:"12", cols:"12"))
}
#Override
def validate(data){
errors = []
if(data.get(name)["value"] != "form1")
errors.add("name not allowed")
return errors
}
}
FormDesc2.groovy
class FormDesc2 extends FormDesc {
static String name = "alias"
FormDesc1 (){
form.add(new InputElement(name:name, type:"text"))
form.add(new TextArea(name:"msg", rows:"12", cols:"12"))
}
#Override
def validate(data){
errors = []
if(data.get(name)["value"] != "form1")
errors.add("name not allowed")
return errors
}
}
There can be many forms in the package /forms and to load all of them in the memory seems very unnecessary. And it would be much nicer if the formDesc consisted of json-data that represented the form and then only has the method validate. E.g:
FormDescUltimate.groovy
class FormDescUltimate extends FormDesc {
form = loadJSONFile("formDescUltimate.json")
#Override
def validate(data){
errors = []
if(data.get("alias")["value"] != "form1")
errors.add("name not allowed")
return errors
}
}
formDescUltimate.json
{
'formId' : 'ultimate_form',
'elements': [
{'InputElement' : {'name': 'alias', 'type':'text'}},
{'TextArea' : {'name': 'msg', 'rows':'12', 'cols':'12'}}
]
}
Is it possible to achieve this nicely with groovy, or is there any better design to accomplish this?
Thanks in advance and sorry for the long post!
Related
I am working on Android Application in Which I am getting specific Data from Room Database by specific path in the Storage. My App Got Crashes as It does not have Any Data in the Storage and the Logcat gives me this..
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
at androidx.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:154)
at androidx.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:135)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onOpen(FrameworkSQLiteOpenHelper.java:195)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:428)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:317)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)
at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
at com.maximus.technologies.views.activities.scanneddatabase.TodoDaoScanned_Impl.getAllScan(TodoDaoScanned_Impl.java:152)
at com.maximus.technologies.views.fragments.scanhistorypackage.QRRetrievingScanClassPresenter$getAllDatFromDatabase$1.invokeSuspend(QRRetrievingScanClassPresenter.kt:29)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
The Above Error or crash Only occurs as the app dont have any data in Storage. But as I put a Data the Crash Problem Get Resolved.
I am not able to Understand what the Problem actually is...
Here is My Room Database Class..
#Database(
entities = [TodoEntity::class,TodoEntityScanned::class],
version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun TodoDao(): TodoDao
abstract fun TodoDaoScanned(): TodoDaoScanned
object DatabaseBuilder {
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
if (INSTANCE == null) {
synchronized(AppDatabase::class) {
INSTANCE = buildRoomDB(context)
}
}
return INSTANCE!!
}
private fun buildRoomDB(context: Context) =
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"mindorks-example-coroutines"
).build()
}
}
Room Database Retrieving Interface where app Crashes on getall()
override fun getAllDatFromDatabase(appDatabasescanned: AppDatabase) {
var list = listOf<TodoEntityScanned>()
try {
GlobalScope.launch(Dispatchers.Default) {
list = appDatabasescanned.TodoDaoScanned().getAllScan()
Log.d("hello","hello")
mView.showAllData(list)
}
}
catch (e:Exception){
Log.d("get hello",e.toString())
}
}
The getAll lies in Dao Class
interface TodoDao {
#Query("SELECT * FROM tablefilepaths")
fun getAll(): List<TodoEntity>
#Query("SELECT * FROM tablefilepaths WHERE imagespath LIKE :title")
fun findByTitle(title: String): TodoEntity
#Insert
fun insertpaths(todo: TodoEntity)
#Delete
fun deletepaths(todo: TodoEntity)
#Query("DELETE FROM tablefilepaths WHERE id = :noteId")
fun deleteNoteById(noteId: Int)
#Update
fun updateTodo(vararg todos: TodoEntity)}
Here is My Fragment Class Where I am Setting data in RecyclerView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerviewcreatehistory?.layoutManager = LinearLayoutManager(context)
recyclerviewcreatehistory?.setHasFixedSize(true)
filefetch()
customAdaptercreatehistory = CustomAdapterCreateHistory(this.context ?: return, charItemcreate!!,this)
recyclerviewcreatehistory?.adapter = customAdaptercreatehistory
}
fun filefetch() {
val noteDatabase: AppDatabase = AppDatabase.DatabaseBuilder.getInstance(requireContext())
retrivingpresenter = QRRetrievingClassPresenter(this)
retrivingpresenter!!.getAllDatFromDatabase(noteDatabase)
}
override fun showAllData(note_list: List<TodoEntity>) {
if (note_list is ArrayList<*>) {
val arraylist = note_list as ArrayList<TodoEntity>
charItemcreate=arraylist
}
if (charItemcreate.isEmpty()){
}else{
customAdaptercreatehistory?.updateUsers(note_list as ArrayList<TodoEntity>)
customAdaptercreatehistory?.notifyDataSetChanged()
// Log.d("hello", note_list[0].imagesPathData)
}
}
You have to do some checks in your getAllDatFromDatabase() inside your coroutine. I guess list variable equals null or something like that. You should check if there are any files and if not you need to put there something else.
I try to understand more about how Geb and Spock work internally to understand what is really happening in my tests.
I found that GebSpec which I extend to write my tests has a field Browser _browser.
I also found that GebSpec has a method getBrowser() which returns _browser, so _browser can be accessed over getBrowser() and get_browser(). But the interesting part is that while debugging in intelliJ expanding an instance of GebSpec shows no field _browser but only a field $spock_sharedField__browser.
A little example:
Debugging my Class: The instance of GebSpec has a field spock_sharedField__browser but no field _browser
How do they manage to hide the _browser field from me in the debugger and why do they do it?
Recall a field Browser _browser is declared in GebSpec and a field $spock_sharedField__browser is not.
There also is no method get$spock_sharedField__browser() but I still can access and manipulate $spock_sharedField__browser.
I tried to do it myself:
I wrote a class TestClass that declares _browser exactly analog to GebSpec, but if I debug here the field _browser is shown normally as one would expect
Can someone explain me what is going on?
Why hide _browser?
What is $spock_sharedField__browser good for?
UPDATE: I think the following code describes summarizes it pretty good:
import geb.spock.GebSpec
class GebHomeSpec extends GebSpec{
def "test Geb homepage"(){
when:
['get$spock_sharedField__browser', 'getBrowser', 'get_browser'].each {
try {
println this."${it}"()
} catch (MissingFieldException e) {
println e
}
}
['$spock_sharedField__browser', 'browser', '_browser'].each {
try {
println this.getMetaClass().getAttribute(this, it)
} catch (MissingFieldException e){
println e
}
}
then:
true
}
}
The result on the console is:
null
geb.Browser#352ff4da
geb.Browser#352ff4da
null
groovy.lang.MissingFieldException: No such field: browser for class: GebHomeSpec
groovy.lang.MissingFieldException: No such field: _browser for class: GebHomeSpec
My interpretation, considering the answer of kriegaex, is that in the Compilation during the Spock transformation the field $spock_sharedField__browser is declared and the field _browser is removed. The field browser never existed. But there are still getters for browser and _browser. I wonder where there get their data from (in this case geb.Browser#352ff4da) as none of the field exists anymore as the exceptions show. At least it matches with the debugging information (c.f. first picture/link) that shows the field $spock_sharedField__browser but neither a field _browser nor a field browser.
Finally I noticed (and I dont really know how to explain that) the getters for _browser and browser are outside of the class no longer available (see below). I thought the concept of private is not implemented in groovy and making getters private makes no sense to me anyways.
import geb.spock.GebSpec
class Main {
static void main(String[] args) {
GebSpec gebSpec = new GebSpec()
['get$spock_sharedField__browser', 'getBrowser', 'get_browser'].each {
try {
println gebSpec."${it}"()
} catch (MissingFieldException e) {
println e
}
}
['$spock_sharedField__browser', 'browser', '_browser'].each {
try {
println gebSpec.getMetaClass().getAttribute(gebSpec, it)
} catch (MissingFieldException e){
println e
}
}
}
}
This leads to
null
groovy.lang.MissingFieldException: No such field: $spock_sharedField__browser for class: org.codehaus.groovy.runtime.NullObject
groovy.lang.MissingFieldException: No such field: $spock_sharedField__browser for class: org.codehaus.groovy.runtime.NullObject
null
groovy.lang.MissingFieldException: No such field: browser for class: geb.spock.GebSpec
groovy.lang.MissingFieldException: No such field: _browser for class: geb.spock.GebSpec
All in all I find this rather confusing and I wonder what this is good for. Why introduce $spock_sharedField__browser and remove _browser?
If you use IntelliJ IDEA, you can just decompile the GebSpec class and will see something like this (this is what the Groovy compiler really produced when it compiled the library class):
public class GebSpec extends Specification implements GroovyObject {
// ...
#Shared
#FieldMetadata(
line = 29,
name = "_browser",
ordinal = 2
)
protected volatile Browser $spock_sharedField__browser;
// ...
public Browser createBrowser() {
CallSite[] var1 = $getCallSiteArray();
return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass() ? (Browser)ScriptBytecodeAdapter.castToType(var1[8].callConstructor(Browser.class, this.createConf()), Browser.class) : (Browser)ScriptBytecodeAdapter.castToType(var1[6].callConstructor(Browser.class, var1[7].callCurrent(this)), Browser.class);
}
public Browser getBrowser() {
CallSite[] var1 = $getCallSiteArray();
if (BytecodeInterface8.isOrigZ() && !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()) {
if (ScriptBytecodeAdapter.compareEqual(var1[11].callGroovyObjectGetProperty(this), (Object)null)) {
Browser var3 = this.createBrowser();
ScriptBytecodeAdapter.setGroovyObjectProperty(var3, GebSpec.class, this, (String)"_browser");
}
} else if (ScriptBytecodeAdapter.compareEqual(var1[9].callGroovyObjectGetProperty(this), (Object)null)) {
Object var2 = var1[10].callCurrent(this);
ScriptBytecodeAdapter.setGroovyObjectProperty((Browser)ScriptBytecodeAdapter.castToType(var2, Browser.class), GebSpec.class, this, (String)"_browser");
}
return (Browser)ScriptBytecodeAdapter.castToType(var1[12].callGroovyObjectGetProperty(this), Browser.class);
}
// ...
public Browser get$spock_sharedField__browser() {
return this.$spock_sharedField__browser;
}
public void set$spock_sharedField__browser(Browser var1) {
this.$spock_sharedField__browser = var1;
}
}
I think you have dived deep enough already to understand without further explanation.
Update: I forgot to mention: Your test class does not inherit GebSpec (which again inherits from Specification, i.e. the code will not be transformed by Spock/Geb because it has the wrong base class. If you do this, though:
package de.scrum_master.stackoverflow
import geb.spock.GebSpec
import spock.lang.Shared
class FooIT extends GebSpec {
#Shared
def myField
def test() {
expect:
true
}
}
Then the decompiled code will be:
package de.scrum_master.stackoverflow;
import geb.spock.GebSpec;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.spockframework.runtime.ErrorCollector;
import org.spockframework.runtime.SpockRuntime;
import org.spockframework.runtime.ValueRecorder;
import org.spockframework.runtime.model.BlockKind;
import org.spockframework.runtime.model.BlockMetadata;
import org.spockframework.runtime.model.FeatureMetadata;
import org.spockframework.runtime.model.FieldMetadata;
import org.spockframework.runtime.model.SpecMetadata;
import spock.lang.Shared;
#SpecMetadata(
filename = "FooIT.groovy",
line = 6
)
public class FooIT extends GebSpec {
#Shared
#FieldMetadata(
line = 7,
name = "myField",
ordinal = 0
)
protected volatile Object $spock_sharedField_myField;
public FooIT() {
CallSite[] var1 = $getCallSiteArray();
}
#FeatureMetadata(
line = 10,
name = "test",
ordinal = 0,
blocks = {#BlockMetadata(
kind = BlockKind.EXPECT,
texts = {}
)},
parameterNames = {}
)
public void $spock_feature_1_0() {
CallSite[] var1 = $getCallSiteArray();
ErrorCollector $spock_errorCollector = (ErrorCollector)ScriptBytecodeAdapter.castToType(var1[2].callConstructor(ErrorCollector.class, false), ErrorCollector.class);
ValueRecorder $spock_valueRecorder = (ValueRecorder)ScriptBytecodeAdapter.castToType(var1[3].callConstructor(ValueRecorder.class), ValueRecorder.class);
Object var10000;
try {
try {
SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "true", Integer.valueOf(12), Integer.valueOf(5), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), true));
var10000 = null;
} catch (Throwable var13) {
SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "true", Integer.valueOf(12), Integer.valueOf(5), (Object)null, var13);
var10000 = null;
} finally {
;
}
var1[4].call(var1[5].call(this.getSpecificationContext()));
} finally {
$spock_errorCollector.validateCollectedErrors();
var10000 = null;
}
}
public Object get$spock_sharedField_myField() {
return this.$spock_sharedField_myField;
}
public void set$spock_sharedField_myField(Object var1) {
this.$spock_sharedField_myField = var1;
}
}
Update 2:
As for your additional questions, I can only speculate about the answers, I am sure users like #erdi (Geb maintainer), #Szymon Stepniak, #Leonard Brünings (who seem to be Groovy cracks, which I am not) could say more about it, but OTOH this is not a discussion forum and the questions are not particularly well suited for SO. Anyway, I edited the question tags to include "groovy" so as to maybe raise their attention.
Why introduce $spock_sharedField__browser and remove _browser?
I think it is just the result of Spock's way of transforming the #Shared annotation into a member variable with named so as to very unlikely collide with any existing member names. You also see this happening in the decompiled version of my own Spock/Geb specification.
But there are still getters for browser and _browser.
Of course there is a getter for browser, as in the Geb DLS you usually don't look behind the scenes but just use the syntactic sugar browser to access the browser instance. This Groovy-ism will call getBrowser(), as you probably know. This particular getter is declared explicitly in the GebSpec class in order to make the member conveniently accessible (you also see some lazy browser instantiation logic here):
Browser getBrowser() {
if (_browser == null) {
_browser = createBrowser()
}
_browser
}
I wonder where there get their data from (in this case geb.Browser#352ff4da) as none of the field exists anymore as the exceptions show.
I do not know enough about Groovy's dynamic language features to answer that, but you can see the actual mechanics in my decompiled code snippets.
Accessing Spock-specific class members from outside a running specification obviously does not work and probably is not meant to be. But if you run this test, it works just fine:
package de.scrum_master.stackoverflow
import geb.spock.GebSpec
import spock.lang.Shared
class FooIT extends GebSpec {
#Shared
def myField = "foo"
def test() {
given:
println browser
println myField
expect:
true
}
}
Console log:
geb.Browser#1722011b
foo
I would like to overwrite a dynamic property on a metaClass but it's not working as I would expect. Here is a simple example that demonstrate the problem. You can run it online at Groovy web console
class Bike {}
class Bell { def ring() { println("calling " + this) } }
def createNamespaces = {
return [ "bell": new Bell() ]
}
def resetNamespaces = { bike ->
// setting to null means 'set it to the default'
bike.metaClass = null
createNamespaces().each { name, namespace ->
println("setting " + namespace)
bike.metaClass."$name" = namespace
}
}
def bike= new Bike()
resetNamespaces(bike)
bike.bell.ring()
resetNamespaces(bike)
bike.bell.ring()
The result is:
setting Bell#14e9bd2b
calling Bell#14e9bd2b
setting Bell#948a7ad
calling Bell#14e9bd2b
So although I changed the property on the metaClass, calling it always returns the first object that was set. Is there some kind of caching?
When I just change the last part of the example to:
resetNamespaces(bike)
resetNamespaces(bike)
bike.bell.ring()
Then the result is as expected. That is, calling the property returns the object that was set on the metaClass as the last:
setting Bell#5b47e0c7
setting Bell#19f373a4
calling Bell#19f373a4
I even tried to set metaClass manually as follows
def resetNamespaces = { bike ->
def newMetaClass = new ExpandoMetaClass(Bike.class, true, true)
newMetaClass.initialize()
bike.metaClass = newMetaClass
...
}
But the result is still the same. So there must be some caching mechanism involved. I can't find anything about this behavior in the documentation.
The confusion arises because you are trying to change a property of the metaclass, not a method. As stated in the official documentation of groovy metaprogramming properties are accessed using the setAttribute/getAttribute methods of the metaclass.
// throws MissingFieldException!
def resetNamespaces = { bike ->
createNamespaces().each { name, namespace ->
bike.metaClass.setAttribute(bike, name, namespace)
}
}
Unfortunately this only works if the property is in the original class definition, here it throws a MissingFieldException: No such field: bell for class: Bike.
On the other hand overwriting methods works like a charm and provides the wanted syntax by adding a dynamic getter method:
def resetNamespaces(bike) {
createNamespaces().each { name, namespace ->
bike.metaClass."get${name.capitalize()}" = { namespace }
}
}
def bike = new Bike()
resetNamespaces(bike)
bike.bell.ring()
resetNamespaces(bike)
bike.bell.ring()
Actually expando works nicely as well
class Bell {
def ring() { println this }
}
def bike = new Expando()
bike.createNamespaces = {
return [ bell : new Bell() ]
}
bike.resetNamespaces = {
bike.createNamespaces().each { name, namespace ->
bike."$name" = namespace
}
}
bike.resetNamespaces bike
bike.bell.ring()
bike.resetNamespaces bike
bike.bell.ring()
I'm trying to put into the field an object that supports a call operation, and then to call him. I can do it without intermediate reading fields in a variable?
My attempt looks like this:
class CallableObjectDynamic {
def call() {
return "5"
}
}
class MyClassDynamic {
CallableObjectDynamic field = new CallableObjectDynamic()
}
class GroovyRunnerDynamic {
static String make(int arg1) {
MyClassDynamic x = new MyClassDynamic()
return x.field()
}
}
But I receive groovy.lang.MissingMethodException.
What can you do? Or can anyone give a proof where it's written that we can't call the field?
Membership (.) has lower order of precedence than function/method/call invocation (()). Thus this line:
return x.field()
is interpreted as "invoke the 'field' method on the 'x' object".
To get Groovy to parse the code as you desire, the minimal change would be to regroup using parentheses, as follows:
return (x.field)()
which is (ultimately) interpreted as "invoke the 'call' method on the 'field' object member of the 'x' object", as desired.
It is trivial issue. Not required to have parenthesis for field.
Change from:
return x.field()
To:
return x.field
If you want to execute call method further, then use below code snippet.
Note that static method return type is changed.
class CallableObjectDynamic {
def call() {
return "5"
}
}
class MyClassDynamic {
CallableObjectDynamic field = new CallableObjectDynamic()
}
class GroovyRunnerDynamic {
static def make(int arg1) {
MyClassDynamic x = new MyClassDynamic()
return x.field
}
}
GroovyRunnerDynamic.make(1).call()
Output would be : 5
Not sure why argument to make method is done here, seems to be not used in the above code.
Alternatively, you can change
class GroovyRunnerDynamic {
static def make(int arg1) {
MyClassDynamic x = new MyClassDynamic()
return x.field.call()
}
}
GroovyRunnerDynamic.make(1)
EDIT: Based on OP's implicit call.
Not really sure how it is working, but the below does implicit call. Just assign x.field to a variable and just add parenthesis for that as shown below.
class GroovyRunnerDynamic {
static String make(int arg1) {
MyClassDynamic x = new MyClassDynamic()
def fun = x.field
fun()
}
}
GroovyRunnerDynamic.make(1)
I'd like to create a simple wrapper, which would allow calling objects methods as a fluent interface. I've been thinking about rewriting methods of a class upon creation, but this doesn't seem to work. Is this possible in some way with groovy metaprograming?
I have this kind of code snippet so far:
class FluentWrapper {
def delegate
FluentWrapper(wrapped) {
delegate = wrapped
delegate.class.getMethods().each { method ->
def name = method.getName()
FluentWrapper.metaClass."$name" = { Object[] varArgs ->
method.invoke(wrapped, name, varArgs)
return this
}
}
}
def methodMissing(String name, args) {
def method = delegate.getClass().getDeclaredMethods().find { it.match(name) }
if(method) {
method.invoke(delegate,name, args)
return FluentWrapper(delegate)
}
else throw new MissingMethodException(name, delegate, args)
}
}
Assuming example Java class:
class Person {
void setAge()
void setName()
}
I'd like to be able to execute the following piece of code:
def wrappedPerson = new FluentWrapper(new Person())
wrappedPerson.setAge().setName()
I'm using Groovy 1.6.7 for this.
This is all Groovy, and I'm using 1.8.6 (the current latest), but given this Person Class:
class Person {
int age
String name
public void setAge( int age ) { this.age = age }
public void setName( String name ) { this.name = name }
public String toString() { "$name $age" }
}
And this FluentWrapper class:
class FluentWrapper {
def delegate
FluentWrapper(wrapped) {
delegate = wrapped
}
def methodMissing(String name, args) {
def method = delegate.getClass().declaredMethods.find { it.name == name }
if(method) {
method.invoke( delegate, args )
return this
}
else throw new MissingMethodException(name, delegate, args)
}
}
Then, you should be able to do:
def wrappedPerson = new FluentWrapper(new Person())
Person person = wrappedPerson.setAge( 85 ).setName( 'tim' ).delegate
And person should have the age and name specified
I find #tim_yates' answer nice, but you couldn't access delegate methods' return values (something one usually likes doing, even for Builders in the case of build() :)
Moreover, if this wasn't intended for a Builder but for an object with a chainable interface (like that of jQuery wrapped objects in JS), it would be a serious issue.
So I'd put the wrapper like this:
class FluentWrapper {
def delegate
FluentWrapper(wrapped) {
delegate = wrapped
}
def methodMissing(String name, args) {
def method = delegate.getClass().declaredMethods.find { it.name == name }
if(method) {
def result = method.invoke(delegate, args)
return result != null ? result : this
}
else throw new MissingMethodException(name, delegate, args)
}
}
Note the elvis operator is unsuitable since a falsy value would never get returned.
Of course, it's up to the invoker to know wether a method is chainable or not, but that could be overcome with method annotations if neccesary.