/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal/CDDLv1_0.txt or * http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal/CDDLv1_0.txt. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2009 Sun Microsystems, Inc. * Portions copyright 2011 ForgeRock AS */ package org.forgerock.i18n; import java.util.Locale; import java.util.ResourceBundle; /** * An opaque handle to a localizable message. */ public final class LocalizableMessageDescriptor { /** * Subclass for creating messages with no arguments. */ public static final class Arg0 extends AbstractLocalizableMessageDescriptor { /** * Cached copy of the message created by this descriptor. We can get away * with this for the zero argument message because it is immutable. */ private final LocalizableMessage message; private final boolean requiresFormat; /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg0(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); final Object[] args = {}; message = new LocalizableMessage(this, args); requiresFormat = containsArgumentLiterals(getFormatString()); } /** * Creates a localizable message. * * @return The localizable message. */ public LocalizableMessage get() { return message; } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return requiresFormat; } /** * Indicates whether or not formatting should be applied to the given format * string. Note that a format string might have literal specifiers (%% or %n * for example) that require formatting but are not replaced by arguments. * * @param s * Candidate for formatting. * @return {@code true} if the format string requires formatting. */ private final boolean containsArgumentLiterals(final String s) { return s.contains("%%") || s.contains("%n"); // match Formatter literals } } /** * Subclass for creating messages with one argument. * * @param * The type of the first message argument. */ public static final class Arg1 extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg1(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param a1 * A message argument. */ public LocalizableMessage get(final T1 a1) { final Object[] args = { a1 }; return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } /** * Subclass for creating messages with two arguments. * * @param * The type of the first message argument. * @param * The type of the second message argument. */ public static final class Arg2 extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg2(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param a1 * A message argument. * @param a2 * A message argument. */ public LocalizableMessage get(final T1 a1, final T2 a2) { final Object[] args = { a1, a2 }; return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } /** * Subclass for creating messages with three arguments. * * @param * The type of the first message argument. * @param * The type of the second message argument. * @param * The type of the third message argument. */ public static final class Arg3 extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg3(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param a1 * A message argument. * @param a2 * A message argument. * @param a3 * A message argument. */ public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3) { final Object[] args = { a1, a2, a3 }; return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } /** * Subclass for creating messages with four arguments. * * @param * The type of the first message argument. * @param * The type of the second message argument. * @param * The type of the third message argument. * @param * The type of the fourth message argument. */ public static final class Arg4 extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg4(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param a1 * A message argument. * @param a2 * A message argument. * @param a3 * A message argument. * @param a4 * A message argument. */ public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3, final T4 a4) { final Object[] args = { a1, a2, a3, a4 }; return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } /** * Subclass for creating messages with five arguments. * * @param * The type of the first message argument. * @param * The type of the second message argument. * @param * The type of the third message argument. * @param * The type of the fourth message argument. * @param * The type of the fifth message argument. */ public static final class Arg5 extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg5(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param a1 * A message argument. * @param a2 * A message argument. * @param a3 * A message argument. * @param a4 * A message argument. * @param a5 * A message argument. */ public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3, final T4 a4, final T5 a5) { final Object[] args = { a1, a2, a3, a4, a5 }; return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } /** * Subclass for creating messages with six arguments. * * @param * The type of the first message argument. * @param * The type of the second message argument. * @param * The type of the third message argument. * @param * The type of the fourth message argument. * @param * The type of the fifth message argument. * @param * The type of the sixth message argument. */ public static final class Arg6 extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg6(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param a1 * A message argument. * @param a2 * A message argument. * @param a3 * A message argument. * @param a4 * A message argument. * @param a5 * A message argument. * @param a6 * A message argument. */ public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3, final T4 a4, final T5 a5, final T6 a6) { final Object[] args = { a1, a2, a3, a4, a5, a6 }; return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } /** * Subclass for creating messages with seven arguments. * * @param * The type of the first message argument. * @param * The type of the second message argument. * @param * The type of the third message argument. * @param * The type of the fourth message argument. * @param * The type of the fifth message argument. * @param * The type of the sixth message argument. * @param * The type of the seventh message argument. */ public static final class Arg7 extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg7(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param a1 * A message argument. * @param a2 * A message argument. * @param a3 * A message argument. * @param a4 * A message argument. * @param a5 * A message argument. * @param a6 * A message argument. * @param a7 * A message argument. */ public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3, final T4 a4, final T5 a5, final T6 a6, final T7 a7) { final Object[] args = { a1, a2, a3, a4, a5, a6, a7 }; return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } /** * Subclass for creating messages with eight arguments. * * @param * The type of the first message argument. * @param * The type of the second message argument. * @param * The type of the third message argument. * @param * The type of the fourth message argument. * @param * The type of the fifth message argument. * @param * The type of the sixth message argument. * @param * The type of the seventh message argument. * @param * The type of the eighth message argument. */ public static final class Arg8 extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg8(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param a1 * A message argument. * @param a2 * A message argument. * @param a3 * A message argument. * @param a4 * A message argument. * @param a5 * A message argument. * @param a6 * A message argument. * @param a7 * A message argument. * @param a8 * A message argument. */ public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3, final T4 a4, final T5 a5, final T6 a6, final T7 a7, final T8 a8) { final Object[] args = { a1, a2, a3, a4, a5, a6, a7, a8 }; return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } /** * Subclass for creating messages with nine arguments. * * @param * The type of the first message argument. * @param * The type of the second message argument. * @param * The type of the third message argument. * @param * The type of the fourth message argument. * @param * The type of the fifth message argument. * @param * The type of the sixth message argument. * @param * The type of the seventh message argument. * @param * The type of the eighth message argument. * @param * The type of the ninth message argument. */ public static final class Arg9 extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public Arg9(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param a1 * A message argument. * @param a2 * A message argument. * @param a3 * A message argument. * @param a4 * A message argument. * @param a5 * A message argument. * @param a6 * A message argument. * @param a7 * A message argument. * @param a8 * A message argument. * @param a9 * A message argument. */ public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3, final T4 a4, final T5 a5, final T6 a6, final T7 a7, final T8 a8, final T9 a9) { final Object[] args = { a1, a2, a3, a4, a5, a6, a7, a8, a9 }; return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } /** * Subclass for creating messages with an any number of arguments. In general * this class should be used when a message needs to be defined with more * arguments that can be handled with the current number of subclasses */ public static final class ArgN extends AbstractLocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ public ArgN(final Class sourceClass, final String resourceName, final String key, final int ordinal) { super(sourceClass, resourceName, key, ordinal); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param args * The message arguments. */ public LocalizableMessage get(final Object... args) { return new LocalizableMessage(this, args); } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return true; } } // Base class for all message descriptors. static abstract class AbstractLocalizableMessageDescriptor { // Container for caching the last locale specific format string. private static final class CachedFormatString { private final Locale locale; private final String formatString; private CachedFormatString(final Locale locale, final String formatString) { this.locale = locale; this.formatString = formatString; } } // Used for accessing format string from the resource bundle. private final String key; /* * The class in which this descriptor is defined. This class will be used to * obtain the ClassLoader for retrieving the ResourceBundle. The class may * also be retrieved in order to uniquely identify the source of a message, * for example using getClass().getPackage().getName(). */ private final Class sourceClass; /* * The name of the resource bundle containing the localizable message. */ private final String resourceName; /* * The ordinal associated with this descriptor or -1 if undefined. A message * can be uniquely identified by its ordinal and class. */ private final int ordinal; // It's ok if there are race conditions. private CachedFormatString cachedFormatString = null; /** * Creates a parameterized message descriptor. * * @param sourceClass * The class in which this descriptor is defined. This class will * be used to obtain the {@code ClassLoader} for retrieving the * {@code ResourceBundle}. The class may also be retrieved in order * to uniquely identify the source of a message, for example using * {@code getClass().getPackage().getName()}. * @param resourceName * The name of the resource bundle containing the localizable * message. * @param key * The resource bundle property key. * @param ordinal * The ordinal associated with this descriptor or {@code -1} if * undefined. A message can be uniquely identified by its ordinal * and class. */ private AbstractLocalizableMessageDescriptor( final Class sourceClass, final String resourceName, final String key, final int ordinal) { this.sourceClass = sourceClass; this.resourceName = resourceName; this.key = key; this.ordinal = ordinal; } /** * Returns the format string which should be used when creating the string * representation of this message using the default locale. * * @return The format string. */ final String getFormatString() { return getFormatString(Locale.getDefault()); } /** * Returns the format string which should be used when creating the string * representation of this message using the specified locale. * * @param locale * The locale. * @return The format string. * @throws NullPointerException * If {@code locale} was {@code null}. */ String getFormatString(final Locale locale) throws NullPointerException { if (locale == null) { throw new NullPointerException("locale was null"); } // Fast path. final CachedFormatString cfs = cachedFormatString; if (cfs != null && cfs.locale == locale) { return cfs.formatString; } // There's a potential race condition here but it's benign - we'll // just do a bit more work than needed. final ResourceBundle bundle = getBundle(locale); final String formatString = bundle.getString(key); cachedFormatString = new CachedFormatString(locale, formatString); return formatString; } /** * Returns the ordinal associated with this message, or {@code -1} if * undefined. A message can be uniquely identified by its resource name and * ordinal. *

* This may be useful when an application wishes to identify the source of a * message. For example, a logging implementation could log the resource * name in addition to the ordinal in order to unambiguously identify a * message in a locale independent way. * * @return The ordinal associated with this descriptor, or {@code -1} if * undefined. */ public final int ordinal() { return ordinal; } /** * Indicates whether or not this descriptor format string should be * processed by {@code Formatter} during string rendering. * * @return {@code true} if a {@code Formatter} should be used, otherwise * {@code false}. */ abstract boolean requiresFormatter(); /** * Returns the name of the resource in which this message is defined. A * message can be uniquely identified by its resource name and ordinal. *

* This may be useful when an application wishes to identify the source of a * message. For example, a logging implementation could log the resource * name in addition to the ordinal in order to unambiguously identify a * message in a locale independent way. *

* The resource name may be used for obtaining named loggers, e.g. using * SLF4J's {@code org.slf4j.LoggerFactory#getLogger(String name)}. * * @return The name of the resource in which this message is defined, or * {@code null} if this message is a raw message and its source is * undefined. */ public final String resourceName() { return resourceName; } private ResourceBundle getBundle(Locale locale) { if (locale == null) { locale = Locale.getDefault(); } return ResourceBundle.getBundle(resourceName, locale, sourceClass.getClassLoader()); } } /** * A descriptor for creating a raw message from a {@code String}. In general * this descriptor should NOT be used internally. */ static final class Raw extends AbstractLocalizableMessageDescriptor { private final String formatString; private final boolean requiresFormatter; /** * Creates a parameterized instance. * * @param formatString * The format string. */ Raw(final CharSequence formatString) { super(Void.class, null, null, -1); this.formatString = formatString.toString(); this.requiresFormatter = this.formatString.matches(".*%.*"); } /** * Creates a message with arguments that will replace format specifiers in * the associated format string when the message is rendered to string * representation. * * @return The localizable message containing the provided arguments. * @param args * The message arguments. */ LocalizableMessage get(final Object... args) { return new LocalizableMessage(this, args); } /** * Overridden in order to bypass the resource bundle plumbing and return the * format string directly. */ @Override String getFormatString(final Locale locale) { return this.formatString; } /** * {@inheritDoc} */ @Override boolean requiresFormatter() { return this.requiresFormatter; } } /** * Cached zero arg raw message descriptor. */ static final LocalizableMessageDescriptor.Raw RAW0 = new LocalizableMessageDescriptor.Raw("%s"); // Prevent instantiation. private LocalizableMessageDescriptor() { // Do nothing. } }