001/* 002 * Copyright 2002-2017 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.util.ObjectUtils; 028 029/** 030 * Base class for message source implementations, providing support infrastructure 031 * such as {@link java.text.MessageFormat} handling but not implementing concrete 032 * methods defined in the {@link org.springframework.context.MessageSource}. 033 * 034 * <p>{@link AbstractMessageSource} derives from this class, providing concrete 035 * {@code getMessage} implementations that delegate to a central template 036 * method for message code resolution. 037 * 038 * @author Juergen Hoeller 039 * @since 2.5.5 040 */ 041public abstract class MessageSourceSupport { 042 043 private static final MessageFormat INVALID_MESSAGE_FORMAT = new MessageFormat(""); 044 045 /** Logger available to subclasses */ 046 protected final Log logger = LogFactory.getLog(getClass()); 047 048 private boolean alwaysUseMessageFormat = false; 049 050 /** 051 * Cache to hold already generated MessageFormats per message. 052 * Used for passed-in default messages. MessageFormats for resolved 053 * codes are cached on a specific basis in subclasses. 054 */ 055 private final Map<String, Map<Locale, MessageFormat>> messageFormatsPerMessage = 056 new HashMap<String, Map<Locale, MessageFormat>>(); 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, 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, Object[] args, Locale locale) { 115 if (msg == null || (!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<Locale, MessageFormat>(); 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 != null ? 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(Object[] args, Locale locale) { 171 return args; 172 } 173 174}