/* * 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 { static { LocalizableMessageDescriptor.messageFactory = new LocalizableMessageDescriptor.MessageFactory() { public LocalizableMessage newMessage( final LocalizableMessageDescriptor descriptor, final Object... args) { return new LocalizableMessage(descriptor, args); } }; } /** * Represents an empty message string. */ public static final LocalizableMessage EMPTY = LocalizableMessage.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 static final boolean IS_AIX_POST5 = isAIXPost5(); /** * Creates an uninternationalized message whose {@code String} representation * is always the same regardless of the requested locale. *

* 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; } }