home *** CD-ROM | disk | FTP | other *** search
/ Java 1.2 How-To / JavaHowTo.iso / 3rdParty / jbuilder / unsupported / JDK1.2beta3 / SOURCE / SRC.ZIP / java / text / MessageFormat.java < prev    next >
Encoding:
Java Source  |  1998-03-20  |  33.6 KB  |  862 lines

  1. /*
  2.  * @(#)MessageFormat.java    1.25 98/03/18
  3.  *
  4.  * (C) Copyright Taligent, Inc. 1996,1997 - All Rights Reserved
  5.  * (C) Copyright IBM Corp. 1996,1997 - All Rights Reserved
  6.  *
  7.  * Portions copyright (c) 1996-1998 Sun Microsystems, Inc. All Rights Reserved.
  8.  *
  9.  *   The original version of this source code and documentation is copyrighted
  10.  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  11.  * materials are provided under terms of a License Agreement between Taligent
  12.  * and Sun. This technology is protected by multiple US and International
  13.  * patents. This notice and attribution to Taligent may not be removed.
  14.  *   Taligent is a registered trademark of Taligent, Inc.
  15.  *
  16.  * Permission to use, copy, modify, and distribute this software
  17.  * and its documentation for NON-COMMERCIAL purposes and without
  18.  * fee is hereby granted provided that this copyright notice
  19.  * appears in all copies. Please refer to the file "copyright.html"
  20.  * for further important copyright and licensing information.
  21.  *
  22.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  23.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  24.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  25.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  26.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  27.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  28.  *
  29.  */
  30.  
  31. package java.text;
  32.  
  33. import java.util.Date;
  34. import java.util.Locale;
  35. import java.text.DecimalFormat;
  36. import java.text.Utility;
  37. /**
  38.  * <code>MessageFormat</code> provides a means to produce concatenated
  39.  * messages in language-neutral way. Use this to construct messages
  40.  * displayed for end users.
  41.  *
  42.  * <p>
  43.  * <code>MessageFormat</code> takes a set of objects, formats them, then
  44.  * inserts the formatted strings into the pattern at the appropriate places.
  45.  *
  46.  * <p>
  47.  * <strong>Note:</strong>
  48.  * <code>MessageFormat</code> differs from the other <code>Format</code>
  49.  * classes in that you create a <code>MessageFormat</code> object with one
  50.  * of its constructors (not with a <code>getInstance</code> style factory
  51.  * method). The factory methods aren't necessary because <code>MessageFormat</code>
  52.  * doesn't require any complex setup for a given locale. In fact,
  53.  * <code>MessageFormat</code> doesn't implement any locale specific behavior
  54.  * at all. It just needs to be set up on a sentence by sentence basis.
  55.  *
  56.  * <p>
  57.  * Here are some examples of usage:
  58.  * <blockquote>
  59.  * <pre>
  60.  * Object[] arguments = {
  61.  *     new Integer(7),
  62.  *     new Date(System.currentTimeMillis()),
  63.  *     "a disturbance in the Force"
  64.  * };
  65.  *
  66.  * String result = MessageFormat.format(
  67.  *     "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
  68.  *     arguments);
  69.  *
  70.  * <em>output</em>: At 12:30 PM on Jul 3, 2053, there was a disturbance
  71.  *           in the Force on planet 7.
  72.  *
  73.  * </pre>
  74.  * </blockquote>
  75.  * Typically, the message format will come from resources, and the
  76.  * arguments will be dynamically set at runtime.
  77.  *
  78.  * <p>
  79.  * Example 2:
  80.  * <blockquote>
  81.  * <pre>
  82.  * Object[] testArgs = {new Long(3), "MyDisk"};
  83.  *
  84.  * MessageFormat form = new MessageFormat(
  85.  *     "The disk \"{1}\" contains {0} file(s).");
  86.  *
  87.  * System.out.println(form.format(testArgs));
  88.  *
  89.  * // output, with different testArgs
  90.  * <em>output</em>: The disk "MyDisk" contains 0 file(s).
  91.  * <em>output</em>: The disk "MyDisk" contains 1 file(s).
  92.  * <em>output</em>: The disk "MyDisk" contains 1,273 file(s).
  93.  * </pre>
  94.  * </blockquote>
  95.  *
  96.  * <p>
  97.  * The pattern is of the form:
  98.  * <blockquote>
  99.  * <pre>
  100.  * messageFormatPattern := string ( "{" messageFormatElement "}" string )*
  101.  *
  102.  * messageFormatElement := argument { "," elementFormat }
  103.  *
  104.  * elementFormat := "time" { "," datetimeStyle }
  105.  *                | "date" { "," datetimeStyle }
  106.  *                | "number" { "," numberStyle }
  107.  *                | "choice" { "," choiceStyle }
  108.  *
  109.  * datetimeStyle := "short"
  110.  *                  | "medium"
  111.  *                  | "long"
  112.  *                  | "full"
  113.  *                  | dateFormatPattern
  114.  *
  115.  * numberStyle := "currency"
  116.  *               | "percent"
  117.  *               | "integer"
  118.  *               | numberFormatPattern
  119.  *
  120.  * choiceStyle := choiceFormatPattern
  121.  * </pre>
  122.  * </blockquote>
  123.  * If there is no <code>elementFormat</code>,
  124.  * then the argument must be a string, which is substituted. If there is
  125.  * no <code>dateTimeStyle</code> or <code>numberStyle</code>, then the
  126.  * default format is used (for example, <code>NumberFormat.getInstance</code>,
  127.  * <code>DateFormat.getTimeInstance</code>, or <code>DateFormat.getInstance</code>).
  128.  *
  129.  * <p>
  130.  * In strings, single quotes can be used to quote the "{"
  131.  * (curly brace) if necessary. A real single quote is represented by ''.
  132.  * Inside a <code>messageFormatElement</code>, quotes are <strong>not</strong>
  133.  * removed. For example, {1,number,$'#',##} will produce a number format
  134.  * with the pound-sign quoted, with a result such as: "$#31,45".
  135.  *
  136.  * <p>
  137.  * If a pattern is used, then unquoted braces in the pattern, if any, must match:
  138.  * that is, "ab {0} de" and "ab '}' de" are ok, but "ab {0'}' de" and "ab } de" are
  139.  * not.
  140.  *
  141.  * <p>
  142.  * The argument is a number from 0 to 9, which corresponds to the
  143.  * arguments presented in an array to be formatted.
  144.  *
  145.  * <p>
  146.  * It is ok to have unused arguments in the array.
  147.  * With missing arguments or arguments that are not of the right class for
  148.  * the specified format, a <code>ParseException</code> is thrown.
  149.  * First, <code>format</code> checks to see if a <code>Format</code> object has been
  150.  * specified for the argument with the <code>setFormats</code> method.
  151.  * If so, then <code>format</code> uses that <code>Format</code> object to format the
  152.  * argument. Otherwise, the argument is formatted based on the object's
  153.  * type. If the argument is a <code>Number</code>, then <code>format</code>
  154.  * uses <code>NumberFormat.getInstance</code> to format the argument; if the
  155.  * argument is a <code>Date</code>, then <code>format</code> uses
  156.  * <code>DateFormat.getDateTimeInstance</code> to format the argument.
  157.  * Otherwise, it uses the <code>toString</code> method.
  158.  *
  159.  * <p>
  160.  * For more sophisticated patterns, you can use a <code>ChoiceFormat</code> to get
  161.  * output such as:
  162.  * <blockquote>
  163.  * <pre>
  164.  * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
  165.  * double[] filelimits = {0,1,2};
  166.  * String[] filepart = {"no files","one file","{0,number} files"};
  167.  * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
  168.  * form.setFormat(1,fileform); // NOT zero, see below
  169.  *
  170.  * Object[] testArgs = {new Long(12373), "MyDisk"};
  171.  *
  172.  * System.out.println(form.format(testArgs));
  173.  *
  174.  * // output, with different testArgs
  175.  * output: The disk "MyDisk" contains no files.
  176.  * output: The disk "MyDisk" contains one file.
  177.  * output: The disk "MyDisk" contains 1,273 files.
  178.  * </pre>
  179.  * </blockquote>
  180.  * You can either do this programmatically, as in the above example,
  181.  * or by using a pattern (see
  182.  * <a href="java.text.ChoiceFormat.html"><code>ChoiceFormat</code></a>
  183.  * for more information) as in:
  184.  * <blockquote>
  185.  * <pre>
  186.  * form.applyPattern(
  187.  *    "There {0,choice,0#are no files|1#is one file|1#are {0,number,integer} files}.");
  188.  * </pre>
  189.  * </blockquote>
  190.  * <p>
  191.  * <strong>Note:</strong> As we see above, the string produced
  192.  * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated specially;
  193.  * occurances of '{' are used to indicated subformats, and cause recursion.
  194.  * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>
  195.  * programmatically (instead of using the string patterns), then be careful not to
  196.  * produce a format that recurses on itself, which will cause an infinite loop.
  197.  * <p>
  198.  * <strong>Note:</strong> formats are numbered by order of
  199.  * variable in the string.
  200.  * This is <strong>not</strong> the same as the argument numbering!
  201.  * For example: with "abc{2}def{3}ghi{0}...",
  202.  * <ul>
  203.  * <li>format0 affects the first variable {2}
  204.  * <li>format1 affects the second variable {3}
  205.  * <li>format2 affects the second variable {0}
  206.  * <li>and so on.
  207.  * </ul>
  208.  * <p>
  209.  * You can use <code>setLocale</code> followed by <code>applyPattern</code>
  210.  * (and then possibly <code>setFormat</code>) to re-initialize a
  211.  * <code>MessageFormat</code> with a different locale.
  212.  *
  213.  * @see          java.util.Locale
  214.  * @see          Format
  215.  * @see          NumberFormat
  216.  * @see          DecimalFormat
  217.  * @see          ChoiceFormat
  218.  * @version      1.15 29 Jan 1997
  219.  * @author       Mark Davis
  220.  */
  221.  
  222. public class MessageFormat extends Format {
  223.     /**
  224.      * Constructs with the specified pattern.
  225.      * @see MessageFormat#applyPattern
  226.      */
  227.     public MessageFormat(String pattern) {
  228.         applyPattern(pattern);
  229.     }
  230.  
  231.     /**
  232.      * Constructs with the specified pattern and formats for the
  233.      * arguments in that pattern.
  234.      */
  235.     public void setLocale(Locale theLocale) {
  236.         locale = theLocale;
  237.     }
  238.  
  239.     /**
  240.      * Gets the locale. This locale is used for fetching default number or date
  241.      * format information.
  242.      */
  243.     public Locale getLocale() {
  244.         return locale;
  245.     }
  246.  
  247.  
  248.     /**
  249.      * Sets the pattern. See the class description.
  250.      */
  251.  
  252.     public void applyPattern(String newPattern) {
  253.             StringBuffer[] segments = new StringBuffer[4];
  254.             for (int i = 0; i < segments.length; ++i) {
  255.                 segments[i] = new StringBuffer();
  256.             }
  257.             int part = 0;
  258.             int formatNumber = 0;
  259.             boolean inQuote = false;
  260.             int braceStack = 0;
  261.             maxOffset = -1;
  262.             for (int i = 0; i < newPattern.length(); ++i) {
  263.                 char ch = newPattern.charAt(i);
  264.                 if (part == 0) {
  265.                     if (ch == '\'') {
  266.                         if (i + 1 < newPattern.length()
  267.                             && newPattern.charAt(i+1) == '\'') {
  268.                             segments[part].append(ch);  // handle doubles
  269.                             ++i;
  270.                         } else {
  271.                             inQuote = !inQuote;
  272.                         }
  273.                     } else if (ch == '{' && !inQuote) {
  274.                         part = 1;
  275.                     } else {
  276.                         segments[part].append(ch);
  277.                     }
  278.                 } else  if (inQuote) {              // just copy quotes in parts
  279.                     segments[part].append(ch);
  280.                     if (ch == '\'') {
  281.                         inQuote = false;
  282.                     }
  283.                 } else {
  284.                     switch (ch) {
  285.                     case ',':
  286.                         if (part < 3)
  287.                             part += 1;
  288.                         else
  289.                             segments[part].append(ch);
  290.                         break;
  291.                     case '{':
  292.                         ++braceStack;
  293.                         segments[part].append(ch);
  294.                         break;
  295.                     case '}':
  296.                         if (braceStack == 0) {
  297.                             part = 0;
  298.                             makeFormat(i, formatNumber, segments);
  299.                             formatNumber++;
  300.                         } else {
  301.                             --braceStack;
  302.                             segments[part].append(ch);
  303.                         }
  304.                         break;
  305.                     case '\'':
  306.                         inQuote = true;
  307.                         // fall through, so we keep quotes in other parts
  308.                     default:
  309.                         segments[part].append(ch);
  310.                         break;
  311.                     }
  312.                 }
  313.             }
  314.             pattern = segments[0].toString();
  315.     }
  316.  
  317.  
  318.     /**
  319.      * Gets the pattern. See the class description.
  320.      */
  321.  
  322.     public String toPattern() {
  323.         // later, make this more extensible
  324.         int lastOffset = 0;
  325.         StringBuffer result = new StringBuffer();
  326.         for (int i = 0; i <= maxOffset; ++i) {
  327.             copyAndFixQuotes(pattern, lastOffset, offsets[i],result);
  328.             lastOffset = offsets[i];
  329.             result.append('{');
  330.             result.append(argumentNumbers[i]);
  331.             if (formats[i] == null) {
  332.                 // do nothing, string format
  333.             } else if (formats[i] instanceof DecimalFormat) {
  334.                 if (formats[i].equals(NumberFormat.getInstance(locale))) {
  335.                     result.append(",number");
  336.                 } else if (formats[i].equals(
  337.                                              NumberFormat.getCurrencyInstance(locale))) {
  338.                     result.append(",number,currency");
  339.                 } else if (formats[i].equals(
  340.                                              NumberFormat.getPercentInstance(locale))) {
  341.                     result.append(",number,percent");
  342.                 } else if (formats[i].equals(getIntegerFormat(locale))) {
  343.                     result.append(",number,integer");
  344.                 } else {
  345.                     result.append(",number," +
  346.                                   ((DecimalFormat)formats[i]).toPattern());
  347.                 }
  348.             } else if (formats[i] instanceof SimpleDateFormat) {
  349.                 if (formats[i].equals(DateFormat.getDateInstance(
  350.                                                                DateFormat.DEFAULT,locale))) {
  351.                     result.append(",date");
  352.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  353.                                                                       DateFormat.SHORT,locale))) {
  354.                     result.append(",date,short");
  355.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  356.                                                                       DateFormat.DEFAULT,locale))) {
  357.                     result.append(",date,medium");
  358.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  359.                                                                       DateFormat.LONG,locale))) {
  360.                     result.append(",date,long");
  361.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  362.                                                                       DateFormat.FULL,locale))) {
  363.                     result.append(",date,full");
  364.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  365.                                                                       DateFormat.DEFAULT,locale))) {
  366.                     result.append(",time");
  367.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  368.                                                                       DateFormat.SHORT,locale))) {
  369.                     result.append(",time,short");
  370.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  371.                                                                       DateFormat.DEFAULT,locale))) {
  372.                     result.append(",time,medium");
  373.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  374.                                                                       DateFormat.LONG,locale))) {
  375.                     result.append(",time,long");
  376.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  377.                                                                       DateFormat.FULL,locale))) {
  378.                     result.append(",time,full");
  379.                 } else {
  380.                     result.append(",date,"
  381.                                   + ((SimpleDateFormat)formats[i]).toPattern());
  382.                 }
  383.             } else if (formats[i] instanceof ChoiceFormat) {
  384.                 result.append(",choice,"
  385.                               + ((ChoiceFormat)formats[i]).toPattern());
  386.             } else {
  387.                 //result.append(", unknown");
  388.             }
  389.             result.append('}');
  390.         }
  391.         copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
  392.         return result.toString();
  393.     }
  394.  
  395.     /**
  396.      * Sets formats to use on parameters.
  397.      * See the class description about format numbering.
  398.      */
  399.     public void setFormats(Format[] newFormats) {
  400.         try {
  401.             formats = (Format[]) newFormats.clone();
  402.         } catch (Exception e) {
  403.             return; // should never occur!
  404.         }
  405.     }
  406.  
  407.     /**
  408.      * Sets formats individually to use on parameters.
  409.      * See the class description about format numbering.
  410.      */
  411.     public void setFormat(int variable, Format newFormat) {
  412.         formats[variable] = newFormat;
  413.     }
  414.  
  415.     /**
  416.      * Gets formats that were set with setFormats.
  417.      * See the class description about format numbering.
  418.      */
  419.     public Format[] getFormats() {
  420.         try {
  421.             return (Format[]) formats.clone();
  422.         } catch (Exception e) {
  423.             return formats; // should never occur!
  424.         }
  425.     }
  426.  
  427.     /**
  428.      * Returns pattern with formatted objects.
  429.      * @param source an array of objects to be formatted & substituted.
  430.      * @param result where text is appended.
  431.      * @param ignore no useful status is returned.
  432.      */
  433.     public final StringBuffer format(Object[] source, StringBuffer result,
  434.                                      FieldPosition ignore)
  435.     {
  436.         return format(source,result,ignore, 0);
  437.     }
  438.  
  439.     /**
  440.      * Convenience routine.
  441.      * Avoids explicit creation of MessageFormat,
  442.      * but doesn't allow future optimizations.
  443.      */
  444.     public static String format(String pattern, Object[] arguments) {
  445.             MessageFormat temp = new MessageFormat(pattern);
  446.             return temp.format(arguments);
  447.     }
  448.  
  449.     // Overrides
  450.     public final StringBuffer format(Object source, StringBuffer result,
  451.                                      FieldPosition ignore)
  452.     {
  453.         return format((Object[])source,result,ignore, 0);
  454.     }
  455.  
  456.     /**
  457.      * Parses the string.
  458.      *
  459.      * <p>Caveats: The parse may fail in a number of circumstances.
  460.      * For example:
  461.      * <ul>
  462.      * <li>If one of the arguments does not occur in the pattern.
  463.      * <li>If the format of an argument is loses information, such as
  464.      *     with a choice format where a large number formats to "many".
  465.      * <li>Does not yet handle recursion (where
  466.      *     the substituted strings contain {n} references.)
  467.      * <li>Will not always find a match (or the correct match)
  468.      *     if some part of the parse is ambiguous.
  469.      *     For example, if the pattern "{1},{2}" is used with the
  470.      *     string arguments {"a,b", "c"}, it will format as "a,b,c".
  471.      *     When the result is parsed, it will return {"a", "b,c"}.
  472.      * <li>If a single argument is formatted twice in the string,
  473.      *     then the later parse wins.
  474.      * </ul>
  475.      * When the parse fails, use ParsePosition.getErrorIndex() to find out
  476.      * where in the string did the parsing failed.  The returned error
  477.      * index is the starting offset of the sub-patterns that the string
  478.      * is comparing with.  For example, if the parsing string "AAA {0} BBB"
  479.      * is comparing against the pattern "AAD {0} BBB", the error index is
  480.      * 0.
  481.      */
  482.     public Object[] parse(String source, ParsePosition status) {
  483.         Object[] resultArray = new Object[10];
  484.         int patternOffset = 0;
  485.         int sourceOffset = status.index;
  486.         ParsePosition tempStatus = new ParsePosition(0);
  487.         for (int i = 0; i <= maxOffset; ++i) {
  488.             // match up to format
  489.             int len = offsets[i] - patternOffset;
  490.             if (len == 0 || pattern.regionMatches(patternOffset,
  491.                                                   source, sourceOffset, len)) {
  492.                 sourceOffset += len;
  493.                 patternOffset += len;
  494.             } else {
  495.                 status.errorIndex = sourceOffset;
  496.                 return null; // leave index as is to signal error
  497.             }
  498.  
  499.             // now use format
  500.             if (formats[i] == null) {   // string format
  501.                 // if at end, use longest possible match
  502.                 // otherwise uses first match to intervening string
  503.                 // does NOT recursively try all possibilities
  504.                 int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
  505.  
  506.                 int next;
  507.                 if (patternOffset >= tempLength) {
  508.                     next = source.length();
  509.                 }else{
  510.                     next = source.indexOf( pattern.substring(patternOffset,tempLength), sourceOffset);
  511.                 }
  512.  
  513.                 if (next < 0) {
  514.                     status.errorIndex = sourceOffset;
  515.                     return null; // leave index as is to signal error
  516.                 } else {
  517.                     String strValue= source.substring(sourceOffset,next);
  518.                     if (!strValue.equals("{"+argumentNumbers[i]+"}"))
  519.                         resultArray[argumentNumbers[i]]
  520.                             = source.substring(sourceOffset,next);
  521.                     sourceOffset = next;
  522.                 }
  523.             } else {
  524.                 tempStatus.index = sourceOffset;
  525.                 resultArray[argumentNumbers[i]]
  526.                     = formats[i].parseObject(source,tempStatus);
  527.                 if (tempStatus.index == sourceOffset) {
  528.                     status.errorIndex = sourceOffset;
  529.                     return null; // leave index as is to signal error
  530.                 }
  531.                 sourceOffset = tempStatus.index; // update
  532.             }
  533.         }
  534.         int len = pattern.length() - patternOffset;
  535.         if (len == 0 || pattern.regionMatches(patternOffset,
  536.                                               source, sourceOffset, len)) {
  537.             status.index = sourceOffset + len;
  538.         } else {
  539.             return null; // leave index as is to signal error
  540.         }
  541.         return resultArray;
  542.     }
  543.  
  544.     /**
  545.      * Parses the string. Does not yet handle recursion (where
  546.      * the substituted strings contain {n} references.)
  547.      * @exception ParseException if the string can't be parsed.
  548.      */
  549.     public Object[] parse(String source) throws ParseException {
  550.         ParsePosition status  = new ParsePosition(0);
  551.         Object[] result = parse(source, status);
  552.         if (status.index == 0)  // unchanged, returned object is null
  553.             throw new ParseException("MessageFormat parse error!", status.errorIndex);
  554.  
  555.         return result;
  556.     }
  557.  
  558.     /**
  559.      * Parses the string. Does not yet handle recursion (where
  560.      * the substituted strings contain %n references.)
  561.      */
  562.     public Object parseObject (String text, ParsePosition status) {
  563.         return parse(text, status);
  564.     }
  565.  
  566.     /**
  567.      * Overrides Cloneable
  568.      */
  569.     public Object clone()
  570.     {
  571.         MessageFormat other = (MessageFormat) super.clone();
  572.  
  573.         // clone arrays. Can't do with utility because of bug in Cloneable
  574.         other.formats = (Format[]) formats.clone(); // shallow clone
  575.         for (int i = 0; i < formats.length; ++i) {
  576.             if (formats[i] != null)
  577.                 other.formats[i] = (Format)formats[i].clone();
  578.         }
  579.         // for primitives or immutables, shallow clone is enough
  580.         other.offsets = (int[]) offsets.clone();
  581.         other.argumentNumbers = (int[]) argumentNumbers.clone();
  582.  
  583.         return other;
  584.     }
  585.  
  586.     /**
  587.      * Equality comparision between two message format objects
  588.      */
  589.     public boolean equals(Object obj) {
  590.         if (this == obj)                      // quick check
  591.             return true;
  592.         if (getClass() != obj.getClass())
  593.             return false;
  594.         MessageFormat other = (MessageFormat) obj;
  595.         return (maxOffset == other.maxOffset
  596.                 && pattern.equals(other.pattern)
  597.             && Utility.objectEquals(locale, other.locale)   // does null check
  598.                 && Utility.arrayEquals(offsets,other.offsets)
  599.             && Utility.arrayEquals(argumentNumbers,other.argumentNumbers)
  600.             && Utility.arrayEquals(formats,other.formats));
  601.     }
  602.  
  603.     /**
  604.      * Generates a hash code for the message format object.
  605.      */
  606.     public int hashCode() {
  607.         return pattern.hashCode(); // enough for reasonable distribution
  608.     }
  609.  
  610.  
  611.     // ===========================privates============================
  612.  
  613.     // Mark : Is this the right fix?  (HS)
  614.     private Locale locale = Locale.getDefault();
  615.     private String pattern = "";
  616.     // later, allow more than ten items
  617.     private Format[] formats = new Format[10];
  618.     private int[] offsets = new int[10];
  619.     private int[] argumentNumbers = new int[10];
  620.     private int maxOffset = -1;
  621.  
  622.     /**
  623.      * Constructs with the specified pattern.
  624.      * @see MessageFormat#applyPattern
  625.      */
  626.     private MessageFormat(String pattern, Locale loc) {
  627.         locale = (Locale)loc.clone();
  628.         applyPattern(pattern);
  629.     }
  630.  
  631.     /**
  632.      * Internal routine used by format.
  633.      * @param recursionProtection Initially zero. Bits 0..9 are used to indicate
  634.      * that a parameter has already been seen, to avoid recursion.  Currently
  635.      * unused.
  636.      */
  637.  
  638.     private StringBuffer format(Object[] arguments, StringBuffer result,
  639.                                 FieldPosition status, int recursionProtection) {
  640.         // note: this implementation assumes a fast substring & index.
  641.         // if this is not true, would be better to append chars one by one.
  642.         int lastOffset = 0;
  643.         for (int i = 0; i <= maxOffset; ++i) {
  644.             result.append(pattern.substring(lastOffset, offsets[i]));
  645.             lastOffset = offsets[i];
  646.             int argumentNumber = argumentNumbers[i];
  647.             if (arguments == null || argumentNumber >= arguments.length) {
  648.                 result.append("{" + argumentNumber + "}");
  649.                 continue;
  650.             }
  651.             // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
  652.             if (false) { // if (argRecursion == 3){
  653.                 // prevent loop!!!
  654.                 result.append('\uFFFD');
  655.             } else {
  656.                 Object obj = arguments[argumentNumber];
  657.                 String arg;
  658.                 boolean tryRecursion = false;
  659.                 if (formats[i] != null) {
  660.                     arg = formats[i].format(obj);
  661.                     tryRecursion = formats[i] instanceof ChoiceFormat;
  662.                 } else if (obj instanceof Number) {
  663.                     // format number if can
  664.                     arg = NumberFormat.getInstance(locale).format(obj); // fix
  665.                 } else if (obj instanceof Date) {
  666.                     // format a Date if can
  667.                     arg = DateFormat.getDateTimeInstance(DateFormat.SHORT,
  668.                                                        DateFormat.SHORT,
  669.                                                        locale).format(obj);//fix
  670.                 } else if (obj instanceof String) {
  671.                     arg = (String) obj;
  672.  
  673.                 } else {
  674.                     arg = obj.toString();
  675.                     if (arg == null) arg = "null";
  676.                 }
  677.  
  678.                 // recurse if necessary
  679.                 if (tryRecursion && arg.indexOf('{') >= 0) {
  680.                     MessageFormat temp = new MessageFormat(arg, locale);
  681.                     temp.format(arguments,result,status,recursionProtection);
  682.                 } else {
  683.                     result.append(arg);
  684.                 }
  685.             }
  686.         }
  687.         result.append(pattern.substring(lastOffset, pattern.length()));
  688.         return result;
  689.     }
  690.     private static final String[] typeList =
  691.     {"", "", "number", "", "date", "", "time", "", "choice"};
  692.     private static final String[] modifierList =
  693.     {"", "", "currency", "", "percent", "", "integer"};
  694.     private static final String[] dateModifierList =
  695.     {"", "", "short", "", "medium", "", "long", "", "full"};
  696.  
  697.     private void makeFormat(int position, int offsetNumber,
  698.                             StringBuffer[] segments)
  699.     {
  700.  
  701.         // get the number
  702.         int argumentNumber;
  703.         try {
  704.             argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized!
  705.             if (argumentNumber < 0 || argumentNumber > 9) {
  706.                 throw new NumberFormatException();
  707.             }
  708.             maxOffset = offsetNumber;
  709.             offsets[offsetNumber] = segments[0].length();
  710.             argumentNumbers[offsetNumber] = argumentNumber;
  711.         } catch (Exception e) {
  712.             throw new IllegalArgumentException("argument number too large at ");
  713.         }
  714.  
  715.         // now get the format
  716.         Format newFormat = null;
  717.         switch (findKeyword(segments[2].toString(), typeList)) {
  718.         case 0:
  719.             // string format
  720.             /*if (!segments[3].equals(""))
  721.               throw new IllegalArgumentException("can't modify string format, at ");
  722.               //*/
  723.         break;
  724.         case 1: case 2:// number
  725.             switch (findKeyword(segments[3].toString(), modifierList)) {
  726.             case 0: // default;
  727.                 newFormat = NumberFormat.getInstance(locale);
  728.                 break;
  729.             case 1: case 2:// currency
  730.                 newFormat = NumberFormat.getCurrencyInstance(locale);
  731.                 break;
  732.             case 3: case 4:// percent
  733.                 newFormat = NumberFormat.getPercentInstance(locale);
  734.                 break;
  735.             case 5: case 6:// integer
  736.                 newFormat = getIntegerFormat(locale);
  737.                 break;
  738.             default: // pattern
  739.                 newFormat = NumberFormat.getInstance(locale);
  740.                 try {
  741.                     ((DecimalFormat)newFormat).applyPattern(segments[3].toString());
  742.                 } catch (Exception e) {
  743.                     throw new IllegalArgumentException(
  744.                                              "Pattern incorrect or locale does not support formats, error at ");
  745.                 }
  746.                 break;
  747.             }
  748.             break;
  749.         case 3: case 4: // date
  750.             switch (findKeyword(segments[3].toString(), dateModifierList)) {
  751.             case 0: // default
  752.                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  753.                 break;
  754.             case 1: case 2: // short
  755.                 newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
  756.                 break;
  757.             case 3: case 4: // medium
  758.                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  759.                 break;
  760.             case 5: case 6: // long
  761.                 newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale);
  762.                 break;
  763.             case 7: case 8: // full
  764.                 newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale);
  765.                 break;
  766.             default:
  767.                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  768.                 try {
  769.                     ((SimpleDateFormat)newFormat).applyPattern(segments[3].toString());
  770.                 } catch (Exception e) {
  771.                     throw new IllegalArgumentException(
  772.                                              "Pattern incorrect or locale does not support formats, error at ");
  773.                 }
  774.                 break;
  775.             }
  776.             break;
  777.         case 5: case 6:// time
  778.             switch (findKeyword(segments[3].toString(), dateModifierList)) {
  779.             case 0: // default
  780.                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  781.                 break;
  782.             case 1: case 2: // short
  783.                 newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale);
  784.                 break;
  785.             case 3: case 4: // medium
  786.                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  787.                 break;
  788.             case 5: case 6: // long
  789.                 newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale);
  790.                 break;
  791.             case 7: case 8: // full
  792.                 newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale);
  793.                 break;
  794.             default:
  795.                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  796.                 try {
  797.                     ((SimpleDateFormat)newFormat).applyPattern(segments[3].toString());
  798.                 } catch (Exception e) {
  799.                     throw new IllegalArgumentException(
  800.                                              "Pattern incorrect or locale does not support formats, error at ");
  801.                 }
  802.                 break;
  803.             }
  804.             break;
  805.         case 7: case 8:// choice
  806.             try {
  807.                 newFormat = new ChoiceFormat(segments[3].toString());
  808.             } catch (Exception e) {
  809.                 throw new IllegalArgumentException(
  810.                                          "Choice Pattern incorrect, error at ");
  811.             }
  812.             break;
  813.         default:
  814.             throw new IllegalArgumentException("unknown format type at ");
  815.         }
  816.         formats[offsetNumber] = newFormat;
  817.         segments[1].setLength(0);   // throw away other segments
  818.         segments[2].setLength(0);
  819.         segments[3].setLength(0);
  820.     }
  821.  
  822.     private static final int findKeyword(String s, String[] list) {
  823.         s = s.trim().toLowerCase();
  824.         for (int i = 0; i < list.length; ++i) {
  825.             if (s.equals(list[i]))
  826.                 return i;
  827.         }
  828.         return -1;
  829.     }
  830.  
  831.     /**
  832.      * Convenience method that ought to be in NumberFormat
  833.      */
  834.     NumberFormat getIntegerFormat(Locale locale) {
  835.         NumberFormat temp = NumberFormat.getInstance(locale);
  836.         if (temp instanceof DecimalFormat) {
  837.             DecimalFormat temp2 = (DecimalFormat) temp;
  838.             temp2.setMaximumFractionDigits(0);
  839.             temp2.setDecimalSeparatorAlwaysShown(false);
  840.             temp2.setParseIntegerOnly(true);
  841.         }
  842.         return temp;
  843.     }
  844.  
  845.     private static final void copyAndFixQuotes(
  846.                                                String source, int start, int end, StringBuffer target) {
  847.         for (int i = start; i < end; ++i) {
  848.             char ch = source.charAt(i);
  849.             if (ch == '{') {
  850.                 target.append("'{'");
  851.             } else if (ch == '}') {
  852.                 target.append("'}'");
  853.             } else if (ch == '\'') {
  854.                 target.append("''");
  855.             } else {
  856.                 target.append(ch);
  857.             }
  858.         }
  859.     }
  860.  
  861. }
  862.