/* * 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(CharSequence formatString, Object... args)
{
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(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(LocalizableMessageDescriptor descriptor, 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(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(Locale locale, 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(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}.
*/
public boolean equals(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(Formatter formatter, int flags, int width,
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.
*/
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(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(int start, 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(Locale locale, int start, 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.
*/
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(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;
}
}