/* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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. */ package com.sun.opends.sdk.util; import java.util.Locale; import java.util.ResourceBundle; import org.opends.sdk.LocalizableMessage; /** * An opaque handle to a localizable message. */ public abstract class LocalizableMessageDescriptor { /** * Subclass for creating messages with no arguments. */ public static final class Arg0 extends LocalizableMessageDescriptor { /** * 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 rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg0(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); message = newMessage(this); requiresFormat = containsArgumentLiterals(getFormatString()); } /** * Creates a message. * * @return LocalizableMessage object */ public LocalizableMessage get() { return message; } /** * {@inheritDoc} */ public boolean requiresFormatter() { return requiresFormat; } } /** * Subclass for creating messages with one argument. * * @param * The type of the first message argument. */ public static final class Arg1 extends LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg1(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param a1 * message argument */ public LocalizableMessage get(T1 a1) { return newMessage(this, a1); } /** * {@inheritDoc} */ public 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 LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg2(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param a1 * message argument * @param a2 * message argument */ public LocalizableMessage get(T1 a1, T2 a2) { return newMessage(this, a1, a2); } /** * {@inheritDoc} */ public 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 LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg3(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param a1 * message argument * @param a2 * message argument * @param a3 * message argument */ public LocalizableMessage get(T1 a1, T2 a2, T3 a3) { return newMessage(this, a1, a2, a3); } /** * {@inheritDoc} */ public 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 LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg4(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param a1 * message argument * @param a2 * message argument * @param a3 * message argument * @param a4 * message argument */ public LocalizableMessage get(T1 a1, T2 a2, T3 a3, T4 a4) { return newMessage(this, a1, a2, a3, a4); } /** * {@inheritDoc} */ public 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 LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg5(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param a1 * message argument * @param a2 * message argument * @param a3 * message argument * @param a4 * message argument * @param a5 * message argument */ public LocalizableMessage get(T1 a1, T2 a2, T3 a3, T4 a4, T5 a5) { return newMessage(this, a1, a2, a3, a4, a5); } /** * {@inheritDoc} */ public 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 LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg6(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param a1 * message argument * @param a2 * message argument * @param a3 * message argument * @param a4 * message argument * @param a5 * message argument * @param a6 * message argument */ public LocalizableMessage get(T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6) { return newMessage(this, a1, a2, a3, a4, a5, a6); } /** * {@inheritDoc} */ public 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 LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg7(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param a1 * message argument * @param a2 * message argument * @param a3 * message argument * @param a4 * message argument * @param a5 * message argument * @param a6 * message argument * @param a7 * message argument */ public LocalizableMessage get(T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6, T7 a7) { return newMessage(this, a1, a2, a3, a4, a5, a6, a7); } /** * {@inheritDoc} */ public 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 LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg8(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param a1 * message argument * @param a2 * message argument * @param a3 * message argument * @param a4 * message argument * @param a5 * message argument * @param a6 * message argument * @param a7 * message argument * @param a8 * message argument */ public LocalizableMessage get(T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6, T7 a7, T8 a8) { return newMessage(this, a1, a2, a3, a4, a5, a6, a7, a8); } /** * {@inheritDoc} */ public 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 LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public Arg9(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param a1 * message argument * @param a2 * message argument * @param a3 * message argument * @param a4 * message argument * @param a5 * message argument * @param a6 * message argument * @param a7 * message argument * @param a8 * message argument * @param a9 * message argument */ public LocalizableMessage get(T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6, T7 a7, T8 a8, T9 a9) { return newMessage(this, a1, a2, a3, a4, a5, a6, a7, a8, a9); } /** * {@inheritDoc} */ public 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 LocalizableMessageDescriptor { /** * Creates a parameterized instance. * * @param rbBase * base of the backing resource bundle * @param key * for accessing the format string from the resource bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ public ArgN(String rbBase, String key, ClassLoader classLoader) { super(rbBase, key, classLoader); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param args * message arguments */ public LocalizableMessage get(Object... args) { return newMessage(this, args); } /** * {@inheritDoc} */ public boolean requiresFormatter() { return true; } } /** * A descriptor for creating a raw message from a String. * In general this descriptor should NOT be used internally. OpenDS * plugins may want to use the mechanism to create messages without * storing their strings in resource bundles. */ public static final class Raw extends LocalizableMessageDescriptor { private final String formatString; private final boolean requiresFormatter; /** * Creates a parameterized instance. * * @param formatString * for created messages */ public Raw(CharSequence formatString) { super(null, null, null); this.formatString = formatString != null ? formatString .toString() : ""; this.requiresFormatter = this.formatString.matches(".*%.*"); } /** * Creates a message with arguments that will replace format * specifiers in the assocated format string when the message is * rendered to string representation. * * @return LocalizableMessage object * @param args * message arguments */ public LocalizableMessage get(Object... args) { return newMessage(this, args); } /** * Overridden in order to bypass the resource bundle plumbing and * return the format string directly. * * @param locale * ignored * @return format string */ public String getFormatString(Locale locale) { return this.formatString; } /** * {@inheritDoc} */ public boolean requiresFormatter() { return this.requiresFormatter; } } // Container for caching the last locale specific format string. private static final class CachedFormatString { private final Locale locale; private final String formatString; private CachedFormatString(Locale locale, String formatString) { this.locale = locale; this.formatString = formatString; } } /** * Factory interface for creating messages. Only LocalizableMessage should * implement this. */ public static interface MessageFactory { /** * Creates a new parameterized message instance. * * @param descriptor * The message descriptor. * @param args * The message parameters. * @return The new message. */ LocalizableMessage newMessage(LocalizableMessageDescriptor descriptor, Object... args); } /** * We use a factory for creating LocalizableMessage objects in order to avoid * exposing this class in the public API. */ public static MessageFactory MESSAGE_FACTORY; // Force MESSAGE_FACTORY to be set. static { try { Class.forName("org.opends.sdk.LocalizableMessage"); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } /** * 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 boolean where true indicates that the format string * requires formatting */ private static final boolean containsArgumentLiterals(String s) { return s.matches(".*%[n|%].*"); // match Formatter literals } private static LocalizableMessage newMessage(LocalizableMessageDescriptor descriptor, Object... args) { return MESSAGE_FACTORY.newMessage(descriptor, args); } // String for accessing backing resource bundle. private final String rbBase; // Used for accessing format string from the resource bundle. private final String key; /* * The class loader to be used to retrieve the ResourceBundle. If null * the default class loader will be used. */ private final ClassLoader classLoader; // It's ok if there are race conditions. private CachedFormatString cachedFormatString = null; /** * Creates a parameterized message descriptor. * * @param rbBase * string for accessing the underlying message bundle * @param key * for accessing the format string from the message bundle * @param classLoader * the class loader to be used to get the ResourceBundle */ private LocalizableMessageDescriptor(String rbBase, String key, ClassLoader classLoader) { this.rbBase = rbBase; this.key = key; this.classLoader = classLoader; } /** * 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}. */ public String getFormatString(Locale locale) throws NullPointerException { Validator.ensureNotNull(locale); // 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; } /** * 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}. */ public abstract boolean requiresFormatter(); private ResourceBundle getBundle(Locale locale) { if (locale == null) { locale = Locale.getDefault(); } if (classLoader == null) { return ResourceBundle.getBundle(this.rbBase, locale); } else { return ResourceBundle.getBundle(this.rbBase, locale, classLoader); } } /** * 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()); } }