/*
* 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 Sun Microsystems, Inc.
*/
package com.sun.opends.sdk.util;
import java.util.Formattable;
import java.util.Formatter;
import java.util.IllegalFormatException;
import java.util.Locale;
/**
* 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 Message objects that render the same
* text regardless of the requested locale. This class implements
* 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 MessageDescriptor
*/
public final class Message implements CharSequence, Formattable,
Comparable
{
/** 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 isAIXPost5 = isAIXPost5();
/**
* Creates an uninternationalized message that will render itself the
* same way regardless of the locale requested in
* toString(Locale). The message will have a category of
* Category.USER_DEFINED and a severity of
* Severity.INFORMATION Note that the types for
* args must be consistent with any argument specifiers
* appearing in formatString 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 formatString is null
*/
static public Message raw(CharSequence formatString, Object... args)
{
Message message = null;
if (formatString != null)
{
message = new MessageDescriptor.Raw(formatString).get(args);
}
return message;
}
/**
* Creates an uninternationalized message from the string
* representation of an object. Note that the types for
* args must be consistent with any argument specifiers
* appearing in formatString 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 arguments
* for message
* @return a message object that will render the same in all locales;
* null if object is null
*/
static public Message fromObject(Object object, Object... arguments)
{
Message message = null;
if (object != null)
{
CharSequence cs = object.toString();
message = raw(cs, arguments);
}
return message;
}
/**
* Returns the string representation of the message in the default
* locale.
*
* @param message
* to stringify
* @return String representation of of message of null if
* message 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
*/
public String toString()
{
return toString(Locale.getDefault());
}
/**
* Gets the string representation of this message appropriate for
* locale.
*
* @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 (isAIXPost5)
{
// 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 chars 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 char value at the specified index of this
* message rendered using the default locale.
*
* @param index
* the index of the char value to be returned
* @return the specified char value
* @throws IndexOutOfBoundsException
* if the index argument is negative or not less
* than length()
*/
public char charAt(int index) throws IndexOutOfBoundsException
{
return charAt(Locale.getDefault(), index);
}
/**
* Returns a new CharSequence that is a subsequence of
* this message rendered using the default locale. The subsequence
* starts with the char value at the specified index and
* ends with the char value at index end - 1.
* The length (in chars) of the returned sequence is
* end - start, so if 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 start or end are negative, if
* end is greater than length(), or if
* start is greater than end
*/
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 chars in this message
*/
public int length(Locale locale)
{
return toString(locale).length();
}
/**
* Returns the char 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 char value to be returned
* @return the specified char value
* @throws IndexOutOfBoundsException
* if the index argument is negative or not less
* than length()
*/
public char charAt(Locale locale, int index)
throws IndexOutOfBoundsException
{
return toString(locale).charAt(index);
}
/**
* Returns a new CharSequence that is a subsequence of
* this message rendered using a specific locale. The subsequence
* starts with the char value at the specified index and
* ends with the char value at index end - 1.
* The length (in chars) of the returned sequence is
* end - start, so if start == end 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 start or end are negative, if
* end is greater than length(), or if
* start is greater than end
*/
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 width then the output will be padded by ' '
* 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 width
* is -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 precision
* characters even if the width is greater than the
* precision. If precision is -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 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 true if this object is the same as the obj
* argument; false otherwise.
* @see #hashCode()
* @see java.util.Hashtable
*/
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
*/
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 post 1.5 on AIX or not.
*
* @return true if we are running post 1.5 on AIX and
* false otherwise.
*/
private boolean isAIXPost5()
{
boolean isJDK15 = false;
try
{
String javaRelease = System.getProperty("java.version");
isJDK15 = javaRelease.startsWith("1.5");
}
catch (Throwable t)
{
System.err.println("Cannot get the java version: " + t);
}
boolean isAIX = "aix".equalsIgnoreCase(System
.getProperty("os.name"));
return !isJDK15 && isAIX;
}
}