Skip to content
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

Merged
merged 12 commits into from
Jan 30, 2018

Conversation

andreiruse
Copy link
Contributor

This new method will not only return the formatted message with values instead of placeholders but also a map of attributes.

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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -110,4 +118,74 @@ public static String format(String messageTemplate, Object... args) {
return builder.toString();
}

/**
* Formats a templated message inserting named arguments.
Copy link
Contributor

@cjkent cjkent Jan 26, 2018

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) {
Copy link
Contributor

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);
Copy link
Contributor

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 "";

Copy link
Contributor Author

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.

Copy link
Contributor

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]);
Copy link
Contributor

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;

Copy link
Contributor Author

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);
Copy link
Contributor

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);
Copy link
Contributor

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())},
Copy link
Member

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.
Copy link
Member

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.
Copy link
Member

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.

Copy link
Contributor Author

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<>();
Copy link
Member

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()) {
Copy link
Member

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(
Copy link
Contributor

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}".
Copy link
Contributor

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 :)

@andreiruse andreiruse merged commit dedab3e into master Jan 30, 2018
@andreiruse andreiruse deleted the topic/messages-format-with-attributes branch January 30, 2018 11:17
@jodastephen jodastephen added this to the v1.7 milestone Feb 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants