/* * 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 org.opends.sdk; import java.util.Formattable; import java.util.Formatter; import java.util.IllegalFormatException; import java.util.Locale; import com.sun.opends.sdk.util.LocalizableMessageDescriptor; import com.sun.opends.sdk.util.Validator; /** * A localizable message whose {@code String} representation can be retrieved in * one or more locales. A message is localized each time it is converted to a * {@code String} using one of its {@link #toString} methods. *
* Localizable messages are particularly useful in situations where a message a * destined for multiple recipients, potentially in different locales. For * example, a server application may record a message in its log file using its * default locale, but also send the same message to the client using the * client's locale (if known). *
* In most cases messages are intended for use in a locale-sensitive manner * although this class defines convenience methods for creating * uninternationalized messages whose {@code String} representation is always * the same regardless of the requested locale. *
* This class implements {@code CharSequence} so that messages can be supplied
* as arguments to other messages. This way messages can be composed of
* fragments of other messages if necessary.
*
* @see LocalizableMessageBuilder
*/
public final class LocalizableMessage implements CharSequence, Formattable,
Comparable
* Note that the types for {@code args} must be consistent with any argument
* specifiers appearing in {@code formatString} according to the rules of
* {@link java.util.Formatter}. A mismatch in type information will cause this
* message to render without argument substitution. Before using this method
* you should be sure that the message you are creating is not locale
* sensitive. If it is locale sensitive consider defining an appropriate
* {@link LocalizableMessageDescriptor}.
*
* @param formatString
* The raw message format string.
* @param args
* The raw message parameters.
* @return An uninternationalized messages whose {@code String} representation
* is always the same regardless of the requested locale.
* @throws NullPointerException
* If {@code formatString} was {@code null}.
*/
public static LocalizableMessage raw(final CharSequence formatString,
final Object... args) throws NullPointerException
{
Validator.ensureNotNull(formatString);
return new LocalizableMessageDescriptor.Raw(formatString).get(args);
}
/**
* Creates a new message whose content is the {@code String} representation of
* the provided {@code Object}.
*
* @param object
* The object to be converted to a message, may be {@code null}.
* @return The new message.
*/
public static LocalizableMessage valueOf(final Object object)
{
if (object instanceof LocalizableMessage)
{
return (LocalizableMessage) object;
}
else if (object instanceof LocalizableMessageBuilder)
{
return ((LocalizableMessageBuilder) object).toMessage();
}
else
{
return new LocalizableMessageDescriptor.Raw(String.valueOf(object)).get();
}
}
/**
* Returns whether we are running post 1.5 on AIX or not.
*
* @return {@code true} if we are running post 1.5 on AIX and {@code false}
* otherwise.
*/
private static boolean isAIXPost5()
{
// TODO: remove this code once the JDK issue referenced in 3077 is
// closed.
boolean isJDK15 = false;
try
{
final String javaRelease = System.getProperty("java.version");
isJDK15 = javaRelease.startsWith("1.5");
}
catch (final Throwable t)
{
System.err.println("Cannot get the java version: " + t);
}
final boolean isAIX = "aix".equalsIgnoreCase(System.getProperty("os.name"));
return !isJDK15 && isAIX;
}
// Descriptor of this message.
private final LocalizableMessageDescriptor descriptor;
// Values used to replace argument specifiers in the format string.
private final Object[] args;
/**
* Creates a new parameterized message instance. See the class header for
* instructions on how to create messages outside of this package.
*
* @param descriptor
* The message descriptor.
* @param args
* The message parameters.
*/
private LocalizableMessage(final LocalizableMessageDescriptor descriptor,
final Object... args)
{
this.descriptor = descriptor;
this.args = args;
}
/**
* Returns the {@code char} value at the specified index of the {@code String}
* representation of this message in the default locale.
*
* @param index
* The index of the {@code char} value to be returned.
* @return The specified {@code char} value.
* @throws IndexOutOfBoundsException
* If the {@code index} argument is negative or not less than
* {@code length()}.
*/
public char charAt(final int index) throws IndexOutOfBoundsException
{
return charAt(Locale.getDefault(), index);
}
/**
* Returns the {@code char} value at the specified index of the {@code String}
* representation of this message in the specified locale.
*
* @param locale
* The locale.
* @param index
* The index of the {@code char} value to be returned.
* @return The specified {@code char} value.
* @throws IndexOutOfBoundsException
* If the {@code index} argument is negative or not less than
* {@code length()}.
* @throws NullPointerException
* If {@code locale} was {@code null}.
*/
public char charAt(final Locale locale, final int index)
throws IndexOutOfBoundsException, NullPointerException
{
return toString(locale).charAt(index);
}
/**
* Compares this message with the specified message for order in the default
* locale. Returns a negative integer, zero, or a positive integer as this
* object is less than, equal to, or greater than the specified object.
*
* @param message
* The message to be compared.
* @return A negative integer, zero, or a positive integer as this object is
* less than, equal to, or greater than the specified object.
*/
public int compareTo(final LocalizableMessage message)
{
return toString().compareTo(message.toString());
}
/**
* Returns {@code true} if the provided object is a message whose {@code
* String} representation is equal to the {@code String} representation of
* this message in the default locale.
*
* @param o
* The object to be compared for equality with this message.
* @return {@code true} if this message is the equal to {@code o}, otherwise
* {@code false}.
*/
@Override
public boolean equals(final Object o)
{
if (this == o)
{
return true;
}
else if (o instanceof LocalizableMessage)
{
final LocalizableMessage message = (LocalizableMessage) o;
return toString().equals(message.toString());
}
else
{
return false;
}
}
/**
* Formats this message using the provided {@link Formatter}.
*
* @param formatter
* The {@link Formatter}.
* @param flags
* The flags modify the output format. The value is interpreted as a
* bitmask. Any combination of the following flags may be set:
* {@link java.util.FormattableFlags#LEFT_JUSTIFY},
* {@link java.util.FormattableFlags#UPPERCASE}, and
* {@link java.util.FormattableFlags#ALTERNATE}. If no flags are set,
* the default formatting of the implementing class will apply.
* @param width
* The minimum number of characters to be written to the output. If
* the length of the converted value is less than the {@code width}
* then the output will be padded by white space until the total
* number of characters equals width. The padding is at the beginning
* by default. If the {@link java.util.FormattableFlags#LEFT_JUSTIFY}
* flag is set then the padding will be at the end. If {@code width}
* is {@code -1} then there is no minimum.
* @param precision
* The maximum number of characters to be written to the output. The
* precision is applied before the width, thus the output will be
* truncated to {@code precision} characters even if the {@code
* width} is greater than the {@code precision}. If {@code precision}
* is {@code -1} then there is no explicit limit on the number of
* characters.
* @throws IllegalFormatException
* If any of the parameters are invalid. For specification of all
* possible formatting errors, see the Details section of the
* formatter class specification.
*/
public void formatTo(final Formatter formatter, final int flags,
final int width, final int precision) throws IllegalFormatException
{
// Ignores flags, width and precision for now.
// see javadoc for Formattable
final Locale l = formatter.locale();
formatter.format(l, descriptor.getFormatString(l), args);
}
/**
* Returns the hash code value for this message calculated using the hash code
* of the {@code String} representation of this message in the default locale.
*
* @return The hash code value for this message.
*/
@Override
public int hashCode()
{
return toString().hashCode();
}
/**
* Returns the length of the {@code String} representation of this message in
* the default locale.
*
* @return The length of the {@code String} representation of this message in
* the default locale.
*/
public int length()
{
return length(Locale.getDefault());
}
/**
* Returns the length of the {@code String} representation of this message in
* the specified locale.
*
* @param locale
* The locale.
* @return The length of the {@code String} representation of this message in
* the specified locale.
* @throws NullPointerException
* If {@code locale} was {@code null}.
*/
public int length(final Locale locale) throws NullPointerException
{
return toString(locale).length();
}
/**
* Returns a new {@code CharSequence} which is a subsequence of the {@code
* String} representation of this message in the default locale. The
* subsequence starts with the {@code char} value at the specified index and
* ends with the {@code char} value at index {@code end - 1} . The length (in
* {@code char}s) of the returned sequence is {@code end - start}, so if
* {@code start == end} then an empty sequence is returned.
*
* @param start
* The start index, inclusive.
* @param end
* The end index, exclusive.
* @return The specified subsequence.
* @throws IndexOutOfBoundsException
* If {@code start} or {@code end} are negative, if {@code end} is
* greater than {@code length()}, or if {@code start} is greater
* than {@code end}.
*/
public CharSequence subSequence(final int start, final int end)
throws IndexOutOfBoundsException
{
return subSequence(Locale.getDefault(), start, end);
}
/**
* Returns a new {@code CharSequence} which is a subsequence of the {@code
* String} representation of this message in the specified locale. The
* subsequence starts with the {@code char} value at the specified index and
* ends with the {@code char} value at index {@code end - 1} . The length (in
* {@code char}s) of the returned sequence is {@code end - start}, so if
* {@code start == end} then an empty sequence is returned.
*
* @param locale
* The locale.
* @param start
* The start index, inclusive.
* @param end
* The end index, exclusive.
* @return The specified subsequence.
* @throws IndexOutOfBoundsException
* If {@code start} or {@code end} are negative, if {@code end} is
* greater than {@code length()}, or if {@code start} is greater
* than {@code end}.
* @throws NullPointerException
* If {@code locale} was {@code null}.
*/
public CharSequence subSequence(final Locale locale, final int start,
final int end) throws IndexOutOfBoundsException, NullPointerException
{
return toString(locale).subSequence(start, end);
}
/**
* Returns the {@code String} representation of this message in the default
* locale.
*
* @return The {@code String} representation of this message.
*/
@Override
public String toString()
{
return toString(Locale.getDefault());
}
/**
* Returns the {@code String} representation of this message in the specified
* locale.
*
* @param locale
* The locale.
* @return The {@code String} representation of this message.
* @throws NullPointerException
* If {@code locale} was {@code null}.
*/
public String toString(final Locale locale) throws NullPointerException
{
String s;
final String fmt = descriptor.getFormatString(locale);
if (descriptor.requiresFormatter())
{
try
{
// TODO: remove this code once the JDK issue referenced in 3077
// is closed.
if (IS_AIX_POST5)
{
// Java 6 in AIX Formatter does not handle properly
// Formattable arguments; this code is a workaround for the
// problem.
boolean changeType = false;
for (final Object o : args)
{
if (o instanceof Formattable)
{
changeType = true;
break;
}
}
if (changeType)
{
final Object[] newArgs = new Object[args.length];
for (int i = 0; i < args.length; i++)
{
if (args[i] instanceof Formattable)
{
newArgs[i] = args[i].toString();
}
else
{
newArgs[i] = args[i];
}
}
s = new Formatter(locale).format(locale, fmt, newArgs).toString();
}
else
{
s = new Formatter(locale).format(locale, fmt, args).toString();
}
}
else
{
s = new Formatter(locale).format(locale, fmt, args).toString();
}
}
catch (final IllegalFormatException e)
{
// This should not happend with any of our internal messages.
// However, this may happen for raw messages that have a
// mismatch between argument specifier type and argument type.
s = fmt;
}
}
else
{
s = fmt;
}
if (s == null)
{
s = "";
}
return s;
}
}