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