Does Groovy #Canonical work with private fields? - groovy

I keep getting a "Could not find matching constructor" when I use #Canonical annotation with private fields. May I know how I can get the following code to compile and pass the asserts? Thanks :)
#groovy.transform.Canonical(includes = ['x','y'])
#groovy.transform.EqualsAndHashCode(includeFields = true)
class Cell implements Comparable<Cell>{
private int x
private int y
int compareTo(Cell cell){
x <=> cell.x ?: y <=> cell.y ?: 0
}
}
Cell cell = new Cell(1,0);
Cell sameCell = new Cell(1,0);
def setOfLiveCells = [] as Set
assert setOfLiveCells.add(cell) // cell is added
assert cell == sameCell // cell equals
assert ! setOfLiveCells.add(sameCell) //should not assert
Regards,
John

Yes, you can do that.
#Canonical is a shorthand for #TupleConstructor #ToString and #EqualsAndHashCode annotations with default values. [code] You can specify them by hand, or simply add necessary annotation before Canonical. #TupleConstructor allows you to set fields, via includeFields or include annotation fields. [doc]
#groovy.transform.TupleConstructor(includeFields = true)
#groovy.transform.Canonical
class Cell implements Comparable<Cell>{
private int x
private int y
int compareTo(Cell cell){
x <=> cell.x ?: y <=> cell.y ?: 0
}
}
Cell cell = new Cell(1,0);
Cell sameCell = new Cell(1,0);
def setOfLiveCells = [] as Set
assert setOfLiveCells.add(cell) // cell is added
assert cell == sameCell // cell equals
assert ! setOfLiveCells.add(sameCell) //should not assert

Related

Groovy: Class fields default conditional values

I have below sample classes in my application:
class A {
Integer a
String b
Integer c = (a < 5) ? a+5 : a+10
}
class B {
void method1() {
A a = new A(a:4, b:"test")
log.print("c: ", a.c)
}
}
When my code is calling method1, then it outputs c = 14. but ideally it should 9.
How we can solve this without explicitly setting value for c?
Thanks in advance!
Not 100% sure of the question, and the example won't print anything as it will fail to add 5 to null, but an alternative might be to use the #Lazy annotation to defer creation of the value until such time as it is requested, ie:
class A {
Integer a
String b
#Lazy Integer c = { (a < 5) ? a+5 : a+10 }()
}
After it has been requested, it will keep this value, no matter how you change a and b (unless you change c yourself). From the question, I have no idea if this is what you want or not
class A {
Integer a
String b
def getC(){ (a < 5) ? a+5 : a+10 }
}
class B {
def method1() {
A a = new A(a:4, b:"test")
println("c: " + a.c)
}
}
new B().method1()

How to Use Groovy to find the INTERSECTION of two lists?

I am trying to retrieve the common items across two lists using Groovy. The following code works just fine, i.e the output from running this code is "DEBUG found in common Items : same". So far so good!
def list1 = ["same", "different"]
def list2 = ["same", "not the same"]
def commonItems = list1.intersect(list2)
for(int i=0; i < commonItems.size(); i++)
{
log.info("DEBUG found in common Items : " + commonItems[i])
}
I hit an issue when I try to apply the above principle to a list of objects - my issue is that the 'commonItems' list does NOT contain the single object I would expect, but is empty. Please note, my custom object 'ErrorWarningDetail' does override compareTo. Can someone see what I am doing wrong / make any suggestions? Thanks in advance!
First of all here is my custom class - note 'CompateTo' just checks the 'Code' field for now.
class ErrorWarningDetail implements Comparable
{
public String Code
public String Description
public String ErrorType
public String State
#Override
int compareTo(Object o) {
int result = Code <=> o.Code
result
}
}
Now here is the code that does the business. I would expect one object to be in 'commonItems' but it is infact empty - what am i doing wrong here? The output of running this is "DEBUG no common items"
def similarONE = new ErrorWarningDetail()
similarONE.Code = "100-1"
def similarTWO =new ErrorWarningDetail()
similarTWO.Code = "100-1"
def completelyDifferent = new ErrorWarningDetail()
completelyDifferent.Code = "697-2"
def List1 = []
def List2 = []
List1.add(similarONE)
List1.add(completelyDifferent)
List2.add(similarTwo)
def commonItems = list1.intersect(list2)
if (commonItems.size() == 0)
{
log.info("DEBUG no common items")
}
Implementing compareTo() is not enough in Java, you should be implementing equals/hashCode instead.
In Groovy there's a handy annotation for that. So, the script down below executes successfully:
import groovy.transform.EqualsAndHashCode
#EqualsAndHashCode( includes = [ 'code' ] )
class ErrorWarningDetail implements Comparable {
String code
String description
String errorType
String state
#Override
int compareTo(Object o) {
code <=> ( o?.code ?: '' )
}
}
def similarONE = new ErrorWarningDetail( code:"100-1" )
def similarTWO = new ErrorWarningDetail( code:"100-1" )
def completelyDifferent = new ErrorWarningDetail( code:"697-2" )
def list1 = [similarONE, completelyDifferent]
def list2 = [similarTWO]
def commonItems = list1.intersect list2
assert 1 == commonItems.size()
P.S. Please, DO NOT name fields starting with Capital letters!
The equals and hashCode are the methods utilized to determine object equality, so the intersect method would rely on those.
The compareTo method is utilized for sorting purposes.
Groovy has some convenient utilities for common tasks in the package groovy.transform
Below is the modified class with the annotations that makes it work as intended.
#EqualsAndHashCode(includes=["Code"])
#ToString(includeFields=true)
class ErrorWarningDetail implements Comparable
{
String Code
String Description
String ErrorType
String State
#Override
int compareTo(Object o) {
Code <=> o?.Code
}
}

