You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2016/05/31 02:01:01 UTC

[1/3] groovy git commit: GROOVY-6950: StringGroovyMethods minor performance improvements (avoid calling toString)

Repository: groovy
Updated Branches:
  refs/heads/master a88c03474 -> 432a8e5ed


GROOVY-6950: StringGroovyMethods minor performance improvements (avoid calling toString)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/24043a5c
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/24043a5c
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/24043a5c

Branch: refs/heads/master
Commit: 24043a5c56fc020d8a5e633f7bd862033cbecf75
Parents: a88c034
Author: paulk <pa...@asert.com.au>
Authored: Mon May 30 17:46:28 2016 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Mon May 30 17:46:28 2016 +1000

----------------------------------------------------------------------
 .../groovy/runtime/StringGroovyMethods.java     | 115 ++++++++-----------
 .../groovy/GroovyCharSequenceMethodsTest.groovy |  95 ++++++++-------
 2 files changed, 95 insertions(+), 115 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/24043a5c/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java b/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
index d782810..058b270 100644
--- a/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
+++ b/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
@@ -257,9 +257,8 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 2.5.0
      */
     public static String uncapitalize(CharSequence self) {
-        String s = self.toString();
-        if (s == null || s.length() == 0) return s;
-        return Character.toLowerCase(s.charAt(0)) + s.substring(1);
+        if (self.length() == 0) return "";
+        return "" + Character.toLowerCase(self.charAt(0)) + self.subSequence(1, self.length());
     }
 
     /**
@@ -279,9 +278,8 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String capitalize(CharSequence self) {
-        String s = self.toString();
-        if (s == null || s.length() == 0) return s;
-        return Character.toUpperCase(s.charAt(0)) + s.substring(1);
+        if (self.length() == 0) return "";
+        return "" + Character.toUpperCase(self.charAt(0)) + self.subSequence(1, self.length());
     }
 
     /**
@@ -341,20 +339,19 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String center(CharSequence self, Number numberOfChars, CharSequence padding) {
-        String s = self.toString();
         String padding1 = padding.toString();
         int numChars = numberOfChars.intValue();
-        if (numChars <= s.length()) {
-            return s;
+        if (numChars <= self.length()) {
+            return self.toString();
         } else {
-            int charsToAdd = numChars - s.length();
+            int charsToAdd = numChars - self.length();
             String semiPad = charsToAdd % 2 == 1 ?
                     getPadding(padding1, charsToAdd / 2 + 1) :
                     getPadding(padding1, charsToAdd / 2);
             if (charsToAdd % 2 == 0)
-                return semiPad + s + semiPad;
+                return semiPad + self + semiPad;
             else
-                return semiPad.substring(0, charsToAdd / 2) + s + semiPad;
+                return semiPad.substring(0, charsToAdd / 2) + self + semiPad;
         }
     }
 
@@ -440,7 +437,6 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String denormalize(final CharSequence self) {
-        final String s = self.toString();
         // Don't do this in static initializer because we may never be needed.
         // TODO: Put this lineSeparator property somewhere everyone can use it.
         if (lineSeparator == null) {
@@ -459,10 +455,10 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
             }
         }
 
-        final int len = s.length();
+        final int len = self.length();
 
         if (len < 1) {
-            return s;
+            return self.toString();
         }
 
         final StringBuilder sb = new StringBuilder((110 * len) / 100);
@@ -470,14 +466,14 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
         int i = 0;
 
         while (i < len) {
-            final char ch = s.charAt(i++);
+            final char ch = self.charAt(i++);
 
             switch (ch) {
                 case '\r':
                     sb.append(lineSeparator);
 
                     // Eat the following LF if any.
-                    if ((i < len) && (s.charAt(i) == '\n')) {
+                    if ((i < len) && (self.charAt(i) == '\n')) {
                         ++i;
                     }
 
@@ -826,23 +822,22 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String expand(CharSequence self, int tabStop) {
-        String s = self.toString();
-        if (s.length() == 0) return s;
+        if (self.length() == 0) return self.toString();
         try {
             StringBuilder builder = new StringBuilder();
-            for (String line : readLines(s)) {
-                builder.append(expandLine(line, tabStop));
+            for (String line : readLines(self)) {
+                builder.append(expandLine((CharSequence)line, tabStop));
                 builder.append("\n");
             }
             // remove the normalized ending line ending if it was not present
-            if (!s.endsWith("\n")) {
+            if (self.charAt(self.length() - 1) != '\n') {
                 builder.deleteCharAt(builder.length() - 1);
             }
             return builder.toString();
         } catch (IOException e) {
             /* ignore */
         }
