This is an automated email from the ASF dual-hosted git repository. sunlan pushed a commit to branch GROOVY-9637 in repository https://gitbox.apache.org/repos/asf/groovy.git
commit aa8d1778037910b188c0604011c970df52eec6ea Author: Daniel Sun <sun...@apache.org> AuthorDate: Mon Jul 13 02:40:57 2020 +0800 GROOVY-9637: Improve the performance of GString --- src/main/java/groovy/lang/GString.java | 47 +++++++++++++++++++++++++++++++++- src/test/groovy/GStringTest.groovy | 16 ++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/main/java/groovy/lang/GString.java b/src/main/java/groovy/lang/GString.java index 207a1a6..ec1c06a 100644 --- a/src/main/java/groovy/lang/GString.java +++ b/src/main/java/groovy/lang/GString.java @@ -27,6 +27,11 @@ import java.io.IOException; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import java.util.regex.Pattern; /** @@ -48,6 +53,22 @@ public abstract class GString extends GroovyObjectSupport implements Comparable, public static final String[] EMPTY_STRING_ARRAY = new String[0]; public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + private static final Set<Class<?>> IMMUTABLE_TYPE_LIST = + new HashSet<>( + Arrays.asList( + String.class, Class.class, + BigDecimal.class, BigInteger.class, + boolean.class, Boolean.class, + char.class, Character.class, + byte.class, Byte.class, + short.class, Short.class, + int.class, Integer.class, + long.class, Long.class, + float.class, Float.class, + double.class, Double.class + ) + ); + /** * A GString containing a single empty String and no values. */ @@ -68,13 +89,16 @@ public abstract class GString extends GroovyObjectSupport implements Comparable, private final Object[] values; + private final boolean immutable; public GString(Object values) { this.values = (Object[]) values; + this.immutable = checkValuesImmutable(); } public GString(Object[] values) { this.values = values; + this.immutable = checkValuesImmutable(); } // will be static in an instance @@ -150,8 +174,15 @@ public abstract class GString extends GroovyObjectSupport implements Comparable, return values[idx]; } + + private String cachedStringLiteral; + @Override public String toString() { + if (null != cachedStringLiteral) { + return cachedStringLiteral; + } + Writer buffer = new StringBuilderWriter(calcInitialCapacity()); try { writeTo(buffer); @@ -159,7 +190,9 @@ public abstract class GString extends GroovyObjectSupport implements Comparable, throw new StringWriterIOException(e); } - return buffer.toString(); + String str = buffer.toString(); + + return immutable ? (cachedStringLiteral = str) : str; } private int calcInitialCapacity() { @@ -276,4 +309,16 @@ public abstract class GString extends GroovyObjectSupport implements Comparable, public byte[] getBytes(String charset) throws UnsupportedEncodingException { return toString().getBytes(charset); } + + private boolean checkValuesImmutable() { + for (Object value : values) { + if (null == value) continue; + if (!(IMMUTABLE_TYPE_LIST.contains(value.getClass()) + || (value instanceof GString && ((GString) value).immutable))) { + return false; + } + } + + return true; + } } diff --git a/src/test/groovy/GStringTest.groovy b/src/test/groovy/GStringTest.groovy index 90fb455..00c75eb 100644 --- a/src/test/groovy/GStringTest.groovy +++ b/src/test/groovy/GStringTest.groovy @@ -20,6 +20,8 @@ package groovy import groovy.test.GroovyTestCase +import java.lang.reflect.Field + class GStringTest extends GroovyTestCase { void check(template, teststr) { @@ -582,4 +584,18 @@ class GStringTest extends GroovyTestCase { assert Eval.me('''def foo='bar'; /$foo\u002abaz/''') == 'bar*baz' assert Eval.me('''def foo='bar'; /${foo}\u002abaz/''') == 'bar*baz' } + + void testNestedGString() { + def gstr = "a${"${123}"}b" + assert 'a123b' == gstr + assert gstr.toString() === gstr.toString() + + Field immutableField = GString.getDeclaredFields().find {f -> f.name == 'immutable'} + immutableField.setAccessible(true) + assert true == immutableField.get(gstr) + + Field cachedStringLiteralField = GString.getDeclaredFields().find {f -> f.name == 'cachedStringLiteral'} + cachedStringLiteralField.setAccessible(true) + assert 'a123b' == cachedStringLiteralField.get(gstr) + } }