• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.13
today-context/src/main/java/infra/validation/MessageSourceMessageInterpolator.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.validation;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.util.LinkedHashSet;
23
import java.util.Locale;
24
import java.util.Set;
25

26
import infra.context.MessageSource;
27
import infra.core.i18n.LocaleContextHolder;
28
import jakarta.validation.MessageInterpolator;
29

30
/**
31
 * Resolves any message parameters via {@link MessageSource} and then interpolates a
32
 * message using the underlying {@link MessageInterpolator}.
33
 *
34
 * @author Dmytro Nosan
35
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
36
 * @author Scott Frederick
37
 * @since 4.0
38
 */
39
class MessageSourceMessageInterpolator implements MessageInterpolator {
40

41
  private static final char PREFIX = '{';
42

43
  private static final char SUFFIX = '}';
44

45
  private static final char ESCAPE = '\\';
46

47
  private final MessageSource messageSource;
48

49
  private final MessageInterpolator messageInterpolator;
50

51
  MessageSourceMessageInterpolator(MessageSource messageSource, MessageInterpolator messageInterpolator) {
2✔
52
    this.messageSource = messageSource;
3✔
53
    this.messageInterpolator = messageInterpolator;
3✔
54
  }
1✔
55

56
  @Override
57
  public String interpolate(String messageTemplate, Context context) {
58
    return interpolate(messageTemplate, context, LocaleContextHolder.getLocale());
×
59
  }
60

61
  @Override
62
  public String interpolate(String messageTemplate, Context context, Locale locale) {
63
    String message = replaceParameters(messageTemplate, locale);
5✔
64
    return this.messageInterpolator.interpolate(message, context, locale);
7✔
65
  }
66

67
  /**
68
   * Recursively replaces all message parameters.
69
   * <p>
70
   * The message parameter prefix <code>&#123;</code> and suffix <code>&#125;</code> can
71
   * be escaped using {@code \}, e.g. <code>\&#123;escaped\&#125;</code>.
72
   *
73
   * @param message the message containing the parameters to be replaced
74
   * @param locale the locale to use when resolving replacements
75
   * @return the message with parameters replaced
76
   */
77
  private String replaceParameters(String message, Locale locale) {
78
    return replaceParameters(message, locale, new LinkedHashSet<>(4));
9✔
79
  }
80

81
  private String replaceParameters(String message, Locale locale, Set<String> visitedParameters) {
82
    StringBuilder buf = new StringBuilder(message);
5✔
83
    int parentheses = 0;
2✔
84
    int startIndex = -1;
2✔
85
    int endIndex = -1;
2✔
86
    for (int i = 0; i < buf.length(); i++) {
8✔
87
      if (buf.charAt(i) == ESCAPE) {
5!
88
        i++;
×
89
      }
90
      else if (buf.charAt(i) == PREFIX) {
5✔
91
        if (startIndex == -1) {
3!
92
          startIndex = i;
2✔
93
        }
94
        parentheses++;
2✔
95
      }
96
      else if (buf.charAt(i) == SUFFIX) {
5✔
97
        if (parentheses > 0) {
2!
98
          parentheses--;
1✔
99
        }
100
        endIndex = i;
2✔
101
      }
102
      if (parentheses == 0 && startIndex < endIndex) {
5✔
103
        String parameter = buf.substring(startIndex + 1, endIndex);
7✔
104
        if (!visitedParameters.add(parameter)) {
4!
105
          throw new IllegalArgumentException("Circular reference '{" + String.join(" -> ", visitedParameters)
×
106
                  + " -> " + parameter + "}'");
107
        }
108
        String value = replaceParameter(parameter, locale, visitedParameters);
6✔
109
        if (value != null) {
2!
110
          buf.replace(startIndex, endIndex + 1, value);
×
111
          i = startIndex + value.length() - 1;
×
112
        }
113
        visitedParameters.remove(parameter);
4✔
114
        startIndex = -1;
2✔
115
        endIndex = -1;
2✔
116
      }
117
    }
118
    return buf.toString();
3✔
119
  }
120

121
  @Nullable
122
  private String replaceParameter(String parameter, Locale locale, Set<String> visitedParameters) {
123
    parameter = replaceParameters(parameter, locale, visitedParameters);
6✔
124
    String value = this.messageSource.getMessage(parameter, null, null, locale);
8✔
125
    return (value != null && !isUsingCodeAsDefaultMessage(value, parameter))
3!
126
            ? replaceParameters(value, locale, visitedParameters) : null;
1✔
127
  }
128

129
  private boolean isUsingCodeAsDefaultMessage(String value, String parameter) {
130
    return value.equals(parameter);
×
131
  }
132

133
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc