GROOVY-7563: InvokerHelper: Safe format recursively and consistently (not just for lists)
Project: http://git-wip-us.apache.org/repos/asf/groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/e28f0014 Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/e28f0014 Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/e28f0014 Branch: refs/heads/master Commit: e28f0014d17458a9f964c74c400a390100497966 Parents: ff4c62e Author: Thibault Kruse <thibault.kr...@gmx.de> Authored: Wed Sep 2 19:38:55 2015 +0200 Committer: paulk <pa...@asert.com.au> Committed: Fri Jul 29 08:36:43 2016 +1000 ---------------------------------------------------------------------- .../codehaus/groovy/runtime/InvokerHelper.java | 75 +++++++++----------- .../runtime/InvokerHelperFormattingTest.groovy | 20 ++++-- 2 files changed, 50 insertions(+), 45 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/groovy/blob/e28f0014/src/main/org/codehaus/groovy/runtime/InvokerHelper.java ---------------------------------------------------------------------- diff --git a/src/main/org/codehaus/groovy/runtime/InvokerHelper.java b/src/main/org/codehaus/groovy/runtime/InvokerHelper.java index 632b79b..53055f0 100644 --- a/src/main/org/codehaus/groovy/runtime/InvokerHelper.java +++ b/src/main/org/codehaus/groovy/runtime/InvokerHelper.java @@ -124,7 +124,7 @@ public class InvokerHelper { } public static String toString(Object arguments) { - return format(arguments, false); + return format(arguments, false, -1, false); } public static String inspect(Object self) { @@ -573,19 +573,23 @@ public class InvokerHelper { } public static String format(Object arguments, boolean verbose, int maxSize) { + return format(arguments, verbose, maxSize, false); + } + + public static String format(Object arguments, boolean verbose, int maxSize, boolean safe) { if (arguments == null) { final NullObject nullObject = NullObject.getNullObject(); return (String) nullObject.getMetaClass().invokeMethod(nullObject, "toString", EMPTY_ARGS); } if (arguments.getClass().isArray()) { if (arguments instanceof Object[]) { - return toArrayString((Object[]) arguments, verbose, maxSize, false); + return toArrayString((Object[]) arguments, verbose, maxSize, safe); } if (arguments instanceof char[]) { return new String((char[]) arguments); } // other primitives - return formatCollection(DefaultTypeTransformation.arrayAsCollection(arguments), verbose, maxSize); + return formatCollection(DefaultTypeTransformation.arrayAsCollection(arguments), verbose, maxSize, safe); } if (arguments instanceof Range) { Range range = (Range) arguments; @@ -596,10 +600,10 @@ public class InvokerHelper { } } if (arguments instanceof Collection) { - return formatCollection((Collection) arguments, verbose, maxSize); + return formatCollection((Collection) arguments, verbose, maxSize, safe); } if (arguments instanceof Map) { - return formatMap((Map) arguments, verbose, maxSize); + return formatMap((Map) arguments, verbose, maxSize, safe); } if (arguments instanceof Element) { try { @@ -626,7 +630,15 @@ public class InvokerHelper { } // TODO: For GROOVY-2599 do we need something like below but it breaks other things // return (String) invokeMethod(arguments, "toString", EMPTY_ARGS); - return arguments.toString(); + try { + return arguments.toString(); + } catch (RuntimeException ex) { + if (!safe) throw ex; + return handleFormattingException(arguments, ex); + } catch (Exception ex) { + if (!safe) throw new GroovyRuntimeException(ex); + return handleFormattingException(arguments, ex); + } } public static String escapeBackslashes(String orig) { @@ -639,7 +651,18 @@ public class InvokerHelper { .replaceAll("\\f", "\\\\f"); // form feed } - private static String formatMap(Map map, boolean verbose, int maxSize) { + private static String handleFormattingException(Object item, Exception ex) { + + String hash; + try { + hash = Integer.toHexString(item.hashCode()); + } catch (Exception ignored) { + hash = "????"; + } + return "<" + item.getClass().getName() + "@" + hash + ">"; + } + + private static String formatMap(Map map, boolean verbose, int maxSize, boolean safe) { if (map.isEmpty()) { return "[:]"; } @@ -662,7 +685,7 @@ public class InvokerHelper { if (entry.getValue() == map) { buffer.append("(this Map)"); } else { - buffer.append(format(entry.getValue(), verbose, sizeLeft(maxSize, buffer))); + buffer.append(format(entry.getValue(), verbose, sizeLeft(maxSize, buffer), safe)); } } buffer.append(']'); @@ -673,10 +696,6 @@ public class InvokerHelper { return maxSize == -1 ? maxSize : Math.max(0, maxSize - buffer.length()); } - private static String formatCollection(Collection collection, boolean verbose, int maxSize) { - return formatCollection(collection, verbose, maxSize, false); - } - private static String formatCollection(Collection collection, boolean verbose, int maxSize, boolean safe) { StringBuilder buffer = new StringBuilder(ITEM_ALLOCATE_SIZE * collection.size()); buffer.append('['); @@ -694,20 +713,7 @@ public class InvokerHelper { if (item == collection) { buffer.append("(this Collection)"); } else { - String str; - try { - str = format(item, verbose, sizeLeft(maxSize, buffer)); - } catch (Exception ex) { - if (!safe) throw new GroovyRuntimeException(ex); - String hash; - try { - hash = Integer.toHexString(item.hashCode()); - } catch (Exception ignored) { - hash = "????"; - } - str = "<" + item.getClass().getName() + "@" + hash + ">"; - } - buffer.append(str); + buffer.append(format(item, verbose, sizeLeft(maxSize, buffer), safe)); } } buffer.append(']'); @@ -752,7 +758,7 @@ public class InvokerHelper { * @return the string representation of the map */ public static String toMapString(Map arg, int maxSize) { - return formatMap(arg, false, maxSize); + return formatMap(arg, false, maxSize, false); } /** @@ -820,20 +826,7 @@ public class InvokerHelper { if (item == collection) { argBuf.append("(this Collection)"); } else { - String str; - try { - str = format(item, verbose, sizeLeft(maxSize, argBuf)); - } catch (Exception ex) { - if (!safe) throw new GroovyRuntimeException(ex); - String hash; - try { - hash = Integer.toHexString(item.hashCode()); - } catch (Exception ignored) { - hash = "????"; - } - str = "<" + item.getClass().getName() + "@" + hash + ">"; - } - argBuf.append(str); + argBuf.append(format(item, verbose, sizeLeft(maxSize, argBuf), safe)); } } argBuf.append(']'); http://git-wip-us.apache.org/repos/asf/groovy/blob/e28f0014/src/test/org/codehaus/groovy/runtime/InvokerHelperFormattingTest.groovy ---------------------------------------------------------------------- diff --git a/src/test/org/codehaus/groovy/runtime/InvokerHelperFormattingTest.groovy b/src/test/org/codehaus/groovy/runtime/InvokerHelperFormattingTest.groovy index 7c70159..bed83fa 100644 --- a/src/test/org/codehaus/groovy/runtime/InvokerHelperFormattingTest.groovy +++ b/src/test/org/codehaus/groovy/runtime/InvokerHelperFormattingTest.groovy @@ -72,6 +72,7 @@ class InvokerHelperFormattingTest extends GroovyTestCase { shouldFail(UnsupportedOperationException) { InvokerHelper.format(eObject, false) } + assert InvokerHelper.format(new ExceptionOnToString(), false, -1, true) =~ (ExceptionOnToString.MATCHER) } public void testFormatRanges() { @@ -79,6 +80,7 @@ class InvokerHelperFormattingTest extends GroovyTestCase { assert "a'b..a'd" == InvokerHelper.format('a\'b'..'a\'d', false) assert "[1..4]" == InvokerHelper.format([1..4], false) assert "[a'b..a'd]" == InvokerHelper.format(['a\'b'..'a\'d'], false) + // verbose assert '1..4' == InvokerHelper.format(1..4, true) assert "'a\\'b'..'a\\'d'" == InvokerHelper.format('a\'b'..'a\'d', true) @@ -104,16 +106,16 @@ class InvokerHelperFormattingTest extends GroovyTestCase { Object eObject = new ExceptionOnToString() assert InvokerHelper.toListString([eObject], -1, true) =~ "\\[${ExceptionOnToString.MATCHER}\\]" List list = [[z: eObject]] - assert InvokerHelper.toListString(list, -1, true) == '[<java.util.LinkedHashMap@' + Integer.toHexString(list[0].hashCode()) +'>]' + assert InvokerHelper.toListString(list, -1, true) =~ "\\[\\[z:${ExceptionOnToString.MATCHER}\\]\\]" // even when throwing object is deeply nested, exception handling only happens in Collection list = [[x: [y: [z: eObject, a: 2, b: 4]]]] - assert InvokerHelper.toListString(list, -1, true) == '[<java.util.LinkedHashMap@' + Integer.toHexString(list[0].hashCode()) + '>]' + assert InvokerHelper.toListString(list, -1, true) =~ "\\[\\[x:\\[y:\\[z:${ExceptionOnToString.MATCHER}, a:2, b:4\\]\\]\\]\\]" list = [[eObject, 1, 2]] // safe argument is not passed on recursively - assert InvokerHelper.toListString(list, -1, true) == '[<java.util.ArrayList@' + Integer.toHexString(list[0].hashCode()) + '>]' + assert InvokerHelper.toListString(list, -1, true) =~ "\\[\\[${ExceptionOnToString.MATCHER}, 1, 2\\]\\]" list = [[[[[eObject, 1, 2]]]]] - assert InvokerHelper.toListString(list, -1, true) == '[<java.util.ArrayList@' + Integer.toHexString(list[0].hashCode()) + '>]' + assert InvokerHelper.toListString(list, -1, true) =~ "\\[\\[\\[\\[\\[${ExceptionOnToString.MATCHER}, 1, 2\\]\\]\\]\\]\\]" shouldFail(UnsupportedOperationException) { InvokerHelper.toListString([eObject], -1, false) @@ -147,6 +149,8 @@ class InvokerHelperFormattingTest extends GroovyTestCase { shouldFail(UnsupportedOperationException) { InvokerHelper.format([eObject] as ExceptionOnToString[], false) } + + assert InvokerHelper.format([new ExceptionOnToString()] as Object[], false, -1, true) =~ "\\[${ExceptionOnToString.MATCHER}\\]" } public void testToStringMaps() { @@ -157,12 +161,18 @@ class InvokerHelperFormattingTest extends GroovyTestCase { public void testFormatMaps() { assert '[:]' == InvokerHelper.format([:], false) assert "[a'b:1, 2:b'c]" == InvokerHelper.format(['a\'b':1, 2:'b\'c'], false) + assert "['a\\'b':1, 2:'b\\'c']" == InvokerHelper.format(['a\'b':1, 2:'b\'c'], true, -1, true) Object eObject = new ExceptionOnToString() shouldFail(UnsupportedOperationException) { InvokerHelper.format([foo: eObject], false) } + assert InvokerHelper.format([foo: eObject], false, -1, true) =~ "\\[foo:${ExceptionOnToString.MATCHER}\\]" + assert InvokerHelper.format([foo: eObject], true, -1, true) =~ "\\['foo':${ExceptionOnToString.MATCHER}\\]" + Map m = [:] + m.put(eObject, eObject) + assert InvokerHelper.format(m, false, -1, true) =~ "\\[${ExceptionOnToString.MATCHER}:${ExceptionOnToString.MATCHER}\\]" } public void testToMapString() { @@ -188,6 +198,8 @@ class InvokerHelperFormattingTest extends GroovyTestCase { list.add(['h', 'i'] as String[]) list.add([10, 11] as int[]) assert "[key:[[a'b:c'd], [e, f, g], 5..9, fog..fop, [h, i], [10, 11]]]" == InvokerHelper.toString([key: list]) + + assert "['key':[['a\\'b':'c\\'d'], ['e', 'f', 'g'], 5..9, 'fog'..'fop', ['h', 'i'], [10, 11]]]" == InvokerHelper.format([key:list], true, -1, false) } public void testToStringSelfContained() {