This is what I'd like to do:
class MyObject {
#Lazy volatile String test = {
//initalize with network access
}()
}
def my = new MyObject()
println my.test
//Should clear the property but throws groovy.lang.ReadOnlyPropertyException
my.test = null
//Should invoke a new initialization
println my.test
Unfortunately lazy fields are readonly fields in Groovy and clearing the property leads to an exception.
Any idea how to make a lazy field reinitializable without reimplementing the double checking logic provided by the #Lazy annotation?
UPDATE:
Considering soft=true (from the 1st answer) made me run a few tests:
class MyObject {
#Lazy() volatile String test = {
//initalize with network access
println 'init'
Thread.sleep(1000)
'test'
}()
}
def my = new MyObject()
//my.test = null
10.times { zahl ->
Thread.start {println "$zahl: $my.test"}
}
Will have the following output on my Groovy console after approx 1 sec:
init
0: test
7: test
6: test
1: test
8: test
4: test
9: test
3: test
5: test
2: test
This is as expected (and wanted). Now I add soft=trueand the result changes dramatically and it takes 10 seconds:
init
init
0: test
init
9: test
init
8: test
init
7: test
init
6: test
init
5: test
init
4: test
init
3: test
init
2: test
1: test
Maybe I'm doing the test wrong or soft=true destroys the caching effect completely. Any ideas?
Can't you use the soft attribute of Lazy, ie:
class MyObject {
#Lazy( soft=true ) volatile String test = {
//initalize with network access
}()
}
edit
With soft=true, the annotation generates a setter and a getter like so:
private volatile java.lang.ref.SoftReference $test
public java.lang.String getTest() {
java.lang.String res = $test?.get()
if ( res != null) {
return res
} else {
synchronized ( this ) {
if ( res != null) {
return res
} else {
res = {
}.call()
$test = new java.lang.ref.SoftReference( res )
return res
}
}
}
}
public void setTest(java.lang.String value) {
if ( value != null) {
$test = new java.lang.ref.SoftReference( value )
} else {
$test = null
}
}
Without soft=true, you don't get a setter
private volatile java.lang.String $test
public java.lang.String getTest() {
java.lang.Object $test_local = $test
if ( $test_local != null) {
return $test_local
} else {
synchronized ( this ) {
if ( $test != null) {
return $test
} else {
return $test = {
}.call()
}
}
}
}
So the variable is read-only. Not currently sure if this is intentional, or a side-effect of using soft=true though...
Edit #2
This looks like it might be a bug in the implementation of Lazy with soft=true
If we change the getter to:
public java.lang.String getTest() {
java.lang.String res = $test?.get()
if( res != null ) {
return res
} else {
synchronized( this ) {
// Get the reference again rather than just check the existing res
res = $test?.get()
if( res != null ) {
return res
} else {
res = {
println 'init'
Thread.sleep(1000)
'test'
}.call()
$test = new java.lang.ref.SoftReference<String>( res )
return res
}
}
}
}
I think it's working... I'll work on a bugfix
Related
I wrote an unit test to test if a Map is updated every second. In the code I inserted some println statements to see if the function really is called. The test runs indefinitely until I abort the test, showing the println in system.out. So the function works correctly, but my test does not.
Testcode and programcode are below.
#Test
fun test_count_is_updated_using_turbine(){
runTest {
testScheduler.runCurrent()
viewmodelUnderTest.runningTaskMap.test(2.seconds){
val firstCount = awaitItem()
assertThat(firstCount).containsKey(1)
viewmodelUnderTest.startUpdates()
val secondCount = awaitItem()
viewmodelUnderTest.stopUpdates()
cancelAndIgnoreRemainingEvents()
assertThat(firstCount).isEqualTo(0)
assertThat(secondCount).isEqualTo(1)
}
}
}
Program code in viewmodel:( RunningMapState is initialized with a value when the viewmodel is initialized, so it is not an empty map.)
var runningTaskState:Map<Int, RunningTaskView> = mapOf()
private val _runningTaskMap = MutableStateFlow<Map<Int, RunningTaskView>>(mapOf())
val runningTaskMap: StateFlow<Map<Int, RunningTaskView>>
get()=_runningTaskMap
suspend fun updateRunningTasks() {
if (runningTaskState.isNotEmpty()) {
println("Before update:"+runningTaskState)
runningTaskState.forEach {
it.value.duration += 1000L
}
println("After update:"+runningTaskState)
//val tempMap = runningTaskState.toMap()
_runningTaskMap.update{ runningTaskState }
}
else{
println("map empty")
}
}
fun startUpdates(){
val previousJob = timerJob
timerJob = viewModelScope.launch (dispatcher){
previousJob?.cancelAndJoin()
while (isActive) {
delay(1000L)
updateRunningTasks()
}
}
}
fun stopUpdates(){
viewModelScope.launch(dispatcher) {
timerJob?.cancelAndJoin()
}
}
To test the testcode I wrote a sample program replacing the map with a Integer. This test functions as expected and passes. Viewmodelcode and testcode are nearly identical.
See below code:
#Test
fun test_count_is_updated_using_turbine(){
runTest {
testDispatcher.scheduler.runCurrent()
viewmodelUnderTest.countFlow.test(2.seconds){
val firstCount = awaitItem()
val started = viewmodelUnderTest.startTimer()
val secondCount = awaitItem()
viewmodelUnderTest.stoptimer()
cancelAndIgnoreRemainingEvents()
assertThat(started).isTrue()
assertThat(firstCount).isEqualTo(0)
assertThat(secondCount).isEqualTo(1)
}
}
}
Viewmodel:
var count = 0
var countFlow = MutableStateFlow<Int>(0)
fun updateTimer(){
count++
countFlow.update { count }
println("count updated to $count")
}
fun stoptimer(){
viewModelScope.launch {
timerJob?.cancelAndJoin()
}
}
fun startTimer():Boolean{
val previousJob = timerJob
timerJob = viewModelScope.launch(dispatcher) {
previousJob?.cancelAndJoin()
println("start updating")
while(isActive) {
delay(1000L)
println("calling update")
updateTimer()
println("update called")
}
}
println("TimerTest is active: "+timerJob?.isActive?.toString()?:"not initialized")
return timerJob?.isActive?:false
}
Can anyone explain why the first test is not finishing?
I'm writing a test using the Spock framework and I found a strange bavior when testing equality of lists.
When I compare two lists like
sourceList == targetList
and those lists contains Comparable objects of the same type, those objects are tested for equality using its compareTo methods instead of equals.
Is there any simple way how to force Groovy to use equals when testing equality on such lists?
Here is a simple test specification where the test should fail, but it does not.
class Test extends Specification {
def "list test"() {
when:
def listA = [[index: 1, text: "1"] as Bean, [index: 2, text: "2"] as Bean]
def listB = [[index: 1, text: "1"] as Bean, [index: 2, text: "3"] as Bean]
then:
listA == listB
}
class Bean implements Comparable<Bean> {
int index
String text
#Override
public int compareTo(Bean o) {
return index.compareTo(o.index);
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + index;
result = prime * result + ((text == null) ? 0 : text.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Bean)) {
return false;
}
Bean other = (Bean) obj;
if (index != other.index) {
return false;
}
if (text == null) {
if (other.text != null) {
return false;
}
} else if (!text.equals(other.text)) {
return false;
}
return true;
}
}
}
Asking for a simple method is subjective. The key to this answer is that, in Groovy, the is() method will check object equality.
Consider this new method on ArrayList. If appropriate, it will zip together the two lists and call is() on each pair of items.
ArrayList.metaClass.myCompare = { listB ->
def result = false
def listA = delegate
if (listB && listA.size() == listB.size()) {
def zipped = [listA, listB].transpose()
result = zipped.inject(true){ ok, pair ->
ok && pair[0].is(pair[1])
}
}
result
}
then:
def beanA = new Bean(index: 1, text: 'a')
def beanA1 = new Bean(index: 1, text: 'a')
def beanB = new Bean(index: 2, text: 'b')
assert [beanA, beanB].myCompare([beanA, beanB])
assert ! [beanA, beanB].myCompare([beanA1, beanB])
assert ! [beanA, beanB].myCompare([beanA])
assert ! [beanA, beanB].myCompare(null)
assert ! [beanA, beanB].myCompare([])
In groovy, "==" is replaced with equals if items are not comparable, or with compareTo if they implement Comparable. I do not know a simple way to force equals, if the collections have no duplicates, you can check the elements of the collection one by one:
def collectionsEqual (Collection<?> collection1, Collection<?> collection2) {
if (collection1.size() != collection2.size()) {
return false
}
for (elem1 in collection1) {
def founded = false;
for (elem2 in collection2) {
if (elem1?.equals (elem2)) {
founded = true
}
}
if (!founded) {
return false
}
}
true
}
but, in https://www.baeldung.com/java-compareto we have:
It is also strongly recommended, though not required, to keep the
compareTo implementation consistent with the equals method
implementation:
x.compareTo(y) == 0 should have the same boolean value as x.equals(y)
This will ensure that we can safely use objects in sorted sets and
sorted maps.
When writing a sequence in an IYamlTypeConverter you might use some code like this:
public class MyObjectConverter : IYamlTypeConverter {
public MyObjectConverter() {}
public bool Accepts(Type type) { return typeof(IMyObject) == type || typeof(IMyObject[]) == type; }
public object ReadYaml(IParser parser, Type type) { return null; }
public void WriteYaml(IEmitter emitter, object value, Type type) {
var itemVal = value as IMyObject;
if (itemVal != null)
emitter.Emit(new Scalar(itemVal.GetID()));
else {
var arrayVal = value as IMyObject[];
emitter.Emit(new SequenceStart(null, null, true, SequenceStyle.Block));
if (arrayVal != null) {
foreach (var item in arrayVal)
if (item != null) emitter.Emit(new Scalar(item.GetID()));
else emitter.Emit(new Scalar("null"));
}
emitter.Emit(new SequenceEnd());
}
}
}
By calling emitter.Emit(new Scalar("null")) you would get a 'null' entry in the sequence, but if you leave the serialization up to YamlDotNet, it would be serialized as '' (empty string).
How do you output a null value in a sequence as an empty string when writing a custom IYamlTypeConverter?
One way to achieve this is to create a custom IEventEmitter that will add this logic:
public class NullStringsAsEmptyEventEmitter : ChainedEventEmitter
{
public NullStringsAsEmptyEventEmitter(IEventEmitter nextEmitter)
: base(nextEmitter)
{
}
public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
{
if (eventInfo.Source.Type == typeof(string) && eventInfo.Source.Value == null)
{
emitter.Emit(new Scalar(string.Empty));
}
else
{
base.Emit(eventInfo, emitter);
}
}
}
You then register it like this:
var serializer = new SerializerBuilder()
.WithEventEmitter(nextEmitter => new NullStringsAsEmptyEventEmitter(nextEmitter))
.Build();
Here's a fiddle with this code
It seems you can represent a null value simply with '~', according to http://www.yaml.org/refcard.html
I have class in groovy
class WhsDBFile {
String name
String path
String svnUrl
String lastRevision
String lastMessage
String lastAuthor
}
and map object
def installFiles = [:]
that filled in loop by
WhsDBFile dbFile = new WhsDBFile()
installFiles[svnDiffStatus.getPath()] = dbFile
now i try to sort this with custom Comparator
Comparator<WhsDBFile> whsDBFileComparator = new Comparator<WhsDBFile>() {
#Override
int compare(WhsDBFile o1, WhsDBFile o2) {
if (FilenameUtils.getBaseName(o1.name) > FilenameUtils.getBaseName(o2.name)) {
return 1
} else if (FilenameUtils.getBaseName(o1.name) > FilenameUtils.getBaseName(o2.name)) {
return -1
}
return 0
}
}
installFiles.sort(whsDBFileComparator);
but get this error java.lang.String cannot be cast to WhsDBFile
Any idea how to fix this? I need to use custom comparator, cause it will be much more complex in the future.
p.s. full source of sample gradle task (description of WhsDBFile class is above):
project.task('sample') << {
def installFiles = [:]
WhsDBFile dbFile = new WhsDBFile()
installFiles['sample_path'] = dbFile
Comparator<WhsDBFile> whsDBFileComparator = new Comparator<WhsDBFile>() {
#Override
int compare(WhsDBFile o1, WhsDBFile o2) {
if (o1.name > o2.name) {
return 1
} else if (o1.name > o2.name) {
return -1
}
return 0
}
}
installFiles.sort(whsDBFileComparator);
}
You can try to sort the entrySet() :
def sortedEntries = installFiles.entrySet().sort { entry1, entry2 ->
entry1.value <=> entry2.value
}
you will have a collection of Map.Entry with this invocation. In order to have a map, you can then collectEntries() the result :
def sortedMap = installFiles.entrySet().sort { entry1, entry2 ->
...
}.collectEntries()
sort can also take a closure as parameter which coerces to a Comparator's compare() method as below. Usage of toUpper() method just mimics the implementation of FilenameUtils.getBaseName().
installFiles.sort { a, b ->
toUpper(a.value.name) <=> toUpper(b.value.name)
}
// Replicating implementation of FilenameUtils.getBaseName()
// This can be customized according to requirement
String toUpper(String a) {
a.toUpperCase()
}
I have the following code which copies property values from one object to another objects by matching their property names:
public static void CopyProperties(object source, object target,bool caseSenstive=true)
{
PropertyInfo[] targetProperties = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo[] sourceProperties = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo tp in targetProperties)
{
var sourceProperty = sourceProperties.FirstOrDefault(p => p.Name == tp.Name);
if (sourceProperty == null && !caseSenstive)
{
sourceProperty = sourceProperties.FirstOrDefault(p => p.Name.ToUpper() == tp.Name.ToUpper());
}
// If source doesn't have this property, go for next one.
if(sourceProperty ==null)
{
continue;
}
// If target property is not writable then we can not set it;
// If source property is not readable then cannot check it's value
if (!tp.CanWrite || !sourceProperty.CanRead)
{
continue;
}
MethodInfo mget = sourceProperty.GetGetMethod(false);
MethodInfo mset = tp.GetSetMethod(false);
// Get and set methods have to be public
if (mget == null)
{
continue;
}
if (mset == null)
{
continue;
}
var sourcevalue = sourceProperty.GetValue(source, null);
tp.SetValue(target, sourcevalue, null);
}
}
This is working well when the type of properties on target and source are the same. But when there is a need for casting, the code doesn't work.
For example, I have the following object:
class MyDateTime
{
public static implicit operator DateTime?(MyDateTime myDateTime)
{
return myDateTime.DateTime;
}
public static implicit operator DateTime(MyDateTime myDateTime)
{
if (myDateTime.DateTime.HasValue)
{
return myDateTime.DateTime.Value;
}
else
{
return System.DateTime.MinValue;
}
}
public static implicit operator MyDateTime(DateTime? dateTime)
{
return FromDateTime(dateTime);
}
public static implicit operator MyDateTime(DateTime dateTime)
{
return FromDateTime(dateTime);
}
}
If I do the following, the implicit cast is called and everything works well:
MyDateTime x= DateTime.Now;
But when I have a two objects that one of them has a DateTime and the other has MyDateTime, and I am using the above code to copy properties from one object to other, it doesn't and generate an error saying that DateTime can not converted to MyTimeDate.
How can I fix this problem?
One ghastly approach which should work is to mix dynamic and reflection:
private static T ConvertValue<T>(dynamic value)
{
return value; // This will perform conversion automatically
}
Then:
var sourceValue = sourceProperty.GetValue(source, null);
if (sourceProperty.PropertyType != tp.PropertyType)
{
var method = typeof(PropertyCopier).GetMethod("ConvertValue",
BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(new[] { tp.PropertyType };
sourceValue = method.Invoke(null, new[] { sourceValue });
}
tp.SetValue(target, sourceValue, null);
We need to use reflection to invoke the generic method with the right type argument, but dynamic typing will use the right conversion operator for you.
Oh, and one final request: please don't include my name anywhere near this code, whether it's in comments, commit logs. Aargh.