-        return s;
+        return self.toString();
     }
 
     /**
@@ -1573,11 +1568,11 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
         return counter;
     }
 
-    private static String getPadding(String padding, int length) {
+    private static String getPadding(CharSequence padding, int length) {
         if (padding.length() < length) {
             return multiply(padding, length / padding.length() + 1).substring(0, length);
         } else {
-            return padding.substring(0, length);
+            return "" + padding.subSequence(0, length);
         }
     }
 
@@ -1632,9 +1627,8 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static boolean isAllWhitespace(CharSequence self) {
-        String s = self.toString();
-        for (int i = 0; i < s.length(); i++) {
-            if (!Character.isWhitespace(s.charAt(i)))
+        for (int i = 0; i < self.length(); i++) {
+            if (!Character.isWhitespace(self.charAt(i)))
                 return false;
         }
         return true;
@@ -1720,11 +1714,10 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static boolean isCase(CharSequence caseValue, Object switchValue) {
-        String s = caseValue.toString();
         if (switchValue == null) {
-            return s == null;
+            return caseValue == null;
         }
-        return s.equals(switchValue.toString());
+        return caseValue.toString().equals(switchValue.toString());
     }
 
     /**
@@ -2096,16 +2089,15 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String multiply(CharSequence self, Number factor) {
-        String s = self.toString();
         int size = factor.intValue();
         if (size == 0)
             return "";
         else if (size < 0) {
             throw new IllegalArgumentException("multiply() should be called with a number of 0 or greater not: " + size);
         }
-        StringBuilder answer = new StringBuilder(s);
+        StringBuilder answer = new StringBuilder(self);
         for (int i = 1; i < size; i++) {
-            answer.append(s);
+            answer.append(self);
         }
         return answer.toString();
     }
@@ -2265,12 +2257,11 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String padLeft(CharSequence self, Number numberOfChars, CharSequence padding) {
-        String s = self.toString();
         int numChars = numberOfChars.intValue();
-        if (numChars <= s.length()) {
-            return s;
+        if (numChars <= self.length()) {
+            return self.toString();
         } else {
-            return getPadding(padding.toString(), numChars - s.length()) + s;
+            return getPadding(padding.toString(), numChars - self.length()) + self;
         }
     }
 
@@ -2343,12 +2334,11 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String padRight(CharSequence self, Number numberOfChars, CharSequence padding) {
-        String s = self.toString();
         int numChars = numberOfChars.intValue();
-        if (numChars <= s.length()) {
-            return s;
+        if (numChars <= self.length()) {
+            return self.toString();
         } else {
-            return s + getPadding(padding.toString(), numChars - s.length());
+            return self + getPadding(padding.toString(), numChars - self.length());
         }
     }
 
@@ -3134,11 +3124,10 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String stripIndent(CharSequence self) {
-        String s = self.toString();
-        if (s.length() == 0) return s;
+        if (self.length() == 0) return self.toString();
         int runningCount = -1;
         try {
-            for (String line : readLines((CharSequence) s)) {
+            for (String line : readLines(self)) {
                 // don't take blank lines into account for calculating the indent
                 if (isAllWhitespace((CharSequence) line)) continue;
                 if (runningCount == -1) runningCount = line.length();
@@ -3148,7 +3137,7 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
         } catch (IOException e) {
             /* ignore */
         }
-        return stripIndent(s, runningCount == -1 ? 0 : runningCount);
+        return stripIndent(self, runningCount == -1 ? 0 : runningCount);
     }
 
     /**
@@ -3164,11 +3153,10 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String stripIndent(CharSequence self, int numChars) {
-        String s = self.toString();
-        if (s.length() == 0 || numChars <= 0) return s;
+        if (self.length() == 0 || numChars <= 0) return self.toString();
         try {
             StringBuilder builder = new StringBuilder();
-            for (String line : readLines((CharSequence) s)) {
+            for (String line : readLines(self)) {
                 // normalize an empty or whitespace line to \n
                 // or strip the indent for lines containing non-space characters
                 if (!isAllWhitespace((CharSequence) line)) {
@@ -3177,14 +3165,14 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
                 builder.append("\n");
             }
             // remove the normalized ending line ending if it was not present
-            if (!s.endsWith("\n")) {
+            if (self.charAt(self.length() - 1) != '\n') {
                 builder.deleteCharAt(builder.length() - 1);
             }
             return builder.toString();
         } catch (IOException e) {
             /* ignore */
         }
-        return s;
+        return self.toString();
     }
 
     /**
@@ -3245,23 +3233,22 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String stripMargin(CharSequence self, char marginChar) {
-        String s = self.toString();
-        if (s.length() == 0) return s;
+        if (self.length() == 0) return self.toString();
         try {
             StringBuilder builder = new StringBuilder();
-            for (String line : readLines((CharSequence) s)) {
+            for (String line : readLines(self)) {
                 builder.append(stripMarginFromLine(line, marginChar));
                 builder.append("\n");
             }
             // remove the normalized ending line ending if it was not present
-            if (!s.endsWith("\n")) {
+            if (self.charAt(self.length() - 1) != '\n') {
                 builder.deleteCharAt(builder.length() - 1);
             }
             return builder.toString();
         } catch (IOException e) {
             /* ignore */
         }
-        return s;
+        return self.toString();
     }
 
     /**
@@ -3275,11 +3262,10 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String stripMargin(CharSequence self, CharSequence marginChar) {
-        String s = self.toString();
         String mc = marginChar.toString();
-        if (mc == null || mc.length() == 0) return stripMargin((CharSequence) s, '|');
+        if (mc.length() == 0) return stripMargin(self, '|');
         // TODO IllegalArgumentException for marginChar.length() > 1 ? Or support String as marker?
-        return stripMargin((CharSequence) s, mc.charAt(0));
+        return stripMargin(self, mc.charAt(0));
     }
 
     /**
@@ -3763,23 +3749,22 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String unexpand(CharSequence self, int tabStop) {
-        String s = self.toString();
-        if (s.length() == 0) return s;
+        if (self.length() == 0) return self.toString();
         try {
             StringBuilder builder = new StringBuilder();
-            for (String line : readLines((CharSequence) s)) {
-                builder.append(unexpandLine(line, tabStop));
+            for (String line : readLines(self)) {
+                builder.append(unexpandLine((CharSequence)line, tabStop));
                 builder.append("\n");
             }
             // remove the normalized ending line ending if it was not present
-            if (!s.endsWith("\n")) {
+            if (self.charAt(self.length() - 1) != '\n') {
                 builder.deleteCharAt(builder.length() - 1);
             }
             return builder.toString();
         } catch (IOException e) {
             /* ignore */
         }
