001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.context.support; 018 019import java.text.MessageFormat; 020import java.util.HashMap; 021import java.util.Locale; 022import java.util.Map; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.lang.Nullable; 028import org.springframework.util.ObjectUtils; 029 030/** 031 * Base class for message source implementations, providing support infrastructure 032 * such as {@link java.text.MessageFormat} handling but not implementing concrete 033 * methods defined in the {@link org.springframework.context.MessageSource}. 034 * 035 * <p>{@link AbstractMessageSource} derives from this class, providing concrete 036 * {@code getMessage} implementations that delegate to a central template 037 * method for message code resolution. 038 * 039 * @author Juergen Hoeller 040 * @since 2.5.5 041 */ 042public abstract class MessageSourceSupport { 043 044 private static final MessageFormat INVALID_MESSAGE_FORMAT = new MessageFormat(""); 045 046 /** Logger available to subclasses. */ 047 protected final Log logger = LogFactory.getLog(getClass()); 048 049 private boolean alwaysUseMessageFormat = false; 050 051 /** 052 * Cache to hold already generated MessageFormats per message. 053 * Used for passed-in default messages. MessageFormats for resolved 054 * codes are cached on a specific basis in subclasses. 055 */ 056 private final Map<String, Map<Locale, MessageFormat>> messageFormatsPerMessage = new HashMap<>(); 057 058 059 /** 060 * Set whether to always apply the {@code MessageFormat} rules, 061 * parsing even messages without arguments. 062 * <p>Default is "false": Messages without arguments are by default 063 * returned as-is, without parsing them through MessageFormat. 064 * Set this to "true" to enforce MessageFormat for all messages, 065 * expecting all message texts to be written with MessageFormat escaping. 066 * <p>For example, MessageFormat expects a single quote to be escaped 067 * as "''". If your message texts are all written with such escaping, 068 * even when not defining argument placeholders, you need to set this 069 * flag to "true". Else, only message texts with actual arguments 070 * are supposed to be written with MessageFormat escaping. 071 * @see java.text.MessageFormat 072 */ 073 public void setAlwaysUseMessageFormat(boolean alwaysUseMessageFormat) { 074 this.alwaysUseMessageFormat = alwaysUseMessageFormat; 075 } 076 077 /** 078 * Return whether to always apply the MessageFormat rules, parsing even 079 * messages without arguments. 080 */ 081 protected boolean isAlwaysUseMessageFormat() { 082 return this.alwaysUseMessageFormat; 083 } 084 085 086 /** 087 * Render the given default message String. The default message is 088 * passed in as specified by the caller and can be rendered into 089 * a fully formatted default message shown to the user. 090 * <p>The default implementation passes the String to {@code formatMessage}, 091 * resolving any argument placeholders found in them. Subclasses may override 092 * this method to plug in custom processing of default messages. 093 * @param defaultMessage the passed-in default message String 094 * @param args array of arguments that will be filled in for params within 095 * the message, or {@code null} if none. 096 * @param locale the Locale used for formatting 097 * @return the rendered default message (with resolved arguments) 098 * @see #formatMessage(String, Object[], java.util.Locale) 099 */ 100 protected String renderDefaultMessage(String defaultMessage, @Nullable Object[] args, Locale locale) { 101 return formatMessage(defaultMessage, args, locale); 102 } 103 104 /** 105 * Format the given message String, using cached MessageFormats. 106 * By default invoked for passed-in default messages, to resolve 107 * any argument placeholders found in them. 108 * @param msg the message to format 109 * @param args array of arguments that will be filled in for params within 110 * the message, or {@code null} if none 111 * @param locale the Locale used for formatting 112 * @return the formatted message (with resolved arguments) 113 */ 114 protected String formatMessage(String msg, @Nullable Object[] args, Locale locale) { 115 if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) { 116 return msg; 117 } 118 MessageFormat messageFormat = null; 119 synchronized (this.messageFormatsPerMessage) { 120 Map<Locale, MessageFormat> messageFormatsPerLocale = this.messageFormatsPerMessage.get(msg); 121 if (messageFormatsPerLocale != null) { 122 messageFormat = messageFormatsPerLocale.get(locale); 123 } 124 else { 125 messageFormatsPerLocale = new HashMap<>(); 126 this.messageFormatsPerMessage.put(msg, messageFormatsPerLocale); 127 } 128 if (messageFormat == null) { 129 try { 130 messageFormat = createMessageFormat(msg, locale); 131 } 132 catch (IllegalArgumentException ex) { 133 // Invalid message format - probably not intended for formatting, 134 // rather using a message structure with no arguments involved... 135 if (isAlwaysUseMessageFormat()) { 136 throw ex; 137 } 138 // Silently proceed with raw message if format not enforced... 139 messageFormat = INVALID_MESSAGE_FORMAT; 140 } 141 messageFormatsPerLocale.put(locale, messageFormat); 142 } 143 } 144 if (messageFormat == INVALID_MESSAGE_FORMAT) { 145 return msg; 146 } 147 synchronized (messageFormat) { 148 return messageFormat.format(resolveArguments(args, locale)); 149 } 150 } 151 152 /** 153 * Create a MessageFormat for the given message and Locale. 154 * @param msg the message to create a MessageFormat for 155 * @param locale the Locale to create a MessageFormat for 156 * @return the MessageFormat instance 157 */ 158 protected MessageFormat createMessageFormat(String msg, Locale locale) { 159 return new MessageFormat(msg, locale); 160 } 161 162 /** 163 * Template method for resolving argument objects. 164 * <p>The default implementation simply returns the given argument array as-is. 165 * Can be overridden in subclasses in order to resolve special argument types. 166 * @param args the original argument array 167 * @param locale the Locale to resolve against 168 * @return the resolved argument array 169 */ 170 protected Object[] resolveArguments(@Nullable Object[] args, Locale locale) { 171 return (args != null ? args : new Object[0]); 172 } 173 174}