-
Notifications
You must be signed in to change notification settings - Fork 283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introducing a Messages.formatWithAttributes() method + unit tests #1649
Conversation
Messages.format("You have included {} placeholders, however only provided {} arguments.", matchesCount, args.length)); | ||
} | ||
matcher.reset(); //Since we have already called .find(), and that state needs to be reset | ||
while (matcher.find()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use Matcher.appendReplacement()
to simplify this?
@@ -110,4 +118,74 @@ public static String format(String messageTemplate, Object... args) { | |||
return builder.toString(); | |||
} | |||
|
|||
/** | |||
* Formats a templated message inserting named arguments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think these Javadoc make it clear enough what the method does. An example would be worth a thousand words.
while (matcher.find()) { | ||
matchesCount++; | ||
} | ||
if (matchesCount > args.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you roll this check into the replacement loop?
*/ | ||
public static Pair<String, Map<String, String>> formatWithAttributes(String messageTemplate, Object... args) { | ||
if (messageTemplate == null) { | ||
return formatWithAttributes("", args); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this achieve the same thing?
return "";
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logically, yes. However, the method has to return Pair<String, Map<String, String>>
- returning a String would not work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point was that it's probably clearer to return immediately rather than calling back into the method and asking the reader to work out how that would behave. Like this:
return Pair.of("", ImmutableMap.of());
return formatWithAttributes("", args); | ||
} | ||
if (args == null) { | ||
return formatWithAttributes(messageTemplate, new Object[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the same as:
return messageTemplate;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logically, yes. However, the method has to return Pair<String, Map<String, String>>
- returning a String would not work
ImmutableMap.Builder<String, String> attributesMap = ImmutableMap.builder(); | ||
|
||
String pattern = "\\{(\\w+)\\}"; | ||
Pattern regexPattern = Pattern.compile(pattern); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compiling a pattern is relatively expensive. Do it once and put it in a static final field. Pattern
is thread safe and can be shared.
|
||
String pattern = "\\{(\\w+)\\}"; | ||
Pattern regexPattern = Pattern.compile(pattern); | ||
Matcher matcher = regexPattern.matcher(messageTemplate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a regex might be a performance problem. But manually parsing is likely to be fiddly. I'd use a regex for simplicity until it's obviously a problem.
return new Object[][]{ | ||
// null template | ||
{null, null, Pair.of("", ImmutableMap.of())}, | ||
{null, new Object[]{}, Pair.of("", ImmutableMap.of())}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be one space between the square and curly braces [] {
on each of these lines
* The message template contains zero to many "{name}" placeholders. | ||
* Each placeholder is replaced by the next available argument. | ||
* Empty "{}" placeholders do not get replaced, and will be present in the output in the same form. | ||
* If there are too few arguments, then an {@link IllegalArgumentException} will be thrown. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No exceptions. This method is used when producing exceptions, so it must not throw one.
* <p> | ||
* The message template contains zero to many "{name}" placeholders. | ||
* Each placeholder is replaced by the next available argument. | ||
* Empty "{}" placeholders do not get replaced, and will be present in the output in the same form. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. {}
is still a placeholder, just an unamed one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That argument is outdated. Both named and unnamed placeholders are not replaced, as expected
|
||
// try to make builder big enough for the message and the args | ||
StringBuilder outputStringBuilder = new StringBuilder(messageTemplate.length() + args.length * 20); | ||
Map<String, String> attributesMap = new HashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment to not use ImmutableMap
as duplicate keys must be allowed even though there is data loss (to avoid exceptions)
int groupIndex = 0; | ||
int lastAddedStringEndIndex = 0; | ||
int matchesCount = 0; | ||
while (matcher.find()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop won't be needed once you get rid of the exception
|
||
if (matchesCount > args.length) { | ||
throw new IllegalArgumentException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't throw an exception. Just ignore the extra placeholders so they appear in the output as {}
or {foo}
.
@@ -110,4 +120,57 @@ public static String format(String messageTemplate, Object... args) { | |||
return builder.toString(); | |||
} | |||
|
|||
/** | |||
* Formats a templated message inserting named arguments. Typical template would look like "Hello, {attributeName}". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think this needs an example. I don't think it's clear from the docs what it does. Something like:
Messages.formatWithAttributes("Foo={foo}, Bar={}", "abc", 123);
returns the message
"Foo=abc, Bar=123"
and the map
{"foo": "123"}
Feel free to copy and paste :)
This new method will not only return the formatted message with values instead of placeholders but also a map of attributes.