Parameterized String Externalization

M

Marco

Hi all,

I need to extract all the string constants in a package, transform
them into patterns (using java.text.MessageFormat), put the patterns
in a resource bundle and replace the strings in the code with calls to
a class that uses such a bundle. For instance this:

System.out.println ( "Process terminated " + n + " file(s)
analyzed" );

would be replaced by something like:

System.out.println ( i18n.tr ( "procTerminated" ), n );

where i18n is a class that wraps the usage of the RB and MessageFormat
(yes, names are ispired by gettext). In addition to code replacement,
a new entry would be created in the resource bundle:

procTerminated = Process terminated {0} file(s) analyzed

I don't actually need this for localization, although the result could
be used for that too. It's just to factorize all the messages in one
place and let other people review them.

Now, Eclipse has a feature that does something similar, but in
practice it works only for constants, in a case like the above it
produces two messages in the RB (!).

Does anyone know if there is some more advanced tool, which is able to
recognize the parameterization?

Thanks in advance for any help.
 
O

Owen Jacobson

Hi all,

I need to extract all the string constants in a package, transform
them into patterns (using java.text.MessageFormat), put the patterns
in a resource bundle and replace the strings in the code with calls to
a class that uses such a bundle. For instance this:

System.out.println ( "Process terminated " + n + " file(s)
analyzed" );

would be replaced by something like:

System.out.println ( i18n.tr ( "procTerminated" ), n );

where i18n is a class that wraps the usage of the RB and MessageFormat
(yes, names are ispired by gettext). In addition to code replacement,
a new entry would be created in the resource bundle:

procTerminated = Process terminated {0} file(s) analyzed

I don't actually need this for localization, although the result could
be used for that too. It's just to factorize all the messages in one
place and let other people review them.

Now, Eclipse has a feature that does something similar, but in
practice it works only for constants, in a case like the above it
produces two messages in the RB (!).

Does anyone know if there is some more advanced tool, which is able to
recognize the parameterization?

Thanks in advance for any help.

It may be too late to take advantage of this now, but if you write your
messages using MessageFormat.format (or, more recently, String.format)
in the first place, then it's much easier to externalize the format
string to a resource bundle later:

System.out.println ( String.format ( "Process terminated %1$d file(s)
analyzed", n ) );

or even

System.out.printf ( "Process terminated %1$d file(s) analyzed\n", n );

(I habitually use positional specifiers -- %1$d, %2$s, etc -- in format
strings to permit localization to re-order the placeholders. Using
non-positional format specifiers means that the placeholders MUST
appear in the same order as the parameters to String.format. I'd love
it if there were named placeholders, as with Python's format
specifiers, but without a syntax for named parameters it'd be painfully
verbose to implement.)

Analyzing the source code for this case isn't too hard _in theory_,
since the structure you're looking for is any place where the parse
tree contains
<string constant expression> ['+' <non-constant expression> ['+'
<string constant expression>]?]+

The tricky bit is in guessing the correct format specifier for each
non-constant expression: defaulting to %N$s (or {N}), which uses
toString(), is probably an acceptable default, but you'll still need to
go over the replacements and change them where you want, eg., a
specific date or number format instead of the default.

I'm not aware of any existing tools that implement this. There are
existing Java grammars for Antlr and JavaCC, though, so you could
probably write one yourself without too much trouble.

-o
 
M

Mark Space

Owen said:
(I habitually use positional specifiers -- %1$d, %2$s, etc -- in format
strings to permit localization to re-order the placeholders. Using
non-positional format specifiers means that the placeholders MUST appear
in the same order as the parameters to String.format. I'd love it if
there were named placeholders, as with Python's format specifiers, but
without a syntax for named parameters it'd be painfully verbose to
implement.)


Interesting. How does this help in Python? It seems to me that even
with named parameters, you still have to rejigger the order somehow.
This should be the same as a method call with parameters in a different
order, I think.

Not trying to start a language flame war, just curious if such a thing
really could be implemented in Java.
 
O

Owen Jacobson

Interesting. How does this help in Python? It seems to me that even
with named parameters, you still have to rejigger the order somehow.
This should be the same as a method call with parameters in a different
order, I think.

Not trying to start a language flame war, just curious if such a thing
really could be implemented in Java.

In python, you have the following construction:

"foo: %(foo)s, bar: %(bar)d" % {'foo': 'hello', 'bar': 667}

which evaluates to "foo: hello, bar: 667". The placeholders can be
reordered however you like; as they contain a name, they're substituted
for only the named entry in the right-hand argument to %.

Doing something similar in Java would require a terse Map syntax. The
closest I've ever come up with,

new HashMap<String, Object> () {{
put ("foo": "hello");
put ("bar": 667);
}}

plays havoc on serialization (if you care about that) and takes up many
many more characters to say almost the same thing, which makes it hard
to use inline in a string-formatting context. The other option would be
a method like format(String, Object...), with a documentation note to
the effect that the varargs parameter is expecting alternating names
and values:

format("foo: %(foo)s, bar: %(bar)d", "foo", "hello", "bar" 667);

and that's, well, wrong.

Cheers,

-o
 
T

Tom Anderson

In python, you have the following construction:

"foo: %(foo)s, bar: %(bar)d" % {'foo': 'hello', 'bar': 667}

which evaluates to "foo: hello, bar: 667". The placeholders can be
reordered however you like; as they contain a name, they're substituted
for only the named entry in the right-hand argument to %.

It is a good feature. I'm often annoyed when other languages don't have
it!
Doing something similar in Java would require a terse Map syntax.

Well, it wouldn't *require* it, but it would be a lot simpler if we had
it.
The closest I've ever come up with,

new HashMap<String, Object> () {{
put ("foo": "hello");
put ("bar": 667);
}}

plays havoc on serialization (if you care about that) and takes up many many
more characters to say almost the same thing, which makes it hard to use
inline in a string-formatting context. The other option would be a method
like format(String, Object...), with a documentation note to the effect that
the varargs parameter is expecting alternating names and values:

format("foo: %(foo)s, bar: %(bar)d", "foo", "hello", "bar" 667);

and that's, well, wrong.

I came up with a similar idea in a different context. Some of our unit
tests involve creating lots of little maps which have strings as both keys
and values, and i thought about writing a method like (untested):

public Map<String, String> map(String... args) {
if ((args.length % 2) != 0) throw new IllegalArgumentException();
Map<String, String> map = new HashMap<String, String>();
for (int i = 0; i < args.length; i += 2) {
map.put(args, args[i + 1]);
}
return map;
}

Used like:

machine.frob(map(
"foo", "hello",
"bar", "667"
))

A more flexible version which could take arbitrary key and value types
would be possible, but as you say, not comfortable. I'd want to pass in
class objects for the key and value types, and use them to cast the
arguments, to ensure type safety (or, yes Mark, use a CheckedMap), but
that starts to look painful. Although not that bad:

machine.frob(map(String.class, Integer.class
"foo", 3770,
"bar", 667
))

tom
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top