Skip to main content
Code Review

Return to Answer

added 2192 characters in body
Source Link
200_success
  • 145.6k
  • 22
  • 190
  • 479

Therefore, the string replacements must be done in a single pass of the format string. I recommend doing it using a regular expression.

Furthermore, withyour method provides no escape mechanism, in case you need to specify a literal Matcher.replaceAll(Function<MatchResult,String> replacer) %(blah) in the format string. In Java, it would be customary to use backslash as an escape character.

Suggested solution

This solution uses Matcher.replaceAll(Function<MatchResult,String> replacer) , which was introduced in Java 9, to provide each substitution text via a callback.

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NamedFormatter {
 private static final Pattern RE = Pattern.compile(
 "\\\\(.)" + // Treat any character after a backslash literally 
 "|" +
 "(%\\(([^)]+)\\))" // Look for %(keys) to replace
 );
 private NamedFormatter() {}
 /**
 * Expands format strings containing <code>%(keys)</code>.
 *
 * <p>Examples:</p>
 *
 * <ul>
 * <li><code>NamedFormatter.format("Hello, %(name)!", Map.of("name", "200_success"))</code> → <code>"Hello, 200_success!"</code></li>
 * <li><code>NamedFormatter.format("Hello, \%(name)!", Map.of("name", "200_success"))</code> → <code>"Hello, %(name)!"</code></li>
 * <li><code>NamedFormatter.format("Hello, %(name)!", Map.of("foo", "bar"))</code> → <code>"Hello, %(name)!"</code></li>
 * </ul>
 *
 * @param fmt The format string. Any character in the format string that
 * follows a backslash is treated literally. Any
 * <code>%(key)</code> is replaced by its corresponding value
 * in the <code>values</code> map. If the key does not exist
 * in the <code>values</code> map, then it is left unsubstituted.
 *
 * @param values Key-value pairs to be used in the substitutions.
 *
 * @return The formatted string.
 */
 public static String format(String fmt, Map<String, Object> values) {
 return RE.matcher(fmt).replaceAll(match ->
 match.group(1) != null ?
 match.group(1) :
 values.getOrDefault(match.group(3), match.group(2)).toString()
 );
 }
}

Therefore, the string replacements must be done in a single pass of the format string. I recommend doing it using a regular expression, with Matcher.replaceAll(Function<MatchResult,String> replacer) to provide each substitution text via a callback.

Therefore, the string replacements must be done in a single pass of the format string. I recommend doing it using a regular expression.

Furthermore, your method provides no escape mechanism, in case you need to specify a literal %(blah) in the format string. In Java, it would be customary to use backslash as an escape character.

Suggested solution

This solution uses Matcher.replaceAll(Function<MatchResult,String> replacer) , which was introduced in Java 9, to provide each substitution text via a callback.

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NamedFormatter {
 private static final Pattern RE = Pattern.compile(
 "\\\\(.)" + // Treat any character after a backslash literally 
 "|" +
 "(%\\(([^)]+)\\))" // Look for %(keys) to replace
 );
 private NamedFormatter() {}
 /**
 * Expands format strings containing <code>%(keys)</code>.
 *
 * <p>Examples:</p>
 *
 * <ul>
 * <li><code>NamedFormatter.format("Hello, %(name)!", Map.of("name", "200_success"))</code> → <code>"Hello, 200_success!"</code></li>
 * <li><code>NamedFormatter.format("Hello, \%(name)!", Map.of("name", "200_success"))</code> → <code>"Hello, %(name)!"</code></li>
 * <li><code>NamedFormatter.format("Hello, %(name)!", Map.of("foo", "bar"))</code> → <code>"Hello, %(name)!"</code></li>
 * </ul>
 *
 * @param fmt The format string. Any character in the format string that
 * follows a backslash is treated literally. Any
 * <code>%(key)</code> is replaced by its corresponding value
 * in the <code>values</code> map. If the key does not exist
 * in the <code>values</code> map, then it is left unsubstituted.
 *
 * @param values Key-value pairs to be used in the substitutions.
 *
 * @return The formatted string.
 */
 public static String format(String fmt, Map<String, Object> values) {
 return RE.matcher(fmt).replaceAll(match ->
 match.group(1) != null ?
 match.group(1) :
 values.getOrDefault(match.group(3), match.group(2)).toString()
 );
 }
}
Source Link
200_success
  • 145.6k
  • 22
  • 190
  • 479

Performing string substitutions using multiple passes is almost always the wrong approach, and leads to bugs. If one of the values happens to be a string that looks like a %(key), then all sorts of unpredictable things could happen, including various uncontrolled format string attacks!

Therefore, the string replacements must be done in a single pass of the format string. I recommend doing it using a regular expression, with Matcher.replaceAll(Function<MatchResult,String> replacer) to provide each substitution text via a callback.

lang-java

AltStyle によって変換されたページ (->オリジナル) /