/*
*/
import java.io.*;
import java.lang.reflect.*;
/*
* This is just a routine to test a few different versions
* of a replaceSubstring function.
*
* I realize that it's a funny-looking way of running a test,
* but I got tired of copying and pasting long testing routines
* (one for each function I was going to test), and it was a real
* pain to update all the duplicate testing blocks if I wanted to
* change the test in any way, so I made everything nice and
* reusable using reflection.
*
* If you want to add an additional function to test, all you have
* to do is paste it as a new method at the end of this class and
* add the method name to the methodNameList array in the startTesting
* method. The only caveat is that the new method has to accept
* parameters exactly like the existing methods do, and it has to
* return the same type of object.
*
* Sept 24, 2003
* UPDATE: Jim Cakalic sent some code for a replaceSubstring5 method,
* which provides some optimizations over replaceSubstring4. He also
* pointed out that the initial "sanity checks" should actually have
* if (find == null || find.length() == 0)
* instead of
* if (find == null || find == "")
* Doh! Thanks Jim.
*
* March 14, 2004
* UPDATE: John Marshall (http://swapcode.com) pointed out that calling
* the ReplaceSubstring methods with "" as both the original string and
* the find string would return "", when it should really return the
* value of whatever you're using as the replace value. His modifications
* are included in the ReplaceSubstring6 method.
*
* Julian Robichaux ( http://www.nsftools.com )
* March 14, 2004
*/
public class ReplaceSubstringTest {
PrintStream out = null;
public static void main (String[] args) {
ReplaceSubstringTest rst = new ReplaceSubstringTest();
rst.startTesting();
}
public ReplaceSubstringTest () {
this(System.out);
}
public ReplaceSubstringTest (PrintStream out) {
this.out = out;
}
public void startTesting () {
try {
int i;
int iterations = 50;
String [] methodNameList = { "replaceSubstring1",
"replaceSubstring2",
"replaceSubstring3",
"replaceSubstring4",
"replaceSubstring5",
"replaceSubstring6" };
Class[] paramTypes = {String.class, String.class, String.class};
Method[] testMethodList = new Method[methodNameList.length];
ReplaceSubstringTest thisClass = new ReplaceSubstringTest();
// based on our methodNameList, create an array of Method objects
// (this is a little more efficient than creating a Method every
// time you want to test a function)
for (i = 0; i < testMethodList.length; i++) {
testMethodList[i] = thisClass.getMethod(thisClass.getClass(), methodNameList[i], paramTypes);
}
// first we'll test each function for correctness (the validateMethod
// routine), and if a function passes that test then we'll time how
// long it takes to run the timeMethod routine. All information is
// sent to the "out" PrintStream defined in the constructor
for (i = 0; i < testMethodList.length; i++) {
if (thisClass.validateMethod(thisClass, testMethodList[i])) {
thisClass.timeMethod(thisClass, testMethodList[i], iterations);
}
}
} catch(Exception e) {
e.printStackTrace(out);
}
}
/*
* This is a simple wrapper around the Class.getMethod method.
* It keeps us from having to write try-catch blocks in the sections
* of code where we're trying to get the individual methods, so
* it makes the code a little easier to read (we just return null
* if there's an error)
*/
public Method getMethod (Class cls, String methodName, Class[] paramTypes) {
Method meth = null;
try {
meth = cls.getMethod(methodName, paramTypes);
} catch (Exception e) {
out.println("Error getting method " + methodName + ": " + e);
}
return meth;
}
/*
* This is a simple wrapper around the Method.invoke method.
* It keeps us from having to write try-catch blocks in the sections
* of code where we're trying to run the individual methods, so
* it makes the code a little easier to read (we just return null
* if there's an error)
*/
public Object runMethod (Object classInstance, Method meth, Object[] params) {
Object objResult = null;
try {
objResult = meth.invoke(classInstance, params);
} catch (InvocationTargetException ite) {
out.println("The method " + meth.getName() + " threw an error as it was running:");
ite.printStackTrace(out);
} catch (Exception e) {
out.println("Error trying to run method " + meth.getName() + ": " + e);
}
return objResult;
}
/*
* This is our validation routine. It runs a few simple replace
* examples and tests to see if the return value is correct.
* If you want to add any additional tests, you can just add
* them to the testParams and results arrays.
*/
public boolean validateMethod (Object classInstance, Method meth) {
boolean retVal = true;
if ((classInstance == null) || (meth == null))
return false;
Object[][] testParams = { {"aaaaaaaaaa", "a", "b"},
{"aaabaaabaaaa", "aa", "a"},
{"aaabaaabaaaa", "baa", "baba"},
{"ddogddogddog", "dog", "og"},
{"abababababa", "a", ""},
{"abababababa", "a", null},
{"abababababa", "", "c"},
{"abababababa", null, "c"},
{"abababababa", "", ""},
{"abababababa", null, null},
{null, "anything", "whatever"} };
String[] results = { "bbbbbbbbbb",
"aabaabaa",
"aaababaababaaa",
"dogdogdog",
"bbbbb",
"bbbbb",
"abababababa",
"abababababa",
"abababababa",
"abababababa",
null };
Object objResult;
out.println("\nTESTING METHOD " + meth.getName());
for (int i = 0; i < testParams.length; i++) {
objResult = runMethod(classInstance, meth, testParams[i]);
if ((results[i] == null) && (objResult == null)) {
out.println("Method " + meth.getName() + " passed test " + i);
} else if ((results[i] != null) && (results[i].equals(objResult))) {
out.println("Method " + meth.getName() + " passed test " + i);
} else {
out.println("FAILED. Method " + meth.getName() + " failed test " + i);
out.println("\tdesired result: " + results[i]);
out.println("\tactual result: " + objResult);
retVal = false;
}
}
return retVal;
}
/*
* This is a routine that times how long it takes to search
* and replace on a large String. We're assuming that the
* Method has already been tested for correctness.
*/
public boolean timeMethod (Object classInstance, Method meth, int iterations) {
boolean retVal = true;
int i = 0;
if ((classInstance == null) || (meth == null))
return false;
String testString = "abcdef";
// after 12 iterations, the 6 character testString
// will have grown to over 24,000 characters in length
for (i = 0; i < 12; i++) {
testString += testString;
}
Object[] testParams = { testString, "abc", "xyz" };
out.println("\nTIMING METHOD " + meth.getName());
long startTime = System.currentTimeMillis();
for (i = 0; i < iterations; i++) {
if (runMethod(classInstance, meth, testParams) == null) {
out.println("FAILED. " + meth.getName() + " is null");
i = iterations;
retVal = false;
} else if (System.currentTimeMillis() - startTime > 10000) {
out.println("TAKING TOO LONG: Breaking out of testing loop after " + i +
" of " + iterations + " iterations.");
break;
}
}
long endTime = System.currentTimeMillis();
out.println(Long.toString(endTime - startTime) + " ms elapsed for " + i + " iterations");
return retVal;
}
/*
* This routine actually fails the validation test, but it's good to
* see what happens in the case of a failure
*/
public String replaceSubstring1 (String str, String find, String replace)
{
for(int i = str.lastIndexOf(find); i >= 0; i = str.lastIndexOf(find, i - 1))
str = str.substring(0, i) + replace + str.substring(i + find.length(), str.length());
return str;
}
/*
* This routine works, but it's dog slow when you're working with
* large Strings and large numbers of replacements because it's
* doing all sorts of in-place String concatenations.
*/
public String replaceSubstring2 (String str, String find, String replace)
{
// sanity checks (you could argue that the method should throw
// an error in the case of nulls, but I'd rather deal with it
// this way)
if (str == null) return null;
if ((find == null) || (find.length() == 0)) return str;
if (replace == null) replace = "";
for(int i = str.indexOf(find); i >= 0; i = str.indexOf(find, i + replace.length()))
str = str.substring(0, i) + replace + str.substring(i + find.length(), str.length());
return str;
}
/*
* This routine is similar to the one above, but it keeps appending
* to a separate String instead of rebuilding the original String
* each time (which makes it just a tiny bit faster)
*/
public String replaceSubstring3 (String str, String find, String replace)
{
// sanity checks (you could argue that the method should throw
// an error in the case of nulls, but I'd rather deal with it
// this way)
if (str == null) return null;
if ((find == null) || (find.length() == 0)) return str;
if (replace == null) replace = "";
String newString = "";
int pos = 0;
int lastPos = 0;
while (pos >= 0) {
pos = str.indexOf(find, lastPos);
if (pos >= 0) {
newString += str.substring(lastPos, pos) + replace;
} else {
newString += str.substring(lastPos);
}
lastPos = pos + find.length();
}
return newString;
}
/*
* This routine is very fast, because it uses a StringBuffer
*/
public String replaceSubstring4 (String str, String find, String replace)
{
// sanity checks (you could argue that the method should throw
// an error in the case of nulls, but I'd rather deal with it
// this way)
if (str == null) return null;
if ((find == null) || (find.length() == 0)) return str;
if (replace == null) replace = "";
StringBuffer sb = new StringBuffer(str.length());
int pos = 0;
int lastPos = 0;
while (pos >= 0) {
pos = str.indexOf(find, lastPos);
if (pos >= 0) {
sb.append(str.substring(lastPos, pos));
sb.append(replace);
} else {
sb.append(str.substring(lastPos));
}
lastPos = pos + find.length();
}
return sb.toString();
}
/*
* Here's a slightly faster method, with modifications from Jim Cakalic.
* The difference in speed between this and replaceSubstring4 isn't
* apparent until you start doing large numbers of iterations, but
* the two essential changes were to store find.length() in a variable
* so you don't have to keep calling the method every time you make a
* replacement, and to deal with the original String as a char array,
* so you avoid the overhead of all the String creation involved with
* many calls to String.substring. He also "stacked" the calls to
* StringBuffer.append as a small efficiency gain.
*/
public String replaceSubstring5 (String str, String find, String replace)
{
// sanity checks (you could argue that the method should throw
// an error in the case of nulls, but I'd rather deal with it
// this way)
if (str == null) return null;
if (find == null || find.length() == 0) return str;
if (replace == null) replace = "";
StringBuffer sb = new StringBuffer(str.length());
char[] chars = str.toCharArray();
int findLen = find.length();
int pos = 0;
int lastPos = 0;
while (pos >= 0) {
pos = str.indexOf(find, lastPos);
if (pos >= 0) {
sb.append(chars, lastPos, pos - lastPos).append(replace);
} else {
sb.append(chars, lastPos, str.length() - lastPos);
}
lastPos = pos + findLen;
}
return sb.toString();
}
/*
* Modified by: John Marshall
* Similar to replaceSubstring5 but with added variable for length of source string.
* Prevents the calculation of source string length per iteration.
* Fixed possible bug where replaceSubstring("", "", "data")
* returned null instead of the value of the replacing string.
*/
public String replaceSubstring6 (String str, String find, String replace)
{
// sanity checks (you could argue that the method should throw
// an error in the case of nulls, but I'd rather deal with it
// this way)
if (null == str) return null;
if (null == find) return str;
if (null == replace) replace = "";
int strLen = str.length();
int findLen = find.length();
if ((0 == strLen) && (0 == findLen)) return replace;
if (0 == findLen) return str;
StringBuffer sb = new StringBuffer(strLen);
char[] chars = str.toCharArray();
int pos = 0;
int lastPos = 0;
while (pos >= 0) {
pos = str.indexOf(find, lastPos);
if (pos >= 0) {
sb.append(chars, lastPos, pos - lastPos).append(replace);
} else {
sb.append(chars, lastPos, strLen - lastPos);
}
lastPos = pos + findLen;
}
return sb.toString();
}
}