Mykola Golubyev
Mykola Golubyev

Reputation: 59844

Groovy compareTo for CustomClass and numbers/strings

I am building DSL and try to define a custom class CustomClass that you can use in expressions like

def result = customInstance >= 100 ? 'a' : 'b'
if (customInstance == 'hello') {...}

Groovy doesn't call == when your class defines equals and implements Comparable (defines compareTo) at the same time.

Instead Groovy calls compareToWithEqualityCheck which has a branching logic. And unless your custom DSL class is assignable from String or Number your custom compareTo won't be called for the example above.

You can't extend CustomClass with String. I feel like I am missing something. Hope you can help me figure out how to implement a simple case like I showed above.

Upvotes: 4

Views: 320

Answers (1)

hlg
hlg

Reputation: 1331

Here is a short answer first: You could extend GString for the CustomClass. Then its compareTo method will be called in both cases - when you check for equality and when you actually compare.

Edit: Considering the following cases, it will work for 1 and 2, but not for 3.

customInstance >= 100      // case 1
customInstance == 'hallo'  // case 2
customInstance == 10       // case 3

Now I will explain what I understand from the implementation in Groovy's ScriptBytecodeAdapter and DefaultTypeTransformation.

For the == operator, in case Comparable is implemented (and there is no simple identity), it tries to use the interface method compareTo, hence the same logic that is used for other comparison operators. Only if Comparable is not implemented it tries to determine equality based on some smart type adjustments and as an ultima ratio falls back to calling the equals method. This happens in DefaultTypeTransformation.compareEqual#L603-L608

For all other comparison operators such as >=, Groovy delegates to the compareToWithEqualityCheck method. Now this method is called with the equalityCheckOnly flag set to false, while it is set to true for the first case when it the invocation originates from the == operator. Again there is some Groovy smartness happening based on the type of the left side if it is Number, Character, or String. If none applies it ends up calling the compareTo method in DefaultTypeTransformation.compareToWithEqualityCheck#L584-L586.

Now, this happens only if

!equalityCheckOnly || left.getClass().isAssignableFrom(right.getClass())
  || (right.getClass() != Object.class && right.getClass().isAssignableFrom(left.getClass())) //GROOVY-4046
  || (left instanceof GString && right instanceof String)

There are some restrictions for the case of equalityCheckOnly, hence when we come from the == operator. While I can not explain all of those I believe these are to prevent exceptions to be thrown under specific circumstances, such as the issue mentioned in the comment.

For brevity I omitted above that there are also cases that are handled upfront in the ScriptBytecodeAdapter and delegated to equals right away, if left and right hand side are both of the same type and one of Integer, Double or Long.

Upvotes: 1

Related Questions