Compressing series of if-else statement in Groovy - if-statement

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?

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.

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"()

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.

Related

What's an idiomatic way to traverse and update data structures functionally in Scala?

I'm coming from a Python-heavy background and trying to learn Scala through a basic "Design a Parking Lot" exercise. I have Scala code that looks something like:
class ParkingLot(spaces: Set[ParkingSpace]) {
var openSpaces: Set[ParkingSpace] = spaces;
var usedSpaces: Set[ParkingSpace] = Set()
def assign(vehicle: Vehicle): Boolean = {
var found = false;
for (s <- openSpaces) {
(s.isCompatibleWithVehicle(vehicle)) match {
case true => {
if (!found) {
s.acceptVehicle(vehicle)
openSpaces -= s
usedSpaces += s
found = true
}
}
case _ => {}
}
}
found
}
}
The logic is pretty simple - I have a ParkingLot with Sets of open and occupied ParkingSpaces. I want to define a function assign that takes in a Vehicle, loops through all the openSpaces and if it finds an available space, will update the open and used spaces. I'm having a hard time coming up with a good idiomatic way to do this. Any thoughts and suggestions about how to reframe questions into a Scala mindset?
The main problem with this code is use of mutable state (var). Rather than changing an existing object, functional code creates new, modified objects. So the functional approach is to create a new ParkingLot each time with the appropriate allocation of spaces.
case class ParkingLot(open: Set[ParkingSpace], used: Set[ParkingSpace])
{
def assignVehicle(vehicle: Vehicle): Option[ParkingLot] =
open.find(_.isCompatibleWithVehicle(vehicle)).map { space =>
ParkingLot(open - space, used + space.acceptVehicle(vehicle))
}
}
assignVehicle can return a new parking lot with the spaces appropriately updated. It returns an Option because there might not be a compatible space, in which case it returns None. The caller can take whatever action is necessary in this case.
Note that ParkingSpace now as an acceptVehicle that returns a new ParkingSpace rather than modifying itself.
As also the answer by #Tim mentioned, you need to avoid mutations, and try to handle this kind of state managements in functions. I'm not gonna dive into the details since Tim mentioned some, I'm just proposing a new approach to the implementation, which uses a map of spaces to weather they're used, and returns a new (not optional) instance every time you assign a new vehicle (if the vehicle fits in, updated instance is returned, and if not, the same instance):
class ParkingLot(spaces: Map[ParkingSpace, Boolean]) {
def withVehicleAssigned(vehicle: Vehicle): ParkingLot =
spaces.collectFirst {
case (space, used) if !used && space.isCompatibleWithVehicle(vehicle) =>
new ParkingLot(spaces.updated(space, true))
}.getOrElse(this)
}
Almost the same process goes for removing vehicles, the usage would be something like this:
parkingLot
.withVehicleAssigned(v1)
.withVehicleAssigned(v2)
.withVehicleRemoved(v1)
Since most answers already explained the importance of immutability and creating new objects, I am just going to propose two alternative models and solutions.
1. Using a queue of empty spaces plus a set of used ones.
final case class ParkingLot(freeSpaces: List[ParkingSpace], occupiedSpaces: Set[ParkingSpace]) {
// Returns the used space and the new parking lot.
// An option is used since the parking lot may be full.
def assign(vehicle: Vehicle): Option[(ParkingSpace, ParkingLot)] =
freeSpaces match {
case freeSpace :: remainingSpaces =>
val usedSpace = freeSpace.withVehicle(vehicle)
Some(copy(freeSpaces = remainingSpaces, usedSpaces = usedSpace + usedSpaces))
case Nil =>
None
}
}
2. Using a List[(ParkingSpace, Boolean)] and a tail-recursive function.
final case class ParkingLot(parkingSpaces: List[(ParkingSpace, Boolean)]) {
// Returns the used space and the new parking lot.
// An option is used since the parking lot may be full.
def assign(vehicle: Vehicle): Option[(ParkingSpace, ParkingLot)] = {
#annotation.tailrec
def loop(remaining: List[(ParkingSpace, Boolean)], acc: List[(ParkingSpace, Boolean)]): Option[(ParkingSpace, List[(ParkingSpace, Boolean)])] =
remaining match {
case (parkingSpace, occupied) :: tail =>
if (occupied) loop(remaining = tail, (parkingSpace, occupied) :: acc)
else {
val usedSpace = parkingSpace.withVehicle(vehicle)
val newSpaces = acc reverse_::: ((usedSpace -> true) :: tail)
Some(usedSpace -> newSpaces)
}
case Nil =>
None
}
loop(remaining = parkingSpaces, acc = List.empty).map {
case (usedSpace, newSpaces) =>
usedSpace -> copy(newSpaces)
}
}
}
Note, the boolean may be redundant since the ParkingSpace should be able to tell us if it is empty or not.

Struggling with simple Groovy List operations

