/*
|
* 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 2008-2010 Sun Microsystems, Inc.
|
* Portions copyright 2011 ForgeRock AS
|
*/
|
|
package org.opends.messages;
|
|
import java.util.Locale;
|
import java.util.Formatter;
|
import java.util.Formattable;
|
import java.util.IllegalFormatException;
|
|
/**
|
* Renders sensitive textural strings. In most cases message are intended
|
* to render textural strings in a locale-sensitive manner although this
|
* class defines convenience methods for creating uninternationalized
|
* <code>Message</code> objects that render the same text regardless of
|
* the requested locale.
|
*
|
* This class implements <code>CharSequence</code> 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 org.opends.messages.MessageDescriptor
|
*/
|
@org.opends.server.types.PublicAPI(
|
stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
|
mayInstantiate=true,
|
mayExtend=false,
|
mayInvoke=true)
|
public final class Message implements CharSequence, Formattable,
|
Comparable<Message> {
|
|
/** Represents an empty message string. */
|
public static final Message EMPTY = Message.raw("");
|
|
// Variable used to workaround a bug in AIX Java 1.6
|
// TODO: remove this code once the JDK issue referenced in 3077 is closed.
|
private final boolean isAIX = isAIX();
|
|
/**
|
* Creates an uninternationalized message that will render itself
|
* the same way regardless of the locale requested in
|
* <code>toString(Locale)</code>. The message will have a
|
* category of <code>Category.USER_DEFINED</code> and a severity
|
* of <code>Severity.INFORMATION</code>
|
*
|
* Note that the types for <code>args</code> must be consistent with any
|
* argument specifiers appearing in <code>formatString</code> according
|
* to the rules of 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 locale sensitive. If so you should instead create
|
* a formal message.
|
*
|
* @param formatString of the message or the message itself if not
|
* arguments are necessary
|
* @param args any arguments for the format string
|
* @return a message object that will render the same in all locales;
|
* null if <code>formatString</code> is null
|
*/
|
static public Message raw(CharSequence formatString, Object... args) {
|
return raw(Category.USER_DEFINED, Severity.INFORMATION, formatString, args);
|
}
|
|
/**
|
* Creates an uninternationalized message that will render itself
|
* the same way regardless of the locale requested in
|
* <code>toString(Locale)</code>.
|
*
|
* Note that the types for <code>args</code> must be consistent with any
|
* argument specifiers appearing in <code>formatString</code> according
|
* to the rules of 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 locale sensitive. If so you should instead create
|
* a formal message.
|
*
|
* @param category of this message
|
* @param severity of this message
|
* @param formatString of the message or the message itself if not
|
* arguments are necessary
|
* @param args any arguments for the format string
|
* @return a message object that will render the same in all locales;
|
* null if <code>formatString</code> is null
|
*/
|
static public Message raw(Category category, Severity severity,
|
CharSequence formatString, Object... args) {
|
Message message = null;
|
if (formatString != null) {
|
if (args == null || args.length == 0)
|
{
|
MessageDescriptor.Raw md = new MessageDescriptor.Raw(
|
"%s", category, severity);
|
message = md.get(formatString);
|
}
|
else
|
{
|
MessageDescriptor.Raw md = new MessageDescriptor.Raw(
|
formatString, category, severity);
|
message = md.get(args);
|
}
|
}
|
return message;
|
}
|
|
/**
|
* Creates an uninternationalized message from the string representation
|
* of an object.
|
*
|
* Note that the types for <code>args</code> must be consistent with any
|
* argument specifiers appearing in <code>formatString</code> according
|
* to the rules of java.util.Formatter. A mismatch in type information
|
* will cause this message to render without argument substitution.
|
*
|
* @param object from which the message will be created
|
* @param args for message
|
* @return a message object that will render the same in all locales;
|
* null if <code>object</code> is null
|
*/
|
static public Message fromObject(Object object, Object... args) {
|
return (object != null) ? raw(object.toString(), args) : null;
|
}
|
|
/**
|
* Returns the string representation of the message in the default locale.
|
* @param message to stringify
|
* @return String representation of of <code>message</code> of null if
|
* <code>message</code> is null
|
*/
|
static public String toString(Message message) {
|
return message != null ? message.toString() : null;
|
}
|
|
/** Descriptor of this message. */
|
private final MessageDescriptor descriptor;
|
|
/** Values used to replace argument specifiers in the format string. */
|
private final Object[] args;
|
|
/**
|
* Gets the string representation of this message.
|
* @return String representation of this message
|
*/
|
@Override
|
public String toString() {
|
return toString(Locale.getDefault());
|
}
|
|
/**
|
* Gets the string representation of this message appropriate for
|
* <code>locale</code>.
|
* @param locale for which the string representation
|
* will be returned
|
* @return String representation of this message
|
*/
|
public String toString(Locale locale) {
|
String s;
|
String fmt = descriptor.getFormatString(locale);
|
if (descriptor.requiresFormatter()) {
|
try {
|
// TODO: remove this code once the JDK issue referenced in 3077 is
|
// closed.
|
if (isAIX)
|
{
|
// Java 6 in AIX Formatter does not handle properly Formattable
|
// arguments; this code is a workaround for the problem.
|
boolean changeType = false;
|
for (Object o : args)
|
{
|
if (o instanceof Formattable)
|
{
|
changeType = true;
|
break;
|
}
|
}
|
if (changeType)
|
{
|
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 (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;
|
}
|
|
/**
|
* Gets the descriptor that holds descriptive information
|
* about this message.
|
* @return MessageDescriptor information
|
*/
|
public MessageDescriptor getDescriptor() {
|
return this.descriptor;
|
}
|
|
/**
|
* Returns the length of this message as rendered using the default
|
* locale.
|
*
|
* @return the number of <code>char</code>s in this message
|
*/
|
public int length() {
|
return length(Locale.getDefault());
|
}
|
|
/**
|
* Returns the byte representation of this messages in the default
|
* locale.
|
*
|
* @return bytes for this message
|
*/
|
public byte[] getBytes() {
|
return toString().getBytes();
|
}
|
|
/**
|
* Returns the <code>char</code> value at the specified index of
|
* this message rendered using the default locale.
|
*
|
* @param index the index of the <code>char</code> value to be returned
|
*
|
* @return the specified <code>char</code> value
|
*
|
* @throws IndexOutOfBoundsException
|
* if the <tt>index</tt> argument is negative or not less than
|
* <tt>length()</tt>
|
*/
|
public char charAt(int index) throws IndexOutOfBoundsException {
|
return charAt(Locale.getDefault(), index);
|
}
|
|
/**
|
* Returns a new <code>CharSequence</code> that is a subsequence
|
* of this message rendered using the default locale.
|
* The subsequence starts with the <code>char</code>
|
* value at the specified index and ends with the <code>char</code>
|
* value at index <tt>end - 1</tt>. The length (in <code>char</code>s)
|
* of the returned sequence is <tt>end - start</tt>, so if
|
* <tt>start == end</tt> 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 <tt>start</tt> or <tt>end</tt> are negative,
|
* if <tt>end</tt> is greater than <tt>length()</tt>,
|
* or if <tt>start</tt> is greater than <tt>end</tt>
|
*/
|
public CharSequence subSequence(int start, int end)
|
throws IndexOutOfBoundsException
|
{
|
return subSequence(Locale.getDefault(), start, end);
|
}
|
|
/**
|
* Returns the length of this message as rendered using a specific
|
* locale.
|
*
|
* @param locale for which the rendering of this message will be
|
* used in determining the length
|
* @return the number of <code>char</code>s in this message
|
*/
|
public int length(Locale locale) {
|
return toString(locale).length();
|
}
|
|
/**
|
* Returns the <code>char</code> value at the specified index of
|
* this message rendered using a specific.
|
*
|
* @param locale for which the rendering of this message will be
|
* used in determining the character
|
* @param index the index of the <code>char</code> value to be returned
|
*
|
* @return the specified <code>char</code> value
|
*
|
* @throws IndexOutOfBoundsException
|
* if the <tt>index</tt> argument is negative or not less than
|
* <tt>length()</tt>
|
*/
|
public char charAt(Locale locale, int index)
|
throws IndexOutOfBoundsException
|
{
|
return toString(locale).charAt(index);
|
}
|
|
/**
|
* Returns a new <code>CharSequence</code> that is a subsequence
|
* of this message rendered using a specific locale.
|
* The subsequence starts with the <code>char</code>
|
* value at the specified index and ends with the <code>char</code>
|
* value at index <tt>end - 1</tt>. The length (in <code>char</code>s)
|
* of the returned sequence is <tt>end - start</tt>, so if
|
* <tt>start == end</tt> then an empty sequence is returned.
|
*
|
* @param locale for which the rendering of this message will be
|
* used in determining the character
|
* @param start the start index, inclusive
|
* @param end the end index, exclusive
|
*
|
* @return the specified subsequence
|
*
|
* @throws IndexOutOfBoundsException
|
* if <tt>start</tt> or <tt>end</tt> are negative,
|
* if <tt>end</tt> is greater than <tt>length()</tt>,
|
* or if <tt>start</tt> is greater than <tt>end</tt>
|
*/
|
public CharSequence subSequence(Locale locale, int start, int end)
|
throws IndexOutOfBoundsException
|
{
|
return toString(locale).subSequence(start, end);
|
}
|
|
/**
|
* Formats the object using the provided {@link Formatter formatter}.
|
*
|
* @param formatter
|
* The {@link Formatter 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
|
* <tt>width</tt> then the output will be padded by
|
* <tt>' '</tt> 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 <tt>width</tt> is
|
* <tt>-1</tt> 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 <tt>precision</tt> characters even if the
|
* <tt>width</tt> is greater than the <tt>precision</tt>. If
|
* <tt>precision</tt> is <tt>-1</tt> 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 <a
|
* href="../util/Formatter.html#detail">Details</a> section of the
|
* formatter class specification.
|
*/
|
public void formatTo(Formatter formatter, int flags,
|
int width, int precision)
|
throws IllegalFormatException
|
{
|
// Ignores flags, width and precission for now.
|
// see javadoc for Formattable
|
Locale l = formatter.locale();
|
formatter.format(l, descriptor.getFormatString(l), args);
|
}
|
|
|
/**
|
* Creates a parameterized instance. See the class header
|
* for instructions on how to create messages outside this
|
* package.
|
* @param descriptor for this message
|
* @param args arguments for replacing specifiers in the
|
* message's format string
|
*/
|
Message(MessageDescriptor descriptor, Object... args) {
|
this.descriptor = descriptor;
|
this.args = args;
|
}
|
|
/**
|
* Compares this object with the specified object for order. Returns a
|
* negative integer, zero, or a positive integer as this object is less
|
* than, equal to, or greater than the specified object.
|
*
|
* @param o the object 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(Message o) {
|
return toString().compareTo(o.toString());
|
}
|
|
/**
|
* Indicates whether some other message is "equal to" this one. Messages
|
* are considered equal if their string representation in the default
|
* locale are equal.
|
*
|
* @param o the reference object with which to compare.
|
* @return <code>true</code> if this object is the same as the obj
|
* argument; <code>false</code> otherwise.
|
* @see #hashCode()
|
* @see java.util.Hashtable
|
*/
|
@Override
|
public boolean equals(Object o) {
|
if (this == o) return true;
|
if (o == null || getClass() != o.getClass()) return false;
|
|
Message message = (Message) o;
|
|
return toString().equals(message.toString());
|
}
|
|
/**
|
* Returns a hash code value for the object.
|
*
|
* @return a hash code value for this object.
|
* @see java.lang.Object#equals(java.lang.Object)
|
* @see java.util.Hashtable
|
*/
|
@Override
|
public int hashCode() {
|
int result;
|
result = 31 * toString().hashCode();
|
return result;
|
}
|
|
|
// TODO: remove this code once the JDK issue referenced in 3077 is closed.
|
/**
|
* Returns whether we are running on AIX or not.
|
* @return <CODE>true</CODE> if we are running on AIX and
|
* <CODE>false</CODE> otherwise.
|
*/
|
private boolean isAIX()
|
{
|
return "aix".equalsIgnoreCase(System.getProperty("os.name"));
|
}
|
|
}
|