groovy-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From pa...@apache.org
Subject incubator-groovy git commit: GROOVY-7540: Add a method to StringGroovyMethods for replacing String pairs supplied as a Map
Date Mon, 10 Aug 2015 23:39:09 GMT
Repository: incubator-groovy
Updated Branches:
  refs/heads/master 1000bc5ba -> f6ade7132


GROOVY-7540: Add a method to StringGroovyMethods for replacing String pairs supplied as a
Map


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

Branch: refs/heads/master
Commit: f6ade7132789c6151dda36278b5435c5c71fff73
Parents: 1000bc5
Author: Paul King <paulk@asert.com.au>
Authored: Fri Aug 7 20:06:12 2015 +1000
Committer: paulk <paulk@asert.com.au>
Committed: Sat Aug 8 17:14:51 2015 +1000

----------------------------------------------------------------------
 .../groovy/runtime/StringGroovyMethods.java     | 142 ++++++++++++++++++-
 1 file changed, 140 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/f6ade713/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 c23ebb0..51943c6 100644
--- a/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
+++ b/src/main/org/codehaus/groovy/runtime/StringGroovyMethods.java
@@ -43,6 +43,7 @@ import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.StringTokenizer;
@@ -2325,7 +2326,7 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport
{
      */
     @Deprecated
     public static String padLeft(String self, Number numberOfChars, String padding) {
-        return padLeft((CharSequence) self, numberOfChars,(CharSequence) padding);
+        return padLeft((CharSequence) self, numberOfChars, (CharSequence) padding);
     }
 
     /**
@@ -2776,7 +2777,7 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport
{
      * Replaces the first occurrence of a captured group by the result of a closure call
on that text.
      * <p>
      * For example (with some replaceAll variants thrown in for comparison purposes),
-     * <pre>
+     * <pre class="groovyTestCase">
      * assert "hellO world" == "hello world".replaceFirst(~"(o)") { it[0].toUpperCase() }
// first match
      * assert "hellO wOrld" == "hello world".replaceAll(~"(o)") { it[0].toUpperCase() } 
 // all matches
      *
@@ -2832,6 +2833,143 @@ public class StringGroovyMethods extends DefaultGroovyMethodsSupport
{
     }
 
     /**
+     * Helper class used by {@link #replace(CharSequence, Map)}
+     */
+    private static class ReplaceState {
+        public ReplaceState(Map<CharSequence, CharSequence> replacements) {
+            this.noMoreMatches = new boolean[replacements.size()];
+            this.replacementsList = DefaultGroovyMethods.toList(replacements.entrySet());
+        }
+
+        int textIndex = -1;
+        int tempIndex = -1;
+        int replaceIndex = -1;
+        int start = -1;
+        boolean[] noMoreMatches;
+        private List<Map.Entry<CharSequence, CharSequence>> replacementsList;
+
+        CharSequence key(int i) {
+            return replacementsList.get(i).getKey();
+        }
+
+        CharSequence value(int i) {
+            return replacementsList.get(i).getValue();
+        }
+
+        int numReplacements() {
+            return replacementsList.size();
+        }
+    }
+
+    /**
+     * Replaces all occurrences of replacement CharSequences (supplied via a map) within
a provided CharSequence.
+     *
+     * <pre class="groovyTestCase">
+     * assert 'foobar'.replace(f:'b', foo:'bar') == 'boobar'
+     * assert 'foobar'.replace(foo:'bar', f:'b') == 'barbar'
+     * def replacements = [foo:'bar', f:'b', b: 'f', bar:'boo']
+     * assert 'foobar'.replace(replacements) == 'barfar'
+     * </pre>
+     *
+     * @param self a CharSequence
+     * @param replacements a map of before (key) and after (value) pairs processed in the
natural order of the map
+     * @return a String formed from the provided CharSequence after performing all of the
replacements
+     */
+    public static String replace(final CharSequence self, final Map<CharSequence, CharSequence>
replacements) {
+        return replace(self, -1, replacements);
+    }
+
+    /**
+     * Replaces all occurrences of replacement CharSequences (supplied via a map) within
a provided CharSequence
+     * with control over the internally created StringBuilder's capacity. This method uses
a StringBuilder internally.
+     * Java auto-expands a StringBuilder's capacity if needed. In rare circumstances, the
overhead involved with
+     * repeatedly expanding the StringBuilder may become significant. If you have measured
the performance of your
+     * application and found this to be a significant bottleneck, use this variant to have
complete control over
+     * the internally created StringBuilder's capacity.
+     *
+     * <pre class="groovyTestCase">
+     * assert 'foobar'.replace(9, [r:'rbaz']) == 'foobarbaz'
+     * assert 'foobar'.replace(1, [fooba:'']) == 'r'
+     * </pre>
+     *
+     * @param self a CharSequence
+     * @param capacity an optimization parameter, set to size after replacements or a little
larger to avoid resizing overheads
+     * @param replacements a map of before (key) and after (value) pairs processed in the
natural order of the map
+     * @return a String formed from the provided CharSequence after performing all of the
replacements
+     */
+    public static String replace(final CharSequence self, final int capacity, final Map<CharSequence,
CharSequence>
+            replacements) {
+        // modelled very closely on the commons lang StringUtils replaceEach method
+        if (self == null) return null;
+        String text = self.toString();
+        if (replacements == null || replacements.isEmpty() || text.isEmpty()) return text;
+        ReplaceState state = new ReplaceState(replacements);
+        nextMatch(text, state);
+        if (state.textIndex == -1) {
+            return text;
+        }
+        StringBuilder buf = new StringBuilder(guessCapacity(capacity, replacements));
+
+        state.start = 0;
+        while (state.textIndex != -1) {
+            for (int i = state.start; i < state.textIndex; i++) {
+                buf.append(text.charAt(i));
+            }
+            buf.append(state.value(state.replaceIndex));
+            state.start = state.textIndex + state.key(state.replaceIndex).length();
+            state.textIndex = -1;
+            state.replaceIndex = -1;
+            nextMatch(text, state);
+        }
+        int textLength = text.length();
+        for (int i = state.start; i < textLength; i++) {
+            buf.append(text.charAt(i));
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Crude heuristic for setting the created StringBuilder capacity if not supplied:
+     * If at least one replacement text is bigger than the original text use a
+     * capacity 50% larger than the original; otherwise, use the original size.
+     */
+    private static int guessCapacity(int capacity, Map<CharSequence, CharSequence>
replacements) {
+        if (capacity >= 0) {
+            return capacity;
+        }
+        boolean possiblyBigger = false;
+        for (Map.Entry<CharSequence, CharSequence> entry : replacements.entrySet())
{
+            if (entry.getValue().length() > entry.getKey().length()) {
+                possiblyBigger = true;
+                break;
+            }
+        }
+        return possiblyBigger ? replacements.size() * 3 / 2 : replacements.size();
+    }
+
+    /**
+     * Helper method to find the next match for the replace method.
+     */
+    private static void nextMatch(String text, ReplaceState state) {
+        for (int i = 0; i < state.numReplacements(); i++) {
+            if (state.noMoreMatches[i] || state.key(i) == null ||
+                    state.key(i).length() == 0 || state.value(i) == null) {
+                continue;
+            }
+            state.tempIndex = text.indexOf(state.key(i).toString(), state.start);
+
+            if (state.tempIndex == -1) {
+                state.noMoreMatches[i] = true;
+            } else {
+                if (state.textIndex == -1 || state.tempIndex < state.textIndex) {
+                    state.textIndex = state.tempIndex;
+                    state.replaceIndex = i;
+                }
+            }
+        }
+    }
+
+    /**
      * Creates a String which is the reverse (backwards) of this CharSequence
      *
      * @param self a CharSequence


Mime
View raw message