I've abstracted a very simple situation here, in which I want to pass a list of strings into my cleanLines function and get a list of strings back out. Unfortunately, I'm new to Groovy and I've spent about a day trying to get this to work with no avail. Here's a stand-alone test that exhibits the problem I'm having:
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
class ConfigFileTest {
private def tab = '\t'
private def returnCarriage = '\r'
private def equals = '='
List <String> cleanLines(List <String> lines) {
lines = lines.collect(){it.findAll(){c -> c != tab && c != returnCarriage}}
lines = lines.findAll(){it.contains(equals)}
lines = lines.collect{it.trim()}
}
#Test
public void test() {
List <String> dirtyLines = [" Colour=Red",
"Shape=Square "]
List <String> cleanedLines = ["Colour=Red",
"Shape=Square"]
assert cleanLines(dirtyLines) == cleanedLine
}
}
I believe that I've followed the correct usage for collect(), findAll() and trim(). But when I run the test, it crashes on the trim() line stating
groovy.lang.MissingMethodException: No signature of method:
java.util.ArrayList.trim() is applicable for argument types: ()
values: []
. Something's suspicious.
I've been staring at this for too long and noticed that my IDE thinks the type of my first lines within the cleanLines function is List<String>, but that by the second line it has type Collection and by the third it expects type List<Object<E>>. I think that String is an Object and so this might be okay, but it certainly hints at a misunderstanding on my part. What am I doing wrong? How can I get my test to pass here?
Here's a corrected script:
import groovy.transform.Field
#Field
def tab = '\t'
#Field
def returnCarriage = '\r'
#Field
def equals = '='
List <String> cleanLines(List <String> lines) {
lines = lines.findAll { it.contains(equals) }
lines = lines.collect { it.replaceAll('\\s+', '') }
lines = lines.collect { it.trim() }
}
def dirtyLines = [" Colour=Red",
"Shape=Square "]
def cleanedLines = ["Colour=Red", "Shape=Square"]
assert cleanLines(dirtyLines) == cleanedLines
In general findAll and collect are maybe not mutually exclusive but have different purposes. Use findAll to find elements that matches certain criteria, whereas collect when you need to process/transform the whole list.
this line
lines = lines.collect(){it.findAll(){c -> c != tab && c != returnCarriage}}`
replaces the original list of Strings with the list of lists. Hence NSME for ArrayList.trim(). You might want to replace findAll{} with find{}
You can clean the lines like this:
def dirtyLines = [" Colour=Red", "Shape=Square "]
def cleanedLines = ["Colour=Red", "Shape=Square"]
assert dirtyLines.collect { it.trim() } == cleanedLines
If you separate the first line of cleanLines() into two separate lines and print lines after each, you will see the problem.
it.findAll { c -> c != tab && c != returnCarriage }
will return a list of strings that match the criteria. The collect method is called on every string in the list of lines. So you end up with a list of lists of strings. I think what you are looking for is something like this:
def cleanLines(lines) {
return lines.findAll { it.contains(equals) }
.collect { it.replaceAll(/\s+/, '') }
}

Using dict like C switch

class checkevent:
def __init__(self,fromuser):
self.fromuser = fromuser
def openid_check(self):# use sqlalchemy
exist_user = User.query.filter_by(openid = self.fromuser).first()
if exist_user is None:
text = u'请绑定后在使用'
return text
def grade(self):
openid_check()
exist_user = User.query.filter_by(openid = self.fromuser).first()
geturp = urp(exist_user.username, exist_user.password_urp) #the function
return geturp #return the grades as text
def key_check(self,x): # use dict like switch
{'grade': self.grade
}
contents = checkevent('ozvT4jlLObJWzz2JQ9EFsWSkdM9U').key_check('grade')
print contents
It's always return None,I want to get a value
and it's the right way to use dict?
There's no return statement in key_check, so naturally it doesn't return anything. You're basically missing the last bit of the implementation: once you look up the appropriate function by name, you need to call that function and return the result.
def key_check(self, key): # "x" is a meaningless name; use something meaningful
lookup = {
'grade': self.grade
}
func = lookup[key] # Look up the correct method
return func() # Call that method and return its result
Technically you could in-line all that into one statement if you really wanted to, but unless performance is at a premium I wouldn't recommend it as the readability suffers.

Need the Groovy way to do partial file substitutions

I have a file that I need to modify. The part I need to modify (not the entire file), is similar to the properties shown below. The problem is that I only need to replace part of the "value", the "ConfigurablePart" if you will. I receive this file so can not control it's format.
alpha.beta.gamma.1 = constantPart1ConfigurablePart1
alpha.beta.gamma.2 = constantPart2ConfigurablePart2
alpha.beta.gamma.3 = constantPart3ConfigurablePart3
I made this work this way, though I know it is really bad!
def updateFile(String pattern, String updatedValue) {
def myFile = new File(".", "inputs/fileInherited.txt")
StringBuffer updatedFileText = new StringBuffer()
def ls = System.getProperty('line.separator')
myFile.eachLine{ line ->
def regex = Pattern.compile(/$pattern/)
def m = (line =~ regex)
if (m.matches()) {
def buf = new StringBuffer(line)
buf.replace(m.start(1), m.end(1), updatedValue)
line = buf.toString()
}
println line
updatedFileText.append(line).append(ls)
}
myFile.write(updatedFileText.toString())
}
The passed in pattern is required to contain a group that is substituted in the StringBuffer. Does anyone know how this should really be done in Groovy?
EDIT -- to define the expected output
The file that contains the example lines needs to be updated such that the "ConfigurablePart" of each line is replaced with the updated text provided. For my ugly solution, I would need to call the method 3 times, once to replace ConfigurablePart1, once for ConfigurablePart2, and finally for ConfigurablePart3. There is likely a better approach to this too!!!
*UPDATED -- Answer that did what I really needed *
In case others ever hit a similar issue, the groovy code improvements I asked about are best reflected in the accepted answer. However, for my problem that did not quite solve my issues. As I needed to substitute only a portion of the matched lines, I needed to use back-references and groups. The only way I could make this work was to define a three-part regEx like:
(.*)(matchThisPart)(.*)
Once that was done, I was able to use:
it.replaceAdd(~/$pattern/, "\$1$replacement\$3")
Thanks to both replies - each helped me out a lot!
It can be made more verbose with the use of closure as args. Here is how this can be done:
//abc.txt
abc.item.1 = someDummyItem1
abc.item.2 = someDummyItem2
abc.item.3 = someDummyItem3
alpha.beta.gamma.1 = constantPart1ConfigurablePart1
alpha.beta.gamma.2 = constantPart2ConfigurablePart2
alpha.beta.gamma.3 = constantPart3ConfigurablePart3
abc.item.4 = someDummyItem4
abc.item.5 = someDummyItem5
abc.item.6 = someDummyItem6
Groovy Code:-
//Replace the pattern in file and write to file sequentially.
def replacePatternInFile(file, Closure replaceText) {
file.write(replaceText(file.text))
}
def file = new File('abc.txt')
def patternToFind = ~/ConfigurablePart/
def patternToReplace = 'NewItem'
//Call the method
replacePatternInFile(file){
it.replaceAll(patternToFind, patternToReplace)
}
println file.getText()
//Prints:
abc.item.1 = someDummyItem1
abc.item.2 = someDummyItem2
abc.item.3 = someDummyItem3
alpha.beta.gamma.1 = constantPart1NewItem1
alpha.beta.gamma.2 = constantPart2NewItem2
alpha.beta.gamma.3 = constantPart3NewItem3
abc.item.4 = someDummyItem4
abc.item.5 = someDummyItem5
abc.item.6 = someDummyItem6
Confirm file abc.txt. I have not used the method updateFile() as done by you, but you can very well parameterize as below:-
def updateFile(file, patternToFind, patternToReplace){
replacePatternInFile(file){
it.replaceAll(patternToFind, patternToReplace)
}
}
For a quick answer I'd just go this route:
patterns = [pattern1 : constantPart1ConfigurablePart1,
pattern2 : constantPart2ConfigurablePart2,
pattern3 : constantPart3ConfigurablePart3]
def myFile = new File(".", "inputs/fileInherited.txt")
StringBuffer updatedFileText = new StringBuffer()
def ls = System.getProperty('line.separator')
myFile.eachLine{ line ->
patterns.each { pattern, replacement ->
line = line.replaceAll(pattern, replacement)
}
println line
updatedFileText.append(line).append(ls)
}
myFile.write(updatedFileText.toString())

Grails Criteria not working in JUnit Test

I'm trying to test search method in my grails app but I'm having a null pointer exception. I mocked the domain in my test as follows:
#TestFor(AuditController)
#Mock(Audit)
class AuditControllerTests {
void testSearch() {
populateValidParams(params)
def audit=new Audit(params)
audit.save(failOnError: true)
//Search existing customer
def model = controller.search()
assert model.auditInstanceList.size() == 1
assert model.auditInstanceList.size() == 1
}
}
I got NPE on model.auditInstanceList. Where it shouldn't be null. Here is the code in my controller:
def search = {
def query
def criteria = Audit.createCriteria()
def results
query = {
and{
if(params.customerName){
ilike("customerName", params.customer + '%')
}
if(params.siteName){
ilike("siteName", params.siteName + '%')
}
max:params.max
offset:params.offset
}
}
results = criteria.list(params, query)
render(view:'list', model:[ auditInstanceList: results,auditInstanceTotal:results.totalCount ])
}
What is going on with this?
Return model at the end of search. As in,
def search = {
...
render(view:'list', model:[ auditInstanceList: results, auditInstanceTotal:results.totalCount ])
[auditInstanceList: results, auditInstanceTotal:results.totalCount]
}
When testing a controller action that calls render(), model and view variables are automatically created and populated in your test. By doing def model = controller.search(), you are replacing the magic model with your own, assigning it to the return value of search(). The correct way to do your assertions is:
controller.search()
assert model.auditInstanceList.size() == 1
assert view == '/audit/list'
I don't know why but sometimes you need to remove model as a return value from controller's action. I use both version alternatively in case one of them fails:
// sometimes this one works
def model = controller.search()
assert model
// sometimes this one works
controller.search()
assert model
Edit: I think of two new possibilities why your action doesn't work:
try to change your action from closure and make it a method.
make sure you have no after filter. I've found this bug: http://jira.grails.org/browse/GRAILS-6825