Gideon
Gideon

Reputation: 1517

Compressing series of if-else statement in Groovy

I have a series of if-else statements in Groovy:

String invoke = params?.target
AuxService aux = new AuxService()
def output

if(invoke?.equalsIgnoreCase("major")) {
    output = aux.major()
}
else if(invoke?.equalsIgnoreCase("minor")) {
    output = aux.minor()
}
else if(invoke?.equalsIgnoreCase("repository")) {
    output = aux.repository()
}
...
else if(invoke?.equalsIgnoreCase("temp")) {
    output = aux.temp()
}
else {
    output = aux.propagate()
}

The ellipsis contains yet another 14 sets of if-else statements, a total of 19. You see, depending on the value of invoke the method that will be called from AuxService. Now I'm thinking the following to reduce the lines:

String invoke = params?.target()
AuxService aux = new AuxService()
def output = aux?."$invoke"() ?: aux.propagate()

But I think the third line might not work, it looks very unconventional. And just a hunch, I think that line is prone to error. Is this a valid code or are there any more optimal approach to compress these lines?

Upvotes: 0

Views: 1172

Answers (3)

divesh premdeep
divesh premdeep

Reputation: 1070

The Elvis operator is used to shorten the equivalent Java ternary operator expression.

For example,

def nationality = (user.nationality!=null) ? user.nationality : "undefined"

can be shortened using the Elvis operator to

def nationality = user.nationality ?: "undefined"

Note that the Elvis operator evaluates the expression to the left of the "?" symbol. If the result is non-null, it returns the result immediately, else it evaluates the expression on the right of the ":" symbol and returns the result.

What this means is that you cannot use the Elvis operator to perform some extra logic on the right side of the "?" symbol if the condition evaluates to true. So, the ternary expression

user.nationality ? reportCitizen(user) : reportAlien(user)

cannot be (directly)expressed using the Elvis operator.

Coming back to the original question, the Elvis operator cannot be (directly) applied to checking if a method exists on an object and invoking it if present. So,

def output = aux?."$invoke"() ?: aux.propagate()

will not work as expected, because the Elvis operator will try to evaluate "aux?."$invoke"()" first. If "invoke" refers to a method that does not exist, you will get a MissingMethodException.

One way I can think of to work around this is -

class AuxService {
    def major() { println 'major invoked' }
    def minor() { println 'minor invoked' }
    def propagate() { println 'propagate invoked' }
}

def auxService = new AuxService()
def allowedMethods = ["major", "minor", "propagate"]

def method = null
allowedMethods.contains(method?.toLowerCase()) ? auxService."${method?.toLowerCase()}"() : auxService.propagate() // Prints "propagate invoked"

method = "MaJoR"
allowedMethods.contains(method?.toLowerCase()) ? auxService."${method?.toLowerCase()}"() : auxService.propagate() // Prints "major invoked"

method = "undefined"
allowedMethods.contains(method?.toLowerCase()) ? auxService."${method?.toLowerCase()}"() : auxService.propagate() // Prints "propagate invoked"

In a nutshell, store the list of invoke-able methods in a list and check to see if we're trying to invoke a method from this list. If not, invoke the default method.

Upvotes: 1

Will
Will

Reputation: 14529

Just test String invoke before using it. Note that aux won't be null, so no need to use safe navigation (?.).

class AuxService {
    def major() { 'major invoked' }
    def minor() { 'minor invoked' }
    def propagate() { 'propagate invoked' } 
}


def callService(invoke) {
    def aux = new AuxService()
    return invoke != null ? aux.invokeMethod(invoke, null) : aux.propagate()
}

assert callService('major') == 'major invoked'
assert callService(null) == 'propagate invoked'

Note this is will fail if the input doesn't contain a valid method in class AuxService.

Upvotes: 1

blackdrag
blackdrag

Reputation: 6508

Firstly, your code is avlid in Groovy. Though, if equalsIgnoreCase is required, your reduced code will not work. The same for if params is null, since then invoke would be null. But I think your basic idea is right. So what I would do is making a map (final static somewhere) with the methods in uppercase as String key and the real method in correct casing as String value. Then you can use that to ensure correctness for different cases. Null handling I would solve separate:

def methodsMap = ["MAJOR":"major",/* more mappings here */]
String invoke = params?.target()
AuxService aux = new AuxService()
def methodName = methodsMap[invoke?.toUpperCase()]
def output = methodName ? aux."$methodName"() : aux.propagate()

An slightly different approach would be to use Closure values in the map. I personally find that a bit overkill, but it allows you to do more than just the plain invocation

def methodsMap = ["MAJOR":{it.major()},/* more mappings here */]
String invoke = params?.target()
AuxService aux = new AuxService()
def stub = methodsMap[invoke?.toUpperCase()]
def output = stub==null ? stub(aux) : aux.propagate()

I thought about using the Map#withDefault, but since that will create a new entry I decided not to. It could potentially cause memory problems. In Java8 you can use Map#getOrDefault:

String invoke = params?.target()
AuxService aux = new AuxService()
def methodName = methodsMap.getOrDefault(invoke?.toUpperCase(), "propagate")
def output = aux."$methodName"()

Upvotes: 1

Related Questions