-        return s;
+        return self.toString();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/groovy/blob/24043a5c/src/test/groovy/GroovyCharSequenceMethodsTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/GroovyCharSequenceMethodsTest.groovy b/src/test/groovy/GroovyCharSequenceMethodsTest.groovy
index 0e40f2c..3554e8d 100644
--- a/src/test/groovy/GroovyCharSequenceMethodsTest.groovy
+++ b/src/test/groovy/GroovyCharSequenceMethodsTest.groovy
@@ -20,33 +20,28 @@ package groovy
 
 /**
  * Tests for DGM methods on CharSequence.
- *
- * @author Paul King
  */
 class GroovyCharSequenceMethodsTest extends GroovyTestCase {
 
-    def s1 = 'Today is Thu Jul 28 06:38:07 EST 2011'
-    def cs1 = [
-            toString:{ -> s1 },
-            subSequence:{ int f, int t -> s1.substring(f, t) },
-            length:{ -> s1.length() },
-            charAt:{ int i -> s1.chars[i] },
-    ] as CharSequence
-    def s2 = 'Foobar'
-    def cs2 = [
-            toString:{ -> s2 },
-            subSequence:{ int f, int t -> s2.substring(f, t) },
-            length:{ -> s2.length() },
-            charAt:{ int i -> s2.chars[i] },
-    ] as CharSequence
-    def cs3 = [
-            toString: { -> '''\
+    private static CharSequence makeCharSequence(String s) {
+        [
+                toString   : { -> s },
+                subSequence: { int f, int t -> s.substring(f, t) },
+                length     : { -> s.length() },
+                charAt     : { int i -> s.chars[i] },
+        ] as CharSequence
+    }
+
+    def cs1 = makeCharSequence('Today is Thu Jul 28 06:38:07 EST 2011')
+
+    def cs2 = makeCharSequence('Foobar')
+
+    def cs3 = makeCharSequence('''\
                 |Foo
                 |bar
-                |'''
-            }
-    ] as CharSequence
-    def csEmpty = [toString:{->''}, length:{->0}] as CharSequence
+                |''')
+
+    def csEmpty = makeCharSequence('')
 
     void testIsCase() {
         // direct
@@ -95,7 +90,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
 
     void testIsAllWhitespace() {
         assert !cs2.isAllWhitespace()
-        assert ([toString:{->' \t\n\r'}] as CharSequence).isAllWhitespace()
+        assert makeCharSequence(' \t\n\r').isAllWhitespace()
     }
 
     void testReplace() {
@@ -109,7 +104,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testFind() {
-        def csDigits = [toString:{->/\d{4}/}] as CharSequence
+        def csDigits = makeCharSequence(/\d{4}/)
         assert cs1.find(csDigits) == '2011'
         assert cs1.find(csDigits, {"--$it--"}) == '--2011--'
         assert cs1.find(~/\d\d:\d\d/) == '06:38'
@@ -117,7 +112,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testFindAll() {
-        def csDigits = [toString:{->/\d\d/}] as CharSequence
+        def csDigits = makeCharSequence(/\d\d/)
         assert cs1.findAll(csDigits) == ['28', '06', '38', '07', '20', '11']
         assert cs1.findAll(csDigits, {"<$it>"}) == ['<28>', '<06>', '<38>', '<07>', '<20>', '<11>']
         assert cs1.findAll(~/\s\d\d/) == [' 28', ' 06', ' 20']
@@ -165,30 +160,30 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testCapitalize() {
-        def csfoo = [toString:{->'foo'}] as CharSequence
+        def csfoo = makeCharSequence('foo')
         assert csfoo.capitalize() == 'Foo'
         assert cs2.capitalize() == 'Foobar'
     }
 
     void testUncapitalize() {
-        def csfoo = [toString:{->'Foo'}] as CharSequence
+        def csfoo = makeCharSequence('Foo')
         assert csfoo.uncapitalize() == 'foo'
         assert cs2.uncapitalize() == 'foobar'
     }
 
     void testExpand() {
-        def csfoobar = [toString:{->'foo\tbar'}] as CharSequence
+        def csfoobar = makeCharSequence('foo\tbar')
         assert csfoobar.expand() == 'foo     bar'
         assert csfoobar.expand(4) == 'foo bar'
-        csfoobar = [toString:{->'\tfoo\n\tbar'}] as CharSequence
+        csfoobar = makeCharSequence('\tfoo\n\tbar')
         assert csfoobar.expand(4) == '    foo\n    bar'
     }
 
     void testUnexpand() {
-        def csfoobar = [toString:{->'foo     bar'}] as CharSequence
+        def csfoobar = makeCharSequence('foo     bar')
         assert csfoobar.unexpand() == 'foo\tbar'
         assert csfoobar.unexpand(4) == 'foo\t\tbar'
-        csfoobar = [toString:{->'     foo\n    bar'}] as CharSequence
+        csfoobar = makeCharSequence('     foo\n    bar')
         assert csfoobar.unexpand(4) == '\t foo\n\tbar'
     }
 
@@ -198,22 +193,22 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testMinus() {
-        def csoo = [toString:{->'oo'}] as CharSequence
+        def csoo = makeCharSequence('oo')
         assert cs2.minus(42) == 'Foobar'
         assert cs2.minus(csoo) == 'Fbar'
         assert cs2 - csoo == 'Fbar'
     }
 
     void testContains() {
-        def csoo = [toString:{->'oo'}] as CharSequence
-        def csbaz = [toString:{->'baz'}] as CharSequence
+        def csoo = makeCharSequence('oo')
+        def csbaz = makeCharSequence('baz')
         assert cs2.contains(csoo)
         assert !cs2.contains(csbaz)
     }
 
     void testCount() {
-        def cszero = [toString:{->'0'}] as CharSequence
-        def csbar = [toString:{->'|'}] as CharSequence
+        def cszero = makeCharSequence('0')
+        def csbar = makeCharSequence('|')
         assert cs1.count(cszero) == 3
         assert cs3.count(csbar) == 3
     }
@@ -234,7 +229,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testToInteger() {
-        def csFourteen = [toString:{->'014'}] as CharSequence
+        def csFourteen = makeCharSequence('014')
         assert csFourteen.isInteger()
         def fourteen = csFourteen.toInteger()
         assert fourteen instanceof Integer
@@ -242,7 +237,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testToLong() {
-        def csFourteen = [toString:{->'014'}] as CharSequence
+        def csFourteen = makeCharSequence('014')
         assert csFourteen.isLong()
         def fourteen = csFourteen.toLong()
         assert fourteen instanceof Long
@@ -250,14 +245,14 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testToShort() {
-        def csFourteen = [toString:{->'014'}] as CharSequence
+        def csFourteen = makeCharSequence('014')
         def fourteen = csFourteen.toShort()
         assert fourteen instanceof Short
         assert fourteen == 14
     }
 
     void testToBigInteger() {
-        def csFourteen = [toString:{->'014'}] as CharSequence
+        def csFourteen = makeCharSequence('014')
         assert csFourteen.isBigInteger()
         def fourteen = csFourteen.toBigInteger()
         assert fourteen instanceof BigInteger
@@ -265,7 +260,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testToFloat() {
-        def csThreePointFive = [toString:{->'3.5'}] as CharSequence
+        def csThreePointFive = makeCharSequence('3.5')
         assert csThreePointFive.isFloat()
         def threePointFive = csThreePointFive.toFloat()
         assert threePointFive instanceof Float
@@ -273,7 +268,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testToDouble() {
-        def csThreePointFive = [toString:{->'3.5'}] as CharSequence
+        def csThreePointFive = makeCharSequence('3.5')
         assert csThreePointFive.isDouble()
         def threePointFive = csThreePointFive.toDouble()
         assert threePointFive instanceof Double
@@ -281,7 +276,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testToBigDecimal() {
-        def csThreePointFive = [toString:{->'3.5'}] as CharSequence
+        def csThreePointFive = makeCharSequence('3.5')
         assert csThreePointFive.isBigDecimal()
         assert csThreePointFive.isNumber()
         def threePointFive = csThreePointFive.toBigDecimal()
@@ -300,8 +295,8 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
 
     void testSplitEachLine() {
         def regexOp = /\s*\*\s*/
-        def csOp = [toString:{->regexOp}] as CharSequence
-        def csTwoLines = [toString:{->'10*15\n11 * 9'}] as CharSequence
+        def csOp = makeCharSequence(regexOp)
+        def csTwoLines = makeCharSequence('10*15\n11 * 9')
         def result = []
         csTwoLines.splitEachLine(csOp){ left, right -> result << left.toInteger() * right.toInteger() }
         assert result == [150, 99]
@@ -342,7 +337,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
 
     private enum Coin { penny, nickel, dime, quarter }
     void testAsType() {
-        def csDime = [toString:{->'dime'}] as CharSequence
+        def csDime = makeCharSequence('dime')
         def dime = csDime as Coin
         assert dime instanceof Coin
         assert dime == Coin.dime
@@ -351,7 +346,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     void testEachMatch() {
         def result = []
         def regexDigits = /(\d)(.)(\d)/
-        def csDigits = [toString:{->regexDigits}] as CharSequence
+        def csDigits = makeCharSequence(regexDigits)
         assert cs1.eachMatch(csDigits) { all, first, delim, second -> result << "$first $delim $second" }
         assert result == ['8   0', '6 : 3', '8 : 0', '2 0 1']
         result = []
@@ -364,8 +359,8 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
     }
 
     void testReplaceAllFirst() {
-        def csDigit = [toString:{->/\d/}] as CharSequence
-        def csUnder = [toString:{->/_/}] as CharSequence
+        def csDigit = makeCharSequence(/\d/)
+        def csUnder = makeCharSequence(/_/)
 
         assert cs1.replaceAll(~/\d/, csUnder) == 'Today is Thu Jul __ __:__:__ EST ____'
         assert cs1.replaceAll(csDigit, csUnder) == 'Today is Thu Jul __ __:__:__ EST ____'
@@ -377,7 +372,7 @@ class GroovyCharSequenceMethodsTest extends GroovyTestCase {
 
     void testNormalizeDenormalize() {
         def text = 'the quick brown\nfox jumped\r\nover the lazy dog'
-        def csText = [toString : { -> text }] as CharSequence
+        def csText = makeCharSequence(text)
         assert csText.normalize() == text.normalize()
         assert csText.normalize().denormalize() == text.normalize().denormalize()
     }


[3/3] groovy git commit: GROOVY-6950: StringGroovyMethods minor performance improvements (minor refactoring - closes #341)

Posted by pa...@apache.org.
GROOVY-6950: StringGroovyMethods minor performance improvements (minor refactoring - closes #341)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/432a8e5e
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/432a8e5e
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/432a8e5e

Branch: refs/heads/master
Commit: 432a8e5ed18960812887273187fd07debed20614
Parents: 35b5f34
Author: paulk <pa...@asert.com.au>
Authored: Tue May 31 11:53:17 2016 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Tue May 31 11:58:07 2016 +1000

----------------------------------------------------------------------
 .../groovy/runtime/StringGroovyMethods.java     | 66 +++++++++-----------
 .../groovy/util/CharSequenceReader.java         | 12 ++--
 2 files changed, 34 insertions(+), 44 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/432a8e5e/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java b/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
index 665f606..2c8f794 100644
--- a/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
+++ b/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
@@ -34,14 +34,12 @@ import org.codehaus.groovy.util.CharSequenceReader;
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
-import java.io.StringReader;
 import java.io.StringWriter;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -49,7 +47,6 @@ import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.StringTokenizer;
-import java.util.TreeSet;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -166,25 +163,25 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
     @SuppressWarnings("unchecked")
     public static <T> T asType(String self, Class<T> c) {
         if (c == List.class) {
-            return (T) toList(self);
+            return (T) toList((CharSequence)self);
         } else if (c == BigDecimal.class) {
-            return (T) toBigDecimal(self);
+            return (T) toBigDecimal((CharSequence)self);
         } else if (c == BigInteger.class) {
-            return (T) toBigInteger(self);
+            return (T) toBigInteger((CharSequence)self);
         } else if (c == Long.class || c == Long.TYPE) {
-            return (T) toLong(self);
+            return (T) toLong((CharSequence)self);
         } else if (c == Integer.class || c == Integer.TYPE) {
-            return (T) toInteger(self);
+            return (T) toInteger((CharSequence)self);
         } else if (c == Short.class || c == Short.TYPE) {
-            return (T) toShort(self);
+            return (T) toShort((CharSequence)self);
         } else if (c == Byte.class || c == Byte.TYPE) {
             return (T) Byte.valueOf(self.trim());
         } else if (c == Character.class || c == Character.TYPE) {
             return (T) toCharacter(self);
         } else if (c == Double.class || c == Double.TYPE) {
-            return (T) toDouble(self);
+            return (T) toDouble((CharSequence)self);
         } else if (c == Float.class || c == Float.TYPE) {
-            return (T) toFloat(self);
+            return (T) toFloat((CharSequence)self);
         } else if (c == File.class) {
             return (T) new File(self);
         } else if (c.isEnum()) {
@@ -910,7 +907,7 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String find(CharSequence self, CharSequence regex, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") Closure closure) {
-        return find(self.toString(), Pattern.compile(regex.toString()), closure);
+        return find(self, Pattern.compile(regex.toString()), closure);
     }
 
     /**
@@ -1506,7 +1503,7 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
         RangeInfo info = subListBorders(text.length(), range);
         String answer = text.substring(info.from, info.to);
         if (info.reverse) {
-            answer = reverse(answer);
+            answer = reverse((CharSequence)answer);
         }
         return answer;
     }
@@ -2463,11 +2460,10 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      *
      * @param self a CharSequence object
      * @return a list of lines
-     * @throws java.io.IOException if an error occurs
      * @since 1.8.2
      */
-    public static List<String> readLines(CharSequence self) throws IOException {
-        return IOGroovyMethods.readLines(new StringReader(self.toString()));
+    public static List<String> readLines(CharSequence self) {
+        return DefaultGroovyMethods.toList(new LineIterable(self));
     }
 
     /**
@@ -2475,7 +2471,7 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @see #readLines(CharSequence)
      */
     @Deprecated
-    public static List<String> readLines(String self) throws IOException {
+    public static List<String> readLines(String self) {
         return readLines((CharSequence) self);
     }
 
@@ -2496,9 +2492,9 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
     }
 
     /**
-     * Replaces all occurrences of a captured group by the result of a closure on that text.
+     * Replaces all occurrences of a captured group by the result of calling a closure on that text.
      * <p>
-     * For examples,
+     * Examples:
      * <pre class="groovyTestCase">
      *     assert "hello world".replaceAll("(o)") { it[0].toUpperCase() } == "hellO wOrld"
      *
@@ -2771,7 +2767,8 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
     private static class ReplaceState {
         public ReplaceState(Map<CharSequence, CharSequence> replacements) {
             this.noMoreMatches = new boolean[replacements.size()];
-            this.replacementsList = DefaultGroovyMethods.toList(replacements.entrySet());
+            this.replacementsList = DefaultGroovyMethods.toList((Iterable<Map.Entry<CharSequence,CharSequence>>)
+                    replacements.entrySet());
         }
 
         int textIndex = -1;
@@ -3058,13 +3055,11 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @param pattern the regular expression Pattern for the delimiter
      * @param closure a closure
      * @return the last value returned by the closure
-     * @throws java.io.IOException if an error occurs
      * @since 1.8.2
      */
-    public static <T> T splitEachLine(CharSequence self, Pattern pattern, @ClosureParams(value=FromString.class,options={"List<String>","String[]"},conflictResolutionStrategy=PickFirstResolver.class) Closure<T> closure) throws IOException {
-        final List<String> list = readLines(self);
+    public static <T> T splitEachLine(CharSequence self, Pattern pattern, @ClosureParams(value=FromString.class,options={"List<String>","String[]"},conflictResolutionStrategy=PickFirstResolver.class) Closure<T> closure) {
         T result = null;
-        for (String line : list) {
+        for (String line : new LineIterable(self)) {
             List vals = Arrays.asList(pattern.split(line));
             result = closure.call(vals);
         }
@@ -3716,21 +3711,16 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      */
     public static String unexpand(CharSequence self, int tabStop) {
         if (self.length() == 0) return self.toString();
-        try {
-            StringBuilder builder = new StringBuilder();
-            for (String line : readLines(self)) {
-                builder.append(unexpandLine((CharSequence)line, tabStop));
-                builder.append("\n");
-            }
-            // remove the normalized ending line ending if it was not present
-            if (self.charAt(self.length() - 1) != '\n') {
-                builder.deleteCharAt(builder.length() - 1);
-            }
-            return builder.toString();
-        } catch (IOException e) {
-            /* ignore */
+        StringBuilder builder = new StringBuilder();
+        for (String line : new LineIterable(self)) {
+            builder.append(unexpandLine((CharSequence)line, tabStop));
+            builder.append("\n");
         }
-        return self.toString();
+        // remove the normalized ending line ending if it was not present
+        if (self.charAt(self.length() - 1) != '\n') {
+            builder.deleteCharAt(builder.length() - 1);
+        }
+        return builder.toString();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/groovy/blob/432a8e5e/src/main/org/codehaus/groovy/util/CharSequenceReader.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/util/CharSequenceReader.java b/src/main/org/codehaus/groovy/util/CharSequenceReader.java
index 2154002..cc5f210 100644
--- a/src/main/org/codehaus/groovy/util/CharSequenceReader.java
+++ b/src/main/org/codehaus/groovy/util/CharSequenceReader.java
@@ -27,14 +27,14 @@ import java.io.Serializable;
  * StringBuilder, CharBuffer or GString.
  * <p>
  * <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}.
+ * <p>
+ * <strong>Note:</strong> This class is mostly a copy from Commons IO and
+ * is intended for internal Groovy usage only. It may be deprecated and
+ * removed from Groovy at a faster pace than other classes. If you need this
+ * functionality in your Groovy programs, we recommend using the Commons IO
+ * equivalent directly.
  */
 public class CharSequenceReader extends Reader implements Serializable {
-    /*
-      NOTE: nearly 100% borrowed from Commons-IO but we don't want to bring
-      in that whole package just yet. We need to consider reworking all of our
-      IO in light of Java 8 streams and decide whether it makes sense to bring
-      in an external package.
-     */
     private static final long serialVersionUID = -6661279371843310693L;
     private final CharSequence charSequence;
     private int idx;


[2/3] groovy git commit: GROOVY-6950: StringGroovyMethods minor performance improvements (make use of line based iterator)

Posted by pa...@apache.org.
GROOVY-6950: StringGroovyMethods minor performance improvements (make use of line based iterator)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/35b5f345
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/35b5f345
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/35b5f345

Branch: refs/heads/master
Commit: 35b5f3450788601e5bad8a18b5a4758663fedebb
Parents: 24043a5
Author: paulk <pa...@asert.com.au>
Authored: Mon May 30 21:11:46 2016 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Mon May 30 21:11:46 2016 +1000

----------------------------------------------------------------------
 .../groovy/runtime/StringGroovyMethods.java     | 146 ++++++----------
 .../groovy/util/CharSequenceReader.java         | 170 +++++++++++++++++++
 2 files changed, 226 insertions(+), 90 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/35b5f345/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java b/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
index 058b270..665f606 100644
--- a/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
+++ b/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
@@ -29,6 +29,7 @@ import groovy.transform.stc.FromString;
 import groovy.transform.stc.PickFirstResolver;
 import groovy.transform.stc.SimpleType;
 import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
+import org.codehaus.groovy.util.CharSequenceReader;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -40,6 +41,7 @@ import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -47,6 +49,7 @@ import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.TreeSet;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -69,36 +72,6 @@ import static org.codehaus.groovy.runtime.DefaultGroovyMethods.join;
  * at the Java method call level. I.e. future versions of Groovy may
  * remove or move a method call in this file but would normally
  * aim to keep the method available from within Groovy.
- *
- * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
- * @author Jeremy Rayner
- * @author Sam Pullara
- * @author Rod Cope
- * @author Guillaume Laforge
- * @author John Wilson
- * @author Hein Meling
- * @author Dierk Koenig
- * @author Pilho Kim
- * @author Marc Guillemot
- * @author Russel Winder
- * @author bing ran
- * @author Jochen Theodorou
- * @author Paul King
- * @author Michael Baehr
- * @author Joachim Baumann
- * @author Alex Tkachman
- * @author Ted Naleid
- * @author Brad Long
- * @author Jim Jagielski
- * @author Rodolfo Velasco
- * @author jeremi Joslin
- * @author Hamlet D'Arcy
- * @author Cedric Champeau
- * @author Tim Yates
- * @author Dinko Srkoc
- * @author Pascal Lombard
- * @author Christophe Charles
- * @author Andres Almiray
  */
 public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
 
@@ -339,15 +312,14 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String center(CharSequence self, Number numberOfChars, CharSequence padding) {
-        String padding1 = padding.toString();
         int numChars = numberOfChars.intValue();
         if (numChars <= self.length()) {
             return self.toString();
         } else {
             int charsToAdd = numChars - self.length();
             String semiPad = charsToAdd % 2 == 1 ?
-                    getPadding(padding1, charsToAdd / 2 + 1) :
-                    getPadding(padding1, charsToAdd / 2);
+                    getPadding(padding, charsToAdd / 2 + 1) :
+                    getPadding(padding, charsToAdd / 2);
             if (charsToAdd % 2 == 0)
                 return semiPad + self + semiPad;
             else
@@ -628,6 +600,19 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
         }
     }
 
+    private static final class LineIterable implements Iterable<String> {
+        private final CharSequence delegate;
+
+        public LineIterable(CharSequence cs) {
+            this.delegate = cs;
+        }
+
+        @Override
+        public Iterator<String> iterator() {
+            return IOGroovyMethods.iterator(new CharSequenceReader(delegate));
+        }
+    }
+
     /**
      * Iterates through this CharSequence line by line.  Each line is passed
      * to the given 1 or 2 arg closure. If a 2 arg closure is found
@@ -660,7 +645,7 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
     public static <T> T eachLine(CharSequence self, int firstLine, @ClosureParams(value=FromString.class, options={"String","String,Integer"}) Closure<T> closure) throws IOException {
         int count = firstLine;
         T result = null;
-        for (String line : readLines((CharSequence)self.toString())) {
+        for (String line : new LineIterable(self)) {
             result = callClosureForLine(closure, line, count);
             count++;
         }
@@ -822,22 +807,17 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      * @since 1.8.2
      */
     public static String expand(CharSequence self, int tabStop) {
-        if (self.length() == 0) return self.toString();
-        try {
-            StringBuilder builder = new StringBuilder();
-            for (String line : readLines(self)) {
-                builder.append(expandLine((CharSequence)line, tabStop));
-                builder.append("\n");
-            }
-            // remove the normalized ending line ending if it was not present
-            if (self.charAt(self.length() - 1) != '\n') {
-                builder.deleteCharAt(builder.length() - 1);
-            }
-            return builder.toString();
-        } catch (IOException e) {
-            /* ignore */
+        if (self.length() == 0) return "";
+        StringBuilder builder = new StringBuilder();
+        for (String line : new LineIterable(self)) {
+            builder.append(expandLine((CharSequence)line, tabStop));
+            builder.append("\n");
         }
-        return self.toString();
+        // remove the normalized ending line ending if it was not present
+        if (self.charAt(self.length() - 1) != '\n') {
+            builder.deleteCharAt(builder.length() - 1);
+        }
+        return builder.toString();
     }
 
     /**
@@ -3126,16 +3106,12 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
     public static String stripIndent(CharSequence self) {
         if (self.length() == 0) return self.toString();
         int runningCount = -1;
-        try {
-            for (String line : readLines(self)) {
-                // don't take blank lines into account for calculating the indent
-                if (isAllWhitespace((CharSequence) line)) continue;
-                if (runningCount == -1) runningCount = line.length();
-                runningCount = findMinimumLeadingSpaces(line, runningCount);
-                if (runningCount == 0) break;
-            }
-        } catch (IOException e) {
-            /* ignore */
+        for (String line : new LineIterable(self)) {
+            // don't take blank lines into account for calculating the indent
+            if (isAllWhitespace((CharSequence) line)) continue;
+            if (runningCount == -1) runningCount = line.length();
+            runningCount = findMinimumLeadingSpaces(line, runningCount);
+            if (runningCount == 0) break;
         }
         return stripIndent(self, runningCount == -1 ? 0 : runningCount);
     }
@@ -3154,25 +3130,20 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      */
     public static String stripIndent(CharSequence self, int numChars) {
         if (self.length() == 0 || numChars <= 0) return self.toString();
-        try {
-            StringBuilder builder = new StringBuilder();
-            for (String line : readLines(self)) {
-                // normalize an empty or whitespace line to \n
-                // or strip the indent for lines containing non-space characters
-                if (!isAllWhitespace((CharSequence) line)) {
-                    builder.append(stripIndentFromLine(line, numChars));
-                }
-                builder.append("\n");
+        StringBuilder builder = new StringBuilder();
+        for (String line : new LineIterable(self)) {
+            // normalize an empty or whitespace line to \n
+            // or strip the indent for lines containing non-space characters
+            if (!isAllWhitespace((CharSequence) line)) {
+                builder.append(stripIndentFromLine(line, numChars));
             }
-            // remove the normalized ending line ending if it was not present
-            if (self.charAt(self.length() - 1) != '\n') {
-                builder.deleteCharAt(builder.length() - 1);
-            }
-            return builder.toString();
-        } catch (IOException e) {
-            /* ignore */
+            builder.append("\n");
         }
-        return self.toString();
+        // remove the normalized ending line ending if it was not present
+        if (self.charAt(self.length() - 1) != '\n') {
+            builder.deleteCharAt(builder.length() - 1);
+        }
+        return builder.toString();
     }
 
     /**
@@ -3234,21 +3205,16 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
      */
     public static String stripMargin(CharSequence self, char marginChar) {
         if (self.length() == 0) return self.toString();
-        try {
-            StringBuilder builder = new StringBuilder();
-            for (String line : readLines(self)) {
-                builder.append(stripMarginFromLine(line, marginChar));
-                builder.append("\n");
-            }
-            // remove the normalized ending line ending if it was not present
-            if (self.charAt(self.length() - 1) != '\n') {
-                builder.deleteCharAt(builder.length() - 1);
-            }
-            return builder.toString();
-        } catch (IOException e) {
-            /* ignore */
+        StringBuilder builder = new StringBuilder();
+        for (String line : new LineIterable(self)) {
+            builder.append(stripMarginFromLine(line, marginChar));
+            builder.append("\n");
         }
-        return self.toString();
+        // remove the normalized ending line ending if it was not present
+        if (self.charAt(self.length() - 1) != '\n') {
+            builder.deleteCharAt(builder.length() - 1);
+        }
+        return builder.toString();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/groovy/blob/35b5f345/src/main/org/codehaus/groovy/util/CharSequenceReader.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/util/CharSequenceReader.java b/src/main/org/codehaus/groovy/util/CharSequenceReader.java
new file mode 100644
index 0000000..2154002
--- /dev/null
+++ b/src/main/org/codehaus/groovy/util/CharSequenceReader.java
@@ -0,0 +1,170 @@
+/*
+ * 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 org.codehaus.groovy.util;
+
+import java.io.Reader;
+import java.io.Serializable;
+
+/**
+ * {@link Reader} implementation that can read from String, StringBuffer,
+ * StringBuilder, CharBuffer or GString.
+ * <p>
+ * <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}.
+ */
+public class CharSequenceReader extends Reader implements Serializable {
+    /*
+      NOTE: nearly 100% borrowed from Commons-IO but we don't want to bring
+      in that whole package just yet. We need to consider reworking all of our
+      IO in light of Java 8 streams and decide whether it makes sense to bring
+      in an external package.
+     */
+    private static final long serialVersionUID = -6661279371843310693L;
+    private final CharSequence charSequence;
+    private int idx;
+    private int mark;
+    private static final int EOF = -1;
+
+    /**
+     * Construct a new instance with the specified character sequence.
+     *
+     * @param charSequence The character sequence, may be {@code null}
+     */
+    public CharSequenceReader(final CharSequence charSequence) {
+        this.charSequence = charSequence != null ? charSequence : "";
+    }
+
+    /**
+     * Close resets the reader back to the start and removes any marked position.
+     */
+    @Override
+    public void close() {
+        idx = 0;
+        mark = 0;
+    }
+
+    /**
+     * Mark the current position.
+     *
+     * @param readAheadLimit ignored
+     */
+    @Override
+    public void mark(final int readAheadLimit) {
+        mark = idx;
+    }
+
+    /**
+     * Mark is supported (returns true).
+     *
+     * @return {@code true}
+     */
+    @Override
+    public boolean markSupported() {
+        return true;
+    }
+
+    /**
+     * Read a single character.
+     *
+     * @return the next character from the character sequence
+     * or -1 if the end has been reached.
+     */
+    @Override
+    public int read() {
+        if (idx >= charSequence.length()) {
+            return EOF;
+        } else {
+            return charSequence.charAt(idx++);
+        }
+    }
+
+    /**
+     * Read the sepcified number of characters into the array.
+     *
+     * @param array The array to store the characters in
+     * @param offset The starting position in the array to store
+     * @param length The maximum number of characters to read
+     * @return The number of characters read or -1 if there are
+     * no more
+     */
+    @Override
+    public int read(final char[] array, final int offset, final int length) {
+        if (idx >= charSequence.length()) {
+            return EOF;
+        }
+        if (array == null) {
+            throw new NullPointerException("Character array is missing");
+        }
+        if (length < 0 || offset < 0 || offset + length > array.length) {
+            throw new IndexOutOfBoundsException("Array Size=" + array.length +
+                    ", offset=" + offset + ", length=" + length);
+        }
+        int count = 0;
+        for (int i = 0; i < length; i++) {
+            final int c = read();
+            if (c == EOF) {
+                return count;
+            }
+            array[offset + i] = (char)c;
+            count++;
+        }
+        return count;
+    }
+
+    /**
+     * Reset the reader to the last marked position (or the beginning if
+     * mark has not been called).
+     */
+    @Override
+    public void reset() {
+        idx = mark;
+    }
+
+    /**
+     * Skip the specified number of characters.
+     *
+     * @param n The number of characters to skip
+     * @return The actual number of characters skipped
+     */
+    @Override
+    public long skip(final long n) {
+        if (n < 0) {
+            throw new IllegalArgumentException(
+                    "Number of characters to skip is less than zero: " + n);
+        }
+        if (idx >= charSequence.length()) {
+            return EOF;
+        }
+        final int dest = (int)Math.min(charSequence.length(), idx + n);
+        final int count = dest - idx;
+        idx = dest;
+        return count;
+    }
+
+    /**
+     * Return a String representation of the underlying
+     * character sequence.
+     *
+     * @return The contents of the character sequence
+     */
+    @Override
+    public String toString() {
+        return charSequence.toString();
+    }
+}
\ No newline at end of file