String Formatter in GWT
How do I format my string in GWT?
I made a method
Formatter format = new Formatter();
int matches = 0;
Forma开发者_StackOverflow中文版tter formattedString = format.format("%d numbers(s, args) in correct position", matches);
return formattedString.toString();
But it complains by saying
Validating newly compiled units
[ERROR] Errors in 'file:/C:/Documents%20and%20Settings/kkshetri/workspace/MasterMind/MasterMind/src/com/kunjan/MasterMind/client/MasterMind.java'
[ERROR] Line 84: No source code is available for type java.util.Formatter; did you forget to inherit a required module?
Isn't Formatter included?
See the official page on GWT date and number formatting.
They suggest the following:
myNum decimal = 33.23232;
myString = NumberFormat.getFormat("#.00").format(decimal);
It is best to use their supported, optimized methods, than to cook up your own non-optimal method. Their compiler will be optimizing them all to nearly the same thing anyway in the end.
A very simple replacement for String.format() in GWT 2.1+:
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.regexp.shared.SplitResult;
public static String format(final String format, final Object... args) {
final RegExp regex = RegExp.compile("%[a-z]");
final SplitResult split = regex.split(format);
final StringBuffer msg = new StringBuffer();
for (int pos = 0; pos < split.length() - 1; ++pos) {
msg.append(split.get(pos));
msg.append(args[pos].toString());
}
msg.append(split.get(split.length() - 1));
return msg.toString();
}
UPDATE: Please see (and vote up) Joseph Lust's post below before looking further at this answer.
Looks like formatter isn't included according to this post. However, they suggest some alternatives.
Or even simpler, not using RegExp, and using only Strings:
public static String format(final String format, final String... args) {
String[] split = format.split("%s");
final StringBuffer msg = new StringBuffer();
for (int pos = 0; pos < split.length - 1; pos += 1) {
msg.append(split[pos]);
msg.append(args[pos]);
}
msg.append(split[split.length - 1]);
return msg.toString();
}
Another suggestion which makes use of JSNI and a nice JavaScript format function from another post:
import com.google.gwt.core.client.JsArrayString;
public abstract class StringFormatter {
public static String format(final String format, final Object... args) {
if (null == args || 0 == args.length)
return format;
JsArrayString array = newArray();
for (Object arg : args) {
array.push(String.valueOf(arg)); // TODO: smarter conversion?
}
return nativeFormat(format, array);
}
private static native JsArrayString newArray()/*-{
return [];
}-*/;
private static native String nativeFormat(final String format, final JsArrayString args)/*-{
return format.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != 'undefined' ? args[number] : match;
});
}-*/;
}
One can then make a call like this:
StringFormatter.format("Greetings {0}, it's {1} o'clock, which is a {2} statement", "Master", 8, false);
...with the result being
Greetings Master, it's 8 o'clock, which is a false statement
There is a potential to improve further at the TODO comment, e.g. utilise NumberFormat. Suggestions are welcome.
An extension to Daniels solution: Also supports escaping using ' and throws if a number cannot be parsed (like the JVM version does):
private static final char OPEN = '{';
private static final char CLOSE = '}';
private static final char ESCAPE = '\'';
@Override
public String format(String pattern, Object... arguments) {
if (pattern == null || pattern.isEmpty())
return "";
// Approximate the result length: format string + 16 character args
StringBuilder sb = new StringBuilder(pattern.length() + (arguments.length * 16));
int cur = 0;
int len = pattern.length();
// if escaped, then its >= 0
int escapedAtIndex = -1;
while (cur < len) {
char currentChar = pattern.charAt(cur);
switch (currentChar) {
case OPEN:
if (escapedAtIndex >= 0) {
// currently escaped
sb.append(currentChar);
} else {
// find close
int close = pattern.indexOf(CLOSE, cur + 1);
switch (close) {
case -1:
// Missing close. Actually an error. But just ignore
sb.append(currentChar);
break;
default:
// Ok, we have a close
final String nStr = pattern.substring(cur + 1, close);
try {
// Append the corresponding argument value
sb.append(arguments[Integer.parseInt(nStr)]);
} catch (Exception e) {
if (e instanceof NumberFormatException) {
throw new IllegalArgumentException(nStr +
" is not a number.");
}
// Append the curlies and the original delimited value
sb.append(OPEN).append(nStr).append(CLOSE);
}
// Continue after the close
cur = close;
break;
}
}
cur++;
break;
case ESCAPE:
// Special case: two '' are just converted to '
boolean nextIsEscapeToo = (cur + 1 < len) && pattern.charAt(cur + 1) == ESCAPE;
if (nextIsEscapeToo) {
sb.append(ESCAPE);
cur = cur + 2;
} else {
if (escapedAtIndex >= 0) {
// Escape end.
escapedAtIndex = -1;
} else {
// Escape start.
escapedAtIndex = cur;
}
cur++;
}
break;
default:
// 90% case: Nothing special, just a normal character
sb.append(currentChar);
cur++;
break;
}
}
return sb.toString();
}
This implementation and the JVM-Version both pass those tests:
// Replace: 0 items
assertFormat("Nothing to replace", "Nothing to replace");
// Replace: 1 item
assertFormat("{0} apples", "15 apples", 15);
assertFormat("number of apples: {0}", "number of apples: zero", "zero");
assertFormat("you ate {0} apples", "you ate some apples", "some");
// Replace 2 items
assertFormat("{1} text {0}", "second text first", "first", "second");
assertFormat("X {1} text {0}", "X second text first", "first", "second");
assertFormat("{0} text {1} X", "first text second X", "first", "second");
Escaping-Tests:
// Escaping with no replacement
assertFormat("It's the world", "Its the world");
assertFormat("It''s the world", "It's the world");
assertFormat("Open ' and now a second ' (closes)", "Open and now a second (closes)");
assertFormat("It'''s the world", "It's the world");
assertFormat("'{0}' {1} {2}", "{0} one two", "zero", "one", "two");
// Stays escaped (if end escape is missing)
assertFormat("'{0} {1} {2}", "{0} {1} {2}", "zero", "one", "two");
assertFormat("'{0} {1}' {2}", "{0} {1} two", "zero", "one", "two");
// No matter where we escape, stays escaped
assertFormat("It's a {0} world", "Its a {0} world", "blue");
// But we can end escape everywhere
assertFormat("It's a {0} world, but not '{1}",
"Its a {0} world, but not always", "blue", "always");
// I think we want this
assertFormat("It''s a {0} world, but not {1}",
"It's a blue world, but not always", "blue", "always");
// Triple
assertFormat("' '' '", " ' ");
// From oracle docs
assertFormat("'{''}'", "{'}");
// Missing argument (just stays 0)
assertFormat("begin {0} end", "begin {0} end");
// Throws
try {
assertFormat("begin {not_a_number} end", "begin {not_a_number} end");
throw new AssertionError("Should not get here");
} catch (IllegalArgumentException iae) {
// OK
}
another very very simple replacement for java.text.MessageFormat.format() :
public static String format(final String format, final Object... args) {
StringBuilder sb = new StringBuilder();
int cur = 0;
int len = format.length();
while (cur < len) {
int fi = format.indexOf('{', cur);
if (fi != -1) {
sb.append(format.substring(cur, fi));
int si = format.indexOf('}', fi);
if (si != -1) {
String nStr = format.substring(fi + 1, si);
int i = Integer.parseInt(nStr);
sb.append(args[i]);
cur = si + 1;
} else {
sb.append(format.substring(fi));
break;
}
} else {
sb.append(format.substring(cur, len));
break;
}
}
return sb.toString();
}
This one is pretty fast and ignores bad curly-delimited values:
public static String format(final String format, final Object... args)
{
if (format == null || format.isEmpty()) return "";
// Approximate the result length: format string + 16 character args
StringBuilder sb = new StringBuilder(format.length() + (args.length*16));
final char openDelim = '{';
final char closeDelim = '}';
int cur = 0;
int len = format.length();
int open;
int close;
while (cur < len)
{
switch (open = format.indexOf(openDelim, cur))
{
case -1:
return sb.append(format.substring(cur, len)).toString();
default:
sb.append(format.substring(cur, open));
switch (close = format.indexOf(closeDelim, open))
{
case -1:
return sb.append(format.substring(open)).toString();
default:
String nStr = format.substring(open + 1, close);
try
{
// Append the corresponding argument value
sb.append(args[Integer.parseInt(nStr)]);
}
catch (Exception e)
{
// Append the curlies and the original delimited value
sb.append(openDelim).append(nStr).append(closeDelim);
}
cur = close + 1;
}
}
}
return sb.toString();
}
Maybe the easiest way to do something like String.format, can be do it with a String.replace, for instance;
instead of do String.format("Hello %s", "Daniel");
==> "Hello %s".replace("%s", "Daniel")
,
both give us the same result, but just the second way works in GWT client side
I'm not keen on abusing string manipulation for doing regexps' job, but, based on bodrin's solution, you can code:
public static String format (String pattern, final Object ... args) {
for (Object arg : args) {
String part1 = pattern.substring(0,pattern.indexOf('{'));
String part2 = pattern.substring(pattern.indexOf('}') + 1);
pattern = part1 + arg + part2;
}
return pattern;
}
As an alternative you can use class NumberFormat:
NumberFormat fmt = NumberFormat.getDecimalFormat();
double value = 12345.6789;
String formatted = fmt.format(value);
// Prints 1,2345.6789 in the default locale
As mentionned above, there is GWT formatters for numbers and dates : NumberFormat
and DateTimeFormat
.
Still, I needed a solution for the well-know String.format(...)
case.
I end up with this solution, I don't know if it's wrong for performance, but it's visually clean. I'd be glad to hear any comment on it, or about other solution.
My String formatter :
public class Strings {
public static String format(final String format, final Object... args) {
String retVal = format;
for (final Object current : args) {
retVal = retVal.replaceFirst("[%][s]", current.toString());
}
return retVal;
}
}
and the JUTest if one want to re-use this :
public class StringsTest {
@Test
public final void testFormat() {
this.assertFormat("Some test here %s.", 54);
this.assertFormat("Some test here %s and there %s, and test [%s]. sfsfs !!!", 54, 59, "HAHA");
}
private void assertFormat(final String format, final Object... args) {
Assert.assertEquals("Formatting is not working", String.format(format, args), Strings.format(format, args));
}
}
精彩评论