package org.dayatang.i18n.impl; import org.dayatang.i18n.HierarchicalI18nService; import org.dayatang.i18n.I18nService; import org.dayatang.i18n.NoSuchMessageException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Locale; public abstract class AbstractI18nService extends I18nServiceSupport implements HierarchicalI18nService { private I18nService parentMessageSource; private boolean useCodeAsDefaultMessage = false; public void setParentMessageSource(I18nService parent) { this.parentMessageSource = parent; } public I18nService getParentMessageSource() { return this.parentMessageSource; } /** * Set whether to use the message code as default message instead of * throwing a NoSuchMessageException. Useful for development and debugging. * Default is "false". * <p> * Note: In case of a MessageSourceResolvable with multiple codes (like a * FieldError) and a MessageSource that has a parent MessageSource, do * <i>not</i> activate "useCodeAsDefaultMessage" in the <i>parent</i>: Else, * you'll get the first code returned as message by the parent, without * attempts to check further codes. * <p> * To be able to work with "useCodeAsDefaultMessage" turned on in the * parent, AbstractMessageSource and AbstractApplicationContext contain * special checks to delegate to the support {@link #getMessageInternal} * method if available. In general, it is recommended to just use * "useCodeAsDefaultMessage" during development and not rely on it in * production in the first place, though. * * @see #getMessage(String, Object[], Locale) * @see org.springframework.validation.FieldError */ public void setUseCodeAsDefaultMessage(boolean useCodeAsDefaultMessage) { this.useCodeAsDefaultMessage = useCodeAsDefaultMessage; } /** * Return whether to use the message code as default message instead of * throwing a NoSuchMessageException. Useful for development and debugging. * Default is "false". * <p> * Alternatively, consider overriding the {@link #getDefaultMessage} method * to return a custom fallback message for an unresolvable code. * * @see #getDefaultMessage(String) */ protected boolean isUseCodeAsDefaultMessage() { return this.useCodeAsDefaultMessage; } public final String getMessage(String code, Object[] args, String defaultMessage, Locale locale) { String msg = getMessageInternal(code, args, locale); if (msg != null) { return msg; } if (defaultMessage == null) { String fallback = getDefaultMessage(code); if (fallback != null) { return fallback; } } return renderDefaultMessage(defaultMessage, args, locale); } public final String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException { String msg = getMessageInternal(code, args, locale); if (msg != null) { return msg; } String fallback = getDefaultMessage(code); if (fallback != null) { return fallback; } throw new NoSuchMessageException(code, locale); } /** * Resolve the given code and arguments as message in the given Locale, * returning <code>null</code> if not found. Does <i>not</i> fall back to * the code as default message. Invoked by <code>getMessage</code> methods. * * @param code * the code to lookup up, such as 'calculator.noRateSet' * @param args * array of arguments that will be filled in for params within * the message * @param locale * the Locale in which to do the lookup * @return the resolved message, or <code>null</code> if not found * @see #getMessage(String, Object[], String, Locale) * @see #getMessage(String, Object[], Locale) * @see #getMessage(MessageSourceResolvable, Locale) * @see #setUseCodeAsDefaultMessage */ protected String getMessageInternal(String code, Object[] args, Locale theLocale) { if (code == null) { return null; } Locale locale = theLocale == null ? Locale.getDefault() : theLocale; Object[] argsToUse = args; if (!isAlwaysUseMessageFormat() && (argsToUse == null || argsToUse.length == 0)) { // Optimized resolution: no arguments to apply, // therefore no MessageFormat needs to be involved. // Note that the default implementation still uses MessageFormat; // this can be overridden in specific subclasses. String message = resolveCodeWithoutArguments(code, locale); if (message != null) { return message; } } else { // Resolve arguments eagerly, for the case where the message // is defined in a parent MessageSource but resolvable arguments // are defined in the child MessageSource. argsToUse = resolveArguments(args, locale); MessageFormat messageFormat = resolveCode(code, locale); if (messageFormat != null) { synchronized (messageFormat) { return messageFormat.format(argsToUse); } } } // Not found -> check parent, if any. return getMessageFromParent(code, argsToUse, locale); } /** * Try to retrieve the given message from the parent MessageSource, if any. * * @param code * the code to lookup up, such as 'calculator.noRateSet' * @param args * array of arguments that will be filled in for params within * the message * @param locale * the Locale in which to do the lookup * @return the resolved message, or <code>null</code> if not found * @see #getParentMessageSource() */ protected String getMessageFromParent(String code, Object[] args, Locale locale) { I18nService parent = getParentMessageSource(); if (parent != null) { if (parent instanceof AbstractI18nService) { // Call support method to avoid getting the default code back // in case of "useCodeAsDefaultMessage" being activated. return ((AbstractI18nService) parent).getMessageInternal( code, args, locale); } else { // Check parent MessageSource, returning null if not found // there. return parent.getMessage(code, args, null, locale); } } // Not found in parent either. return null; } /** * Return a fallback default message for the given code, if any. * <p> * Default is to return the code itself if "useCodeAsDefaultMessage" is * activated, or return no fallback else. In case of no fallback, the caller * will usually receive a NoSuchMessageException from * <code>getMessage</code>. * * @param code * the message code that we couldn't resolve and that we didn't * receive an explicit default message for * @return the default message to use, or <code>null</code> if none * @see #setUseCodeAsDefaultMessage */ protected String getDefaultMessage(String code) { if (isUseCodeAsDefaultMessage()) { return code; } return null; } /** * Render the given default message String. The default message is passed in * as specified by the caller and can be rendered into a fully formatted * default message shown to the user. * <p> * The default implementation passes the String to {@link #formatMessage}, * resolving any argument placeholders found in them. Subclasses may * override this method to plug in custom processing of default messages. * * @param defaultMessage * the passed-in default message String * @param args * array of arguments that will be filled in for params within * the message, or <code>null</code> if none. * @param locale * the Locale used for formatting * @return the rendered default message (with resolved arguments) * @see #formatMessage(String, Object[], java.util.Locale) */ @Override protected String renderDefaultMessage(String defaultMessage, Object[] args, Locale locale) { return formatMessage(defaultMessage, args, locale); } /** * Searches through the given array of objects, finds any * MessageSourceResolvable objects and resolves them. * <p> * Allows for messages to have MessageSourceResolvables as arguments. * * @param args * array of arguments for a message * @param locale * the locale to resolve through * @return an array of arguments with any MessageSourceResolvables resolved */ @Override protected Object[] resolveArguments(Object[] args, Locale locale) { if (args == null) { return new Object[0]; } List<Object> resolvedArgs = new ArrayList<Object>(args.length); for (Object arg : args) { // if (arg instanceof MessageSourceResolvable) { // resolvedArgs.add(getMessage((MessageSourceResolvable) arg, // locale)); // } else { // resolvedArgs.add(arg); // } resolvedArgs.add(arg); } return resolvedArgs.toArray(new Object[resolvedArgs.size()]); } /** * Subclasses can override this method to resolve a message without * arguments in an optimized fashion, i.e. to resolve without involving a * MessageFormat. * <p> * The default implementation <i>does</i> use MessageFormat, through * delegating to the {@link #resolveCode} method. Subclasses are encouraged * to replace this with optimized resolution. * <p> * Unfortunately, <code>java.text.MessageFormat</code> is not implemented in * an efficient fashion. In particular, it does not detect that a message * pattern doesn't contain argument placeholders in the first place. * Therefore, it is advisable to circumvent MessageFormat for messages * without arguments. * * @param code * the code of the message to resolve * @param locale * the Locale to resolve the code for (subclasses are encouraged * to support internationalization) * @return the message String, or <code>null</code> if not found * @see #resolveCode * @see java.text.MessageFormat */ protected String resolveCodeWithoutArguments(String code, Locale locale) { MessageFormat messageFormat = resolveCode(code, locale); if (messageFormat != null) { synchronized (messageFormat) { return messageFormat.format(new Object[0]); } } return null; } /** * Subclasses must implement this method to resolve a message. * <p> * Returns a MessageFormat instance rather than a message String, to allow * for appropriate caching of MessageFormats in subclasses. * <p> * <b>Subclasses are encouraged to provide optimized resolution for messages * without arguments, not involving MessageFormat.</b> See the * {@link #resolveCodeWithoutArguments} javadoc for details. * * @param code * the code of the message to resolve * @param locale * the Locale to resolve the code for (subclasses are encouraged * to support internationalization) * @return the MessageFormat for the message, or <code>null</code> if not * found * @see #resolveCodeWithoutArguments(String, java.util.Locale) */ protected abstract MessageFormat resolveCode(String code, Locale locale); }