I have code like this:
def updateSensor(List<String> boardIds, SensorShort sensor) {
for (String boardId : boardIds) {
println("Working on ${boardId} for ${sensor.sensorId}")
pool.submit({
println("[${Thread.currentThread().name}] Working on ${boardId} for ${sensor.sensorId}")
})
}
}
result of this code is:
Working on 400 for 11
Working on 100 for 11
Working on 101 for 11
Working on 300 for 11
[pool-4-thread-4] Working on 300 for 11
[pool-4-thread-1] Working on 300 for 11
[pool-4-thread-3] Working on 300 for 11
[pool-3-thread-1] Working on 300 for 11
but is wrong. It seams boardId object has been rewritten
You're submitting a job based on a non-final local variable, instead try:
def updateSensor(List<String> boardIds, SensorShort sensor) {
boardIds.each { String boardId ->
println("Working on ${boardId} for ${sensor.sensorId}")
pool.submit {
println("[${Thread.currentThread().name}] Working on ${boardId} for ${sensor.sensorId}")
}
}
}
The problem with the original code is that by the time the Closure is evaluated on a separate thread, the loop has finished, and the local boardId variable has the value of the last item in the list. So each job runs with the last element, rather than the element you required.
In Java, you would declare (indeed, Java would force you to declare the variable as final):
for( final String boardId : boardIds ) {
However, groovy does not have local final variables :-/
By doing it with boardIds.each, the local boardId variable inside the each closure has the value you need...
Hope that explains it?
Related
I've the following class:
class Names {
has #!names;
method add-name( $name ) { #!names.push($name) }
multi method AT-POS( ::?CLASS:D: $index ) {
my $new-names-obj = Names.new;
for #!names[$index] -> $name {
$new-names-obj.add-name($name);
}
return $new-names-obj;
}
method gist {
#!names.join("\n")
}
}
I'd like to be able to slice a Names object and the returned value
should be another Names object whose elements are sliced off from the
original Names object. For instance:
my $original = Names.new;
$original.add-name($_) for <jonathan joseph jotaro josuke giorno>;
my $sliced-off = $original[0..2];
say $original.^name; #=> Names
say $original; #=> jonathan, joseph, jotaro, josuke, giorno
say $sliced-off.^name; #=> List
say $sliced-off; #=> (jonathan joseph jotaro)
When a single argument is passed, it works as expected and as described in this answer but it's not the case with range since AT-POS ends up being called multiple times and collecting the results in a list. Thus I'm wondering if it's possible to return a single object $sliced-off, not a list of results, when using a range.
The AT-POS method is intended to let an object act as a Positional object. This is not what you appear to want. You want object[slice] DWIM.
The best way to achieve that, is to create a postcircumfic:<[ ]> (multi) candidate for your object:
class A {
method slice(#_) {
say #_; # just to show the principle
}
}
sub postcircumfix:<[ ]>($object, *#indices) {
constant &slicer = &postcircumfix:<[ ]>;
$object ~~ A
?? $object.slice(#indices)
!! slicer($object, #indices)
}
A.new[1,2,4,5]; # [1 2 4 5]
my #a = ^10; # check if foo[] still works
say #a[1,2,4,5]; # (1 2 4 5)
To make sure that the common behaviour of #a[] is kept, we save the value of the system's postcircumfix:[ ]> at compile time (with a constant). Then at runtime, when the object is not of the right class, invoke the original version of postcircumfix:<[ ]> with the given parameters.
Building on Liz's guidance:
class Names {
has #.names; # Make public so [] can access.
method new (*#names) { nextwith :#names } # Positional .new constructor.
submethod BUILD (:#!names) {} # Called by nextwith'd Mu new.
multi sub postcircumfix:<[ ]> # Overload [] subscript.
( Names $n, $index, *#indices ) # Why `$index, *#indices`?
is default is export # And why `is default`?
{ Names.new: |$n.names[ |$index, |#indices ] } # Why? See my comment
method gist { #!names.join(', ') } # below Liz's answer.
}
import Names;
my $original = Names.new: <jonathan joseph jotaro josuke giorno>;
my $sliced-off = $original[0..2];
say $original.^name; #=> Names
say $original; #=> jonathan, joseph, jotaro, josuke, giorno
say $sliced-off.^name; #=> Names
say $sliced-off; #=> jonathan, joseph, jotaro
PLMK if the code or explanation is inadequate.
I have groovy code as below:
def randomInt = RandomUtil.getRandomInt(1,200);
log.info randomInt
def chars = (("1".."9") + ("A".."Z") + ("a".."z")).join()
def randomString = RandomUtil.getRandomString(chars, randomInt) //works well with this code
log.info randomString
evaluate("log.info new Date()")
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
I want to evaluate a String which one like a {classname}.{methodname} in SoapUI with Groovy, just like above, but got error here, how to handle this and make it works well as I expect?
I have tried as blew:
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
Error As below:
Thu May 23 22:26:30 CST 2019:ERROR:An error occurred [No such property: getRandomString(chars, randomInt) for class: com.hypers.test.apitest.util.RandomUtil], see error log for details
The following code:
log = [info: { println(it) }]
class RandomUtil {
static def random = new Random()
static int getRandomInt(int from, int to) {
from + random.nextInt(to - from)
}
static String getRandomString(alphabet, len) {
def s = alphabet.size()
(1..len).collect { alphabet[random.nextInt(s)] }.join()
}
}
randomInt = RandomUtil.getRandomInt(1, 200)
log.info randomInt
chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
def randomString = RandomUtil.getRandomString(chars, 10) //works well with this code
log.info randomString
evaluate("log.info new Date()")
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
emulates your code, works, and produces the following output when run:
~> groovy solution.groovy
70
DDSQi27PYG
Thu May 23 20:51:58 CEST 2019
~>
I made up the RandomUtil class as you did not include the code for it.
I think the reason you are seeing the error you are seeing is that you define your variables char and randomInt using:
def chars = ...
and
def randomInt = ...
this puts the variables in local script scope. Please see this stackoverflow answer for an explanation with links to documentation of different ways of putting things in the script global scope and an explanation of how this works.
Essentially your groovy script code is implicitly an instance of the groovy Script class which in turn has an implicit Binding instance associated with it. When you write def x = ..., your variable is locally scoped, when you write x = ... or binding.x = ... the variable is defined in the script binding.
The evaluate method uses the same binding as the implicit script object. So the reason my example above works is that I omitted the def and just typed chars = and randomInt = which puts the variables in the script binding thus making them available for the code in the evaluate expression.
Though I have to say that even with all that, the phrasing No such property: getRandomString(chars, randomInt) seems really strange to me...I would have expected No such method or No such property: chars etc.
Sharing the code for for RandomUtil might help here.
I have a method to generate an 8 characters alpha numeric string.
log.info(
new Random().with {
(1..8).collect { (('A'..'Z')+('0'..'9')).join()[ nextInt( (('A'..'Z')+('0'..'9')).join().length() ) ] }.join()
})
I ran it as test case set up script and it returns:
Thu Sep 18 10:37:18 NZST 2014:INFO:L7S76IP1
When I include this in the test request as one of the attribute:
${new Random().with {(1..8).collect { (('A'..'Z')+('0'..'9')).join()[ nextInt( (('A'..'Z')+('0'..'9')).join().length() ) ] }.join()}}
Then it did not return the random string as I expected.
Can you please help me out?
Thanks.
You need to use the equal sign in the property expansion, like so: ${=new Random .....}
Also, you might want to look at Apache RandomStringUtils.
When try this sample code:
use(groovy.time.TimeCategory) {
800.millisecond + 300.millisecond
}
in groovy web console, I get a funny result:
0.1100 seconds
Does any one know why this happens or how to fix it?
That looks like a bug, the TimeDuration contains 1100 milliseconds, but when it prints it out, it converts it wrongly to seconds.
I've added it to the Groovy JIRA as a bug EDIT It's now marked as FIXED for versions 2.0.6, 1.8.9 and 2.1.0
In the mean time, I guess you'll need to do your own converter from TimeDuration to String :-/
Edit
You could do something like this (and there is probably a neater way of doing it)
groovy.time.TimeDuration.metaClass.normalize = { ->
def newdmap = ['days','hours','minutes','seconds','millis'].collectEntries {
[ (it):delegate."$it" ]
}.with { dmap ->
[millis:1000,seconds:60,minutes:60,hours:24,days:-1].inject( [ dur:[ days:0, hours:0, minutes:0, seconds:0, millis:0 ], roll:0 ] ) { val, field ->
val.dur."$field.key" = dmap."$field.key" + val.roll
val.roll = val.dur."$field.key".intdiv( field.value )
val.dur."$field.key" = field.value < 0 ?
val.dur."$field.key" :
val.dur."$field.key" % field.value
val
}.dur
}
new TimeDuration( newdmap.days, newdmap.hours, newdmap.minutes, newdmap.seconds, newdmap.millis )
}
That adds a normalize method to TimeDuration, so then doing:
use(groovy.time.TimeCategory) {
800.millisecond + 300.millisecond
}.normalize()
Shows 1.100 seconds
I haven't done a huge amount of testing on that method, so be warned it could do with some unit tests to make sure it doesn't fall over with other situations.
Just started learning Groovy, got the PragProg book "Programming Groovy" and had a problem compiling one of the sample scripts:
class GCar2 {
final miles = 0
def getMiles() {
println "getMiles called"
miles
}
def drive(dist) {
if (dist > 0) {
miles += dist
}
}
}
def car = new GCar2()
println "Miles: $car.miles"
println 'Driving'
car.drive(10)
println "Miles: $car.miles"
try {
print 'Can I see the miles? '
car.miles = 12
} catch (groovy.lang.ReadOnlyPropertyException ex) {
println ex.message
GroovyCar2.groovy: 20: cannnot access final field or property outside of constructor.
# line 20, column 35.
def drive(dist) { if (dist > 0) miles += dist }
^
Groovy versions prior to 1.7 do not give an error. I looked through whatever documentation I could find and did not see the issue discussed. What is going on here?
Aaron
I don't know much about Groovy 1.7, but it looks like a bug in earlier versions which has now been fixed - if a variable is final, you shouldn't be able to assign to it outside the constructor (or its declaration). If you can, what's the point of making it final?
I doubt that it'll stop you from reading it outside the constructor though...
You shouldn't be able to assign to a final variable in a normal method. It was a bug in groovy, fixed in 1.7.