• 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

88.24
today-context/src/main/java/infra/context/support/MessageSourceSupport.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.context.support;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.text.MessageFormat;
23
import java.util.Locale;
24
import java.util.Map;
25
import java.util.concurrent.ConcurrentHashMap;
26

27
import infra.logging.Logger;
28
import infra.logging.LoggerFactory;
29
import infra.util.ObjectUtils;
30

31
/**
32
 * Base class for message source implementations, providing support infrastructure
33
 * such as {@link java.text.MessageFormat} handling but not implementing concrete
34
 * methods defined in the {@link infra.context.MessageSource}.
35
 *
36
 * <p>{@link AbstractMessageSource} derives from this class, providing concrete
37
 * {@code getMessage} implementations that delegate to a central template
38
 * method for message code resolution.
39
 *
40
 * @author Juergen Hoeller
41
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
42
 * @since 4.0
43
 */
44
public abstract class MessageSourceSupport {
2✔
45

46
  private static final MessageFormat INVALID_MESSAGE_FORMAT = new MessageFormat("");
6✔
47

48
  /** Logger available to subclasses. */
49
  protected final Logger logger = LoggerFactory.getLogger(getClass());
5✔
50

51
  private boolean alwaysUseMessageFormat = false;
3✔
52

53
  /**
54
   * Cache to hold already generated MessageFormats per message.
55
   * Used for passed-in default messages. MessageFormats for resolved
56
   * codes are cached on a specific basis in subclasses.
57
   */
58
  private final ConcurrentHashMap<String, Map<Locale, MessageFormat>> messageFormatsPerMessage = new ConcurrentHashMap<>();
6✔
59

60
  /**
61
   * Set whether to always apply the {@code MessageFormat} rules, parsing even
62
   * messages without arguments.
63
   * <p>Default is {@code false}: Messages without arguments are by default
64
   * returned as-is, without parsing them through {@code MessageFormat}.
65
   * Set this to {@code true} to enforce {@code MessageFormat} for all messages,
66
   * expecting all message texts to be written with {@code MessageFormat} escaping.
67
   * <p>For example, {@code MessageFormat} expects a single quote to be escaped
68
   * as two adjacent single quotes ({@code "''"}). If your message texts are all
69
   * written with such escaping, even when not defining argument placeholders,
70
   * you need to set this flag to {@code true}. Otherwise, only message texts
71
   * with actual arguments are supposed to be written with {@code MessageFormat}
72
   * escaping.
73
   *
74
   * @see java.text.MessageFormat
75
   */
76
  public void setAlwaysUseMessageFormat(boolean alwaysUseMessageFormat) {
77
    this.alwaysUseMessageFormat = alwaysUseMessageFormat;
3✔
78
  }
1✔
79

80
  /**
81
   * Return whether to always apply the {@code MessageFormat} rules, parsing even
82
   * messages without arguments.
83
   */
84
  protected boolean isAlwaysUseMessageFormat() {
85
    return this.alwaysUseMessageFormat;
3✔
86
  }
87

88
  /**
89
   * Render the given default message String. The default message is
90
   * passed in as specified by the caller and can be rendered into
91
   * a fully formatted default message shown to the user.
92
   * <p>The default implementation passes the String to {@code formatMessage},
93
   * resolving any argument placeholders found in them. Subclasses may override
94
   * this method to plug in custom processing of default messages.
95
   *
96
   * @param defaultMessage the passed-in default message String
97
   * @param args array of arguments that will be filled in for params within
98
   * the message, or {@code null} if none.
99
   * @param locale the Locale used for formatting
100
   * @return the rendered default message (with resolved arguments)
101
   * @see #formatMessage(String, Object[], java.util.Locale)
102
   */
103
  protected String renderDefaultMessage(String defaultMessage, Object @Nullable [] args, @Nullable Locale locale) {
104
    return formatMessage(defaultMessage, args, locale);
6✔
105
  }
106

107
  /**
108
   * Format the given message String, using cached MessageFormats.
109
   * By default invoked for passed-in default messages, to resolve
110
   * any argument placeholders found in them.
111
   *
112
   * @param msg the message to format
113
   * @param args array of arguments that will be filled in for params within
114
   * the message, or {@code null} if none
115
   * @param locale the Locale used for formatting
116
   * @return the formatted message (with resolved arguments)
117
   */
118
  protected String formatMessage(String msg, Object @Nullable [] args, @Nullable Locale locale) {
119
    if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
6✔
120
      return msg;
2✔
121
    }
122
    var messageFormatsPerLocale = messageFormatsPerMessage.computeIfAbsent(msg, key -> new ConcurrentHashMap<>());
11✔
123
    MessageFormat messageFormat = messageFormatsPerLocale.computeIfAbsent(locale, key -> {
9✔
124
      try {
125
        return createMessageFormat(msg, locale);
5✔
126
      }
127
      catch (IllegalArgumentException ex) {
1✔
128
        // Invalid message format - probably not intended for formatting,
129
        // rather using a message structure with no arguments involved...
130
        if (isAlwaysUseMessageFormat()) {
3!
131
          throw ex;
2✔
132
        }
133
        // Silently proceed with raw message if format not enforced...
134
        return INVALID_MESSAGE_FORMAT;
×
135
      }
136
    });
137
    if (messageFormat == INVALID_MESSAGE_FORMAT) {
3!
138
      return msg;
×
139
    }
140
    synchronized(messageFormat) {
4✔
141
      return messageFormat.format(resolveArguments(args, locale));
9✔
142
    }
143
  }
144

145
  /**
146
   * Create a {@code MessageFormat} for the given message and Locale.
147
   *
148
   * @param msg the message to create a {@code MessageFormat} for
149
   * @param locale the Locale to create a {@code MessageFormat} for
150
   * @return the {@code MessageFormat} instance
151
   */
152
  protected MessageFormat createMessageFormat(String msg, @Nullable Locale locale) {
153
    return new MessageFormat(msg, locale);
6✔
154
  }
155

156
  /**
157
   * Template method for resolving argument objects.
158
   * <p>The default implementation simply returns the given argument array as-is.
159
   * Can be overridden in subclasses in order to resolve special argument types.
160
   *
161
   * @param args the original argument array
162
   * @param locale the Locale to resolve against
163
   * @return the resolved argument array
164
   */
165
  protected Object[] resolveArguments(Object @Nullable [] args, @Nullable Locale locale) {
166
    return (args != null ? args : new Object[0]);
7✔
167
  }
168

169
}
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