Is there a way to sort values in a CSV file in Scala?

I have a csv file that outputs rows like this:
Distributed Systems|SOFE 4790U|M|14|10|17|0|University Building A1 UA2240
Research Methods|SSCI 2900U|F|14|10|17|0|61 Charles DTA219
Social Control|SSCI 2030U|R|14|10|17|0|61 Charles DTA219
Is there anyway to sort the values in the csv file based on one column? Like say The 3rd column which represents day of the week. How would I go about sorting it by M T W R F in that order? I have been trying to use case classes and overriding the equals/compare method but not sure where to go from what I have?
case class Foo(i: String)
{
override def equals()
}
val a = Foo("F")
val b = Foo("M")
println(a<b)
Any help would be great.
You could implement it as string comparison but it would be cleaner to build separate AST of DayOfWeek type.
sealed trait DayOfWeek extends Comparable[DayOfWeek] {
val value: Int
override def compareTo(o: DayOfWeek): Int = value compareTo o.value
}
object DayOfWeek {
case object M extends DayOfWeek { val value = 0 }
case object T extends DayOfWeek { val value = 1 }
case object W extends DayOfWeek { val value = 2 }
case object R extends DayOfWeek { val value = 3 }
case object F extends DayOfWeek { val value = 4 }
}
Now, we won't see < operator but, fortunately, Scala has concept of extension methods.
object DayOfWeek {
// ...
implicit class syntax(dow: DayOfWeek) {
def <(other: DayOfWeek): Boolean = (dow compareTo other) < 0
}
}
Now you could use it like this
import DayOfWeek._
println(M < T)
// true
and if you wanted to construct this type out of string you might go for something like
object DayOfWeek {
// ...
// this method is unsafe, you might consider returning option instead, this is just for demonstartion purposes
def apply(s: String): DayOfWeek = s match {
case "M" => M
case "T" => T
case "W" => W
case "R" => R
case "F" => F
}
}
import DayOfWeek._
println(DayOfWeek("M") < DayOfWeek("T"))
// true

How to instantiate a class using an explicit first, and a default second argument, via the meta model?

I have the following toplevel class:
class Example(shared String first = "default one", shared String second = "default two") {
}
Now, I want to instantiate this class using an explicit value for first, but the default value for second.
I know how to do this via explicitly compiled code, by using named arguments:
void instantiateExample(String firstValue) {
Example ex = Example { first = firstValue; };
assert(ex.first == firstValue);
assert(ex.second == "default two");
}
Now, I would like to do the same thing as the above code, but by using the Class<Example, []|[String,String=]> object.
with class model it is ...
Class<Example,[]|[String, String=]> exampleModel = `Example`;
value e1 = exampleModel();
value e2 = exampleModel("foo");
value e3 = exampleModel("foo", "bar");
or with class declaration it is ...
ClassDeclaration exampleDeclaration = `class Example`;
assert(is Example e1 = exampleDeclaration.instantiate());
assert(is Example e2 = exampleDeclaration.instantiate([], "foo"));
assert(is Example e3 = exampleDeclaration.instantiate([], "foo", "bar"));
HTH

How do I use groovy's AS keyword

This may be a duplicate but "as" is an INCREDABLY hard keyword to google, even S.O. ignores "as" as part of query.
So I'm wondering how to implement a class that supports "as" reflexively. For an example class:
class X {
private val
public X(def v) {
val=v
}
public asType(Class c) {
if (c == Integer.class)
return val as Integer
if(c == String.class)
return val as String
}
}
This allows something like:
new X(3) as String
to work, but doesn't help with:
3 as X
I probably have to attach/modify the "asType" on String and Integer somehow, but I feel any changes like this should be confined to the "X" class... Can the X class either implement a method like:
X fromObject(object)
or somehow modify the String/Integer class from within X. This seems tough since it won't execute any code in X until X is actually used... what if my first usage of X is "3 as X", will X get a chance to override Integer's asType before Groovy tries to call is?
As you say, it's not going to be easy to change the asType method for Integer to accept X as a new type of transformation (especially without destroying the existing functionality).
The best I can think of is to do:
Integer.metaClass.toX = { -> new X( delegate ) }
And then you can call:
3.toX()
I can't think how 3 as X could be done -- as you say, the other way; new X('3') as Integer is relatively easy.
Actually, you can do this:
// Get a handle on the old `asType` method for Integer
def oldAsType = Integer.metaClass.getMetaMethod( "asType", [Class] as Class[] )
// Then write our own
Integer.metaClass.asType = { Class c ->
if( c == X ) {
new X( delegate )
}
else {
// if it's not an X, call the original
oldAsType.invoke( delegate, c )
}
}
3 as X
This keeps the functionality out of the Integer type, and minimizes scope of the effect (which is good or bad depending on what you're looking for).
This category will apply asType from the Integer side.
class IntegerCategory {
static Object asType(Integer inty, Class c) {
if(c == X) return new X(inty)
else return inty.asType(c)
}
}
use (IntegerCategory) {
(3 as X) instanceof X
}

Resources