Author: bayard
Date: Mon Jan 28 21:33:17 2008
New Revision: 616170
URL: http://svn.apache.org/viewvc?rev=616170&view=rev
Log:
Applying my modified version of Chris Hyzer's patch from LANG-180 - adding a
replaceEach(String, String[], String[]) and replaceRepeatedly(String, String[],
String[]) pair of methods. The internal code to the private replaceEach method
has not been fully reviewed yet - I wanted to get more eyes focused on the
algorithm
Modified:
commons/proper/lang/trunk/src/java/org/apache/commons/lang/StringUtils.java
commons/proper/lang/trunk/src/test/org/apache/commons/lang/StringUtilsTest.java
Modified:
commons/proper/lang/trunk/src/java/org/apache/commons/lang/StringUtils.java
URL:
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/StringUtils.java?rev=616170&r1=616169&r2=616170&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/StringUtils.java
(original)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/StringUtils.java
Mon Jan 28 21:33:17 2008
@@ -3523,6 +3523,269 @@
return buf.toString();
}
+ /**
+ * <p>
+ * Replaces all occurances of Strings within another String.
+ * </p>
+ *
+ * <p>
+ * A <code>null</code> reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored. This will not repeat, for repeating replaces, call the
+ * overloaded method.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.replaceEach(null, *, *) = null
+ * StringUtils.replaceEach("", *, *) = ""
+ * StringUtils.replaceEach("aba", null, null) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0]) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}) =
"b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})
= "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"w", "t"}) = "wcte"
+ * (example of how it does not repeat)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"d", "t"}) = "dcte"
+ * </pre>
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param repl
+ * the Strings to search for, no-op if null
+ * @param with
+ * the Strings to replace with, no-op if null
+ * @return the text with any replacements processed, <code>null</code> if
+ * null String input
+ * @throws IndexOutOfBoundsException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ public static String replaceEach(String text, String[] repl, String[]
with) {
+ return replaceEach(text, repl, with, false, 0);
+ }
+
+ /**
+ * <p>
+ * Replaces all occurances of Strings within another String.
+ * </p>
+ *
+ * <p>
+ * A <code>null</code> reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored. This will not repeat, for repeating replaces, call the
+ * overloaded method.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.replaceEach(null, *, *, *) = null
+ * StringUtils.replaceEach("", *, *, *) = ""
+ * StringUtils.replaceEach("aba", null, null, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *)
= "b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"},
*) = "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"w", "t"}, *) = "wcte"
+ * (example of how it repeats)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"d", "t"}, false) = "dcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"d", "t"}, true) = "tcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"d", "ab"}, true) = IllegalArgumentException
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"d", "ab"}, false) = "dcabe"
+ * </pre>
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param repl
+ * the Strings to search for, no-op if null
+ * @param with
+ * the Strings to replace with, no-op if null
+ * @return the text with any replacements processed, <code>null</code> if
+ * null String input
+ * @throws IllegalArgumentException
+ * if the search is repeating and there is an endless loop due
+ * to outputs of one being inputs to another
+ * @throws IndexOutOfBoundsException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ public static String replaceEachRepeatedly(String text, String[] repl,
String[] with) {
+
+ // timeToLive should be 0 if not used or nothing to replace, else it's
+ // the length of the replace array
+ int timeToLive = repl == null ? 0 : repl.length;
+ return replaceEach(text, repl, with, true, timeToLive);
+ }
+
+ /**
+ * <p>
+ * Replaces all occurances of Strings within another String.
+ * </p>
+ *
+ * <p>
+ * A <code>null</code> reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.replaceEach(null, *, *, *) = null
+ * StringUtils.replaceEach("", *, *, *) = ""
+ * StringUtils.replaceEach("aba", null, null, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *)
= "b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"},
*) = "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"w", "t"}, *) = "wcte"
+ * (example of how it repeats)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"d", "t"}, false) = "dcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"d", "t"}, true) = "tcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new
String[]{"d", "ab"}, *) = IllegalArgumentException
+ * </pre>
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param repl
+ * the Strings to search for, no-op if null
+ * @param with
+ * the Strings to replace with, no-op if null
+ * @param timeToLive
+ * if less than 0 then there is a circular reference and endless
+ * loop
+ * @return the text with any replacements processed, <code>null</code> if
+ * null String input
+ * @throws IllegalArgumentException
+ * if the search is repeating and there is an endless loop due
+ * to outputs of one being inputs to another
+ * @throws IndexOutOfBoundsException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ private static String replaceEach(String text, String[] repl, String[]
with,
+ boolean repeat, int timeToLive) {
+
+ // mchyzer Performance note: This creates very few new objects (one
major goal)
+ // let me know if there are performance requests, we can create a
harness to measure
+
+ if (text == null || text.length() == 0 ||
+ repl == null || repl.length == 0 ||
+ with == null || with.length == 0)
+ {
+ return text;
+ }
+
+ // if recursing, this shouldnt be less than 0
+ if (timeToLive < 0) {
+ throw new IllegalStateException("TimeToLive of " + timeToLive + "
is less than 0: " + text);
+ }
+
+ int replLength = repl.length;
+ int withLength = with.length;
+
+ // make sure lengths are ok, these need to be equal
+ if (replLength != withLength) {
+ throw new IllegalArgumentException("Search and Replace array
lengths don't match: " + replLength + " vs " + withLength);
+ }
+
+ // keep track of which still have matches
+ boolean[] noMoreMatchesForReplIndex = new boolean[replLength];
+
+ // index on index that the match was found
+ int textIndex = -1;
+ int replaceIndex = -1;
+ int tempIndex = -1;
+
+ // index of replace array that will replace the search string found
+ // NOTE: logic duplicated below START
+ for (int i = 0; i < replLength; i++) {
+ if (noMoreMatchesForReplIndex[i] || repl[i] == null ||
repl[i].length() == 0 || with[i] == null) {
+ continue;
+ }
+ tempIndex = text.indexOf(repl[i]);
+
+ // see if we need to keep searching for this
+ if (tempIndex == -1) {
+ noMoreMatchesForReplIndex[i] = true;
+ } else {
+ if (textIndex == -1 || tempIndex < textIndex) {
+ textIndex = tempIndex;
+ replaceIndex = i;
+ }
+ }
+ }
+ // NOTE: logic mostly below END
+
+ // no search strings found, we are done
+ if (textIndex == -1) {
+ return text;
+ }
+
+ int start = 0;
+
+ // get a good guess on the size of the result buffer so it doesnt have
to double if it goes over a bit
+ int increase = 0;
+
+ // count the replacement text elements that are larger than their
corresponding text being replaced
+ for (int i=0; i<repl.length; i++) {
+ int greater = with[i].length() - repl[i].length();
+ if(greater > 0) {
+ increase += 3 * greater; // assume 3 matches
+ }
+ }
+ // have upper-bound at 20% increase, then let Java take over
+ increase = Math.min(increase, text.length() / 5);
+
+ StringBuffer buf = new StringBuffer(text.length() + increase);
+
+ while (textIndex != -1) {
+
+ for (int i = start; i < textIndex; i++) {
+ buf.append(text.charAt(i));
+ }
+ buf.append(with[replaceIndex]);
+
+ start = textIndex + repl[replaceIndex].length();
+
+ textIndex = -1;
+ replaceIndex = -1;
+ tempIndex = -1;
+ // find the next earliest match
+ // NOTE: logic mostly duplicated above START
+ for (int i = 0; i < replLength; i++) {
+ if (noMoreMatchesForReplIndex[i] || repl[i] == null ||
repl[i].length() == 0 || with[i] == null) {
+ continue;
+ }
+ tempIndex = text.indexOf(repl[i], start);
+
+ //see if we need to keep searching for this
+ if (tempIndex == -1) {
+ noMoreMatchesForReplIndex[i] = true;
+ } else {
+ if (textIndex == -1 || tempIndex < textIndex) {
+ textIndex = tempIndex;
+ replaceIndex = i;
+ }
+ }
+ }
+ // NOTE: logic duplicated above END
+
+ }
+ int textLength = text.length();
+ for (int i = start; i < textLength; i++) {
+ buf.append(text.charAt(i));
+ }
+ String result = buf.toString();
+ if (!repeat) {
+ return result;
+ }
+
+ return replaceEach(result, repl, with, repeat, timeToLive - 1);
+ }
+
// Replace, character based
//-----------------------------------------------------------------------
/**
Modified:
commons/proper/lang/trunk/src/test/org/apache/commons/lang/StringUtilsTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/org/apache/commons/lang/StringUtilsTest.java?rev=616170&r1=616169&r2=616170&view=diff
==============================================================================
---
commons/proper/lang/trunk/src/test/org/apache/commons/lang/StringUtilsTest.java
(original)
+++
commons/proper/lang/trunk/src/test/org/apache/commons/lang/StringUtilsTest.java
Mon Jan 28 21:33:17 2008
@@ -1030,6 +1030,64 @@
assertEquals("foofoo", StringUtils.replaceOnce("foofoofoo", "foo",
""));
}
+ /**
+ * Test method for
'org.apache.commons.lang.StringUtils.replaceEach(String, String[], String[])'
+ */
+ public void testReplace_StringStringArrayStringArray() {
+
+
+ //JAVADOC TESTS START
+ assertNull(StringUtils.replaceEach(null, new String[]{"a"}, new
String[]{"b"}));
+ assertEquals(StringUtils.replaceEach("", new String[]{"a"}, new
String[]{"b"}),"");
+ assertEquals(StringUtils.replaceEach("aba", null, null),"aba");
+ assertEquals(StringUtils.replaceEach("aba", new String[0],
null),"aba");
+ assertEquals(StringUtils.replaceEach("aba", null, new
String[0]),"aba");
+ assertEquals(StringUtils.replaceEach("aba", new String[]{"a"},
null),"aba");
+
+ assertEquals(StringUtils.replaceEach("aba", new String[]{"a"}, new
String[]{""}),"b");
+ assertEquals(StringUtils.replaceEach("aba", new String[]{null}, new
String[]{"a"}),"aba");
+ assertEquals(StringUtils.replaceEach("abcde", new String[]{"ab", "d"},
new String[]{"w", "t"}),"wcte");
+ assertEquals(StringUtils.replaceEach("abcde", new String[]{"ab", "d"},
new String[]{"d", "t"}),"dcte");
+ //JAVADOC TESTS END
+
+ assertEquals("bcc", StringUtils.replaceEach("abc", new String[]{"a",
"b"}, new String[]{"b", "c"}));
+ assertEquals("q651.506bera", StringUtils.replaceEach("d216.102oren",
+ new String[]{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
"k", "l", "m", "n",
+ "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"A", "B", "C", "D",
+ "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T",
+ "U", "V", "W", "X", "Y", "Z", "1", "2", "3", "4", "5", "6",
"7", "8", "9"},
+ new String[]{"n", "o", "p", "q", "r", "s", "t", "u", "v", "w",
"x", "y", "z", "a",
+ "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"N", "O", "P", "Q",
+ "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "A", "B", "C",
"D", "E", "F", "G",
+ "H", "I", "J", "K", "L", "M", "5", "6", "7", "8", "9", "1",
"2", "3", "4"}));
+ }
+
+ /**
+ * Test method for
'org.apache.commons.lang.StringUtils.replaceEachRepeatedly(String, String[],
String[])'
+ */
+ public void testReplace_StringStringArrayStringArrayBoolean() {
+ //JAVADOC TESTS START
+ assertNull(StringUtils.replaceEachRepeatedly(null, new String[]{"a"},
new String[]{"b"}));
+ assertEquals(StringUtils.replaceEachRepeatedly("", new String[]{"a"},
new String[]{"b"}),"");
+ assertEquals(StringUtils.replaceEachRepeatedly("aba", null,
null),"aba");
+ assertEquals(StringUtils.replaceEachRepeatedly("aba", new String[0],
null),"aba");
+ assertEquals(StringUtils.replaceEachRepeatedly("aba", null, new
String[0]),"aba");
+ assertEquals(StringUtils.replaceEachRepeatedly("aba", new String[0],
null),"aba");
+
+ assertEquals(StringUtils.replaceEachRepeatedly("aba", new
String[]{"a"}, new String[]{""}),"b");
+ assertEquals(StringUtils.replaceEachRepeatedly("aba", new
String[]{null}, new String[]{"a"}),"aba");
+ assertEquals(StringUtils.replaceEachRepeatedly("abcde", new
String[]{"ab", "d"}, new String[]{"w", "t"}),"wcte");
+ assertEquals(StringUtils.replaceEachRepeatedly("abcde", new
String[]{"ab", "d"}, new String[]{"d", "t"}),"tcte");
+
+ try {
+ StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab",
"d"}, new String[]{"d", "ab"});
+ fail("Should be a circular reference");
+ } catch (IllegalArgumentException e) {}
+
+ //JAVADOC TESTS END
+
+ }
+
public void testReplaceChars_StringCharChar() {
assertEquals(null, StringUtils.replaceChars(null, 'b', 'z'));
assertEquals("", StringUtils.replaceChars("", 'b', 'z'));