diff --git a/src/main/groovy/lang/IgnoreDefaultEqualsAndToString.java b/src/main/groovy/lang/IgnoreDefaultEqualsAndToString.java new file mode 100644 index 000000000000..07a9b37a8472 --- /dev/null +++ b/src/main/groovy/lang/IgnoreDefaultEqualsAndToString.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package groovy.lang; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Classes marked with this {@link IgnoreDefaultEqualsAndToString} annotation + * by-pass the default groovy formatting and equality rules and allowing + * a user to provide a custom format and equals method + * + * @author Paolo Di Tommaso + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface IgnoreDefaultEqualsAndToString { +} diff --git a/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java index 03f061cf87fc..e19037146fb3 100644 --- a/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java +++ b/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java @@ -12026,6 +12026,9 @@ public static boolean equals(List left, List right) { if (left == right) { return true; } + if( left.getClass().getAnnotation(IgnoreDefaultEqualsAndToString.class)!=null && right.getClass().getAnnotation(IgnoreDefaultEqualsAndToString.class)!=null ) { + return left.equals(right); + } if (left.size() != right.size()) { return false; } @@ -12078,6 +12081,9 @@ public static boolean equals(Set self, Set other) { if (self == other) { return true; } + if( self.getClass().getAnnotation(IgnoreDefaultEqualsAndToString.class)!=null && other.getClass().getAnnotation(IgnoreDefaultEqualsAndToString.class)!=null ) { + return self.equals(other); + } if (self.size() != other.size()) { return false; } @@ -12122,6 +12128,9 @@ public static boolean equals(Map self, Map other) { if (self == other) { return true; } + if( self.getClass().getAnnotation(IgnoreDefaultEqualsAndToString.class)!=null && other.getClass().getAnnotation(IgnoreDefaultEqualsAndToString.class)!=null ) { + return self.equals(other); + } if (self.size() != other.size()) { return false; } diff --git a/src/main/org/codehaus/groovy/runtime/InvokerHelper.java b/src/main/org/codehaus/groovy/runtime/InvokerHelper.java index 6f7406ac4319..5384ee3bb690 100644 --- a/src/main/org/codehaus/groovy/runtime/InvokerHelper.java +++ b/src/main/org/codehaus/groovy/runtime/InvokerHelper.java @@ -25,6 +25,7 @@ import groovy.lang.GroovyObject; import groovy.lang.GroovyRuntimeException; import groovy.lang.GroovySystem; +import groovy.lang.IgnoreDefaultEqualsAndToString; import groovy.lang.MetaClass; import groovy.lang.MetaClassRegistry; import groovy.lang.MissingMethodException; @@ -688,6 +689,9 @@ private static String formatMap(Map map, boolean verbose, int maxSize, boolean s if (map.isEmpty()) { return "[:]"; } + if (map.getClass().getAnnotation(IgnoreDefaultEqualsAndToString.class)!=null) { + return map.toString(); + } StringBuilder buffer = new StringBuilder(ITEM_ALLOCATE_SIZE * map.size() * 2); buffer.append('['); boolean first = true; @@ -723,6 +727,9 @@ private static int sizeLeft(int maxSize, StringBuilder buffer) { } private static String formatCollection(Collection collection, boolean verbose, int maxSize, boolean safe) { + if (collection.getClass().getAnnotation(IgnoreDefaultEqualsAndToString.class)!=null) { + return collection.toString(); + } StringBuilder buffer = new StringBuilder(ITEM_ALLOCATE_SIZE * collection.size()); buffer.append('['); boolean first = true; diff --git a/src/test/org/codehaus/groovy/runtime/DefaultGroovyMethodsTest.groovy b/src/test/org/codehaus/groovy/runtime/DefaultGroovyMethodsTest.groovy index ebea8fd46eeb..30bc1311c86f 100644 --- a/src/test/org/codehaus/groovy/runtime/DefaultGroovyMethodsTest.groovy +++ b/src/test/org/codehaus/groovy/runtime/DefaultGroovyMethodsTest.groovy @@ -266,4 +266,56 @@ public class DefaultGroovyMethodsTest extends GroovyTestCase { delegate.iterator() } } + + + @IgnoreDefaultEqualsAndToString + private static class CustomList extends ArrayList { + CustomList( Object... items) { + addAll(items) + } + + @Override + boolean equals(Object other) { + return this.sum() == other.sum() + } + } + + @IgnoreDefaultEqualsAndToString + private static class CustomSet extends HashSet { + CustomSet( Object... items) { + addAll(items) + } + + @Override + boolean equals(Object other) { + return this.collect { it.toString().toUpperCase() } == other.collect { it.toString().toUpperCase() } + } + } + + @IgnoreDefaultEqualsAndToString + private static class CustomMap extends HashMap { + CustomMap(Map params) { + this.putAll(params) + } + + boolean equals(Object other) { + this.values().sum() == other.values().sum() + } + } + + public void testCustomEqualsForList() { + + assertTrue(new CustomList(1,2,3).equals(new CustomList(3,3))) + assertTrue(new CustomList(1,2,3) == new CustomList(3,3) ) + + assertTrue(new CustomSet('a','b','c').equals(new CustomSet('A','B','C'))) + assertTrue(new CustomSet('a','b','c') == new CustomSet('A','B','C') ) + + assertTrue(new CustomMap(a:10) == new CustomMap(b:3,c:7)) + assertTrue(new CustomMap(a:10).equals(new CustomMap(b:3,c:7))) + assertFalse(new CustomMap(a:1) == new CustomMap(b:2)) + + } + + } diff --git a/src/test/org/codehaus/groovy/runtime/InvokerHelperFormattingTest.groovy b/src/test/org/codehaus/groovy/runtime/InvokerHelperFormattingTest.groovy index 3c327e9a3910..2d1a1e3ad5f7 100644 --- a/src/test/org/codehaus/groovy/runtime/InvokerHelperFormattingTest.groovy +++ b/src/test/org/codehaus/groovy/runtime/InvokerHelperFormattingTest.groovy @@ -214,4 +214,42 @@ class InvokerHelperFormattingTest extends GroovyTestCase { assert '[(this Map):(this Map)]' == InvokerHelper.toString(m) } + + + public void testIgnoreDefaultToStringForCustomList() { + + String TEST = "" + + "@IgnoreDefaultEqualsAndToString \n" + + "class Foo extends ArrayList { \n" + + " String toString() { return this.reverse().join('-') } \n" + + "}\n" + + "" + + "def foo = new Foo()\n" + + "foo << 1 << 2 << 3 \n" + + "assert foo.toString() == \"1-2-3\"\n" + + "assert \"$foo\".toString() == \"1-2-3\"\n" + + "return true"; + + assertTrue((Boolean)Eval.me(TEST)); + + } + + public void testIgnoreDefaultToStringForCustomMap() { + + String TEST = "" + + "@IgnoreDefaultEqualsAndToString \n" + + "class Foo extends HashMap { \n" + + " String toString() { return 'MyCustomToString' } \n" + + "}\n" + + "" + + "def foo = new Foo()\n" + + "assert foo.toString() == 'MyCustomToString'\n" + + "assert \"$foo\".toString() == 'MyCustomToString'\n" + + "return true"; + + assertTrue((Boolean)Eval.me(TEST)); + + } + + }