Thursday, December 13, 2012

CommaStringBuilder

I realized I was building a lot of comma separated lists in my Suneido Java code. I had used a mixture of approaches.

If I had an array or iterable, I could use things like Guava's Joiner or Ints.join. But if I needed to process the items that wouldn't work. (You could use something like Iterables transform or FluentIterables, but without lambdas, it's a lot of overhead to write a class just for one line of processing.)

Otherwise, I'd use a StringBuilder with a variety of ways to handle getting commas between items but not before the first or after the last.

Sometimes I'd do:

for (int i = 0; i < array.length; ++i) {
if (i > 0)
sb.append(",");
sb.append(fn(array[i]));
}
return sb.toString();
view raw gistfile1.java hosted with ❤ by GitHub
Other times:

if (list.isEmpty())
return "";
for (Object x : list)
sb.append(fn(x)).append(",");
return sb.deleteCharAt(sb.length() - 1).toString();
view raw gistfile1.java hosted with ❤ by GitHub
And other times:

if (list.isEmpty())
return "";
for (Object x : list)
sb.append(",").append(fn(x));
return sb.substring(1);
view raw gistfile1.java hosted with ❤ by GitHub
Note that in the last two you need to check for an empty list or else the deleteCharAt or substring will fail.

I decided to write a utility class to make my code simpler and more consistent. Here's an example of its use:

CommaStringBuilder csb = new CommaStringBuilder("(");
for (Object x : list)
csb.add(fn(x));
return csb.append(")").toString();
view raw gistfile1.java hosted with ❤ by GitHub
The add methods are for the comma separated items, the append methods are just passed to the internal StringBuilder. The comma separator is hard coded, but obviously it would be easy to allow passing it in. I decided to implement the Appendable interface since it was easy.

public class CommaStringBuilder implements Appendable {
private final StringBuilder sb;
private boolean first = true;
public CommaStringBuilder() {
sb = new StringBuilder();
}
public CommaStringBuilder(String s) {
sb = new StringBuilder(s);
}
public CommaStringBuilder(StringBuilder sb) {
this.sb = sb;
}
public CommaStringBuilder add(String s) {
if (first)
first = false;
else
sb.append(",");
sb.append(s);
return this;
}
public CommaStringBuilder add(Object x) {
return add(x.toString());
}
public CommaStringBuilder add(long i) {
if (first)
first = false;
else
sb.append(",");
sb.append(i);
return this;
}
@Override
public CommaStringBuilder append(CharSequence s) {
sb.append(s);
return this;
}
@Override
public CommaStringBuilder append(CharSequence csq, int start, int end) {
sb.append(csq, start, end);
return this;
}
@Override
public CommaStringBuilder append(char c) {
sb.append(c);
return this;
}
public CommaStringBuilder append(Object x) {
sb.append(x);
return this;
}
public CommaStringBuilder append(long i) {
sb.append(i);
return this;
}
public void clear() {
sb.setLength(0);
first = true;
}
@Override
public String toString() {
return sb.toString();
}
}

No comments: