Tiago Pimenta
Tiago Pimenta

Reputation: 746

How to dynamically add all methods of a class into another class

I have a global shared library on Jenkins implicitly loaded on all pipelines, then my Jenkinsfile is like that:

new com.company.Pipeline()()

And then the shared library has on directory src/com/company some files, below the Pipeline.groovy class:

package com.company  

import static Utils.*

def call() {
  // some stuff here...
}

The problem is, this way I have to static declare all methods, thus I lose the context and cannot access jenkins' methods easly without the Pipeline class' instance. As you can see here they passing this to the method mvn.

Thinking of avoid this I was wondering about dynamically add all methods as closures by calling Utils.install this instead of using import static Utils.*, then my Utils.groovy is something like that:

package com.company

private Utils() {}

static def install(def instance) {
  def utils = new Utils()
  // Some extra check needed here I know, but it is not the problem now
  for (def method in (utils.metaClass.methods*.name as Set) - (instance.metaClass.methods*.name as Set)) {
    def closure = utils.&"$method"
    closure.delegate = instance
    instance.metaClass."$method" = closure
  }
}

def someMethod() {
  // here I want to use sh(), tool(), and other stuff freely.
}

But it raises an GStringImpl cannot be cast to String error, I believe .& do not work with variables, how can I convert a method into closure having the method name on a variable? I have the MetaMethod mostly being a CachedMethod instance, if it were possible to turn it a ClosureMetaMethod instance maybe the problem can be solved, but whenever I search for method to closure conversion for groovy I just found the .& solution!

If I use instance.metaClass.someMethod = utils.&someMethod it do work, but I want it to be dinamic as I add new methods without needing to worry about sharing it.

Upvotes: 1

Views: 1259

Answers (1)

Szymon Stepniak
Szymon Stepniak

Reputation: 42184

There is a way to do it dynamically. Notation utils.&someMethod returns a MethodClosure object that can be simply instantiated with its constructor:

MethodClosure(Object owner, String method)

Consider following example:

class Utils {
    def foo() {
        println "Hello, Foo!"
    }
    def bar() {
        println "Hello, Bar!"
    }
}

class Consumer {
}

def instance = new Consumer()
def utils = new Utils()

(utils.metaClass.methods*.name - instance.metaClass.methods*.name).each { method ->
    def closure = new MethodClosure(utils, method)
    closure.delegate = instance
    instance.metaClass."$method" = closure
}

instance.foo() // Prints "Hello, Foo!"
instance.bar() // Prints "Hello, Bar!"

In this example I use def closure = new MethodClosure(utils, method) to get object method reference and then add this method to instance object. I hope it helps.

Upvotes: 3

Related Questions