home *** CD-ROM | disk | FTP | other *** search
/ Chip 1998 November / Chip_1998-11_cd.bin / tema / Cafe / main.bin / MessageFormat.java < prev    next >
Text File  |  1997-05-20  |  34KB  |  828 lines

  1. /*
  2.  * @(#)MessageFormat.java    1.15 97/01/29
  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-1997 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.  * <output>: 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.  * <output>: The disk "MyDisk" contains 0 file(s).
  91.  * <output>: The disk "MyDisk" contains 1 file(s).
  92.  * <output>: 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.  *
  209.  * @see          java.util.Locale
  210.  * @see          Format
  211.  * @see          NumberFormat
  212.  * @see          DecimalFormat
  213.  * @see          ChoiceFormat
  214.  * @version      1.15 29 Jan 1997
  215.  * @author       Mark Davis
  216.  */
  217.  
  218. public class MessageFormat extends Format {
  219.     /**
  220.      * Constructs with the specified pattern.
  221.      * @see MessageFormat#applyPattern
  222.      */
  223.     public MessageFormat(String pattern) {
  224.         applyPattern(pattern);
  225.     }
  226.  
  227.     /**
  228.      * Constructs with the specified pattern and formats for the
  229.      * arguments in that pattern.
  230.      * @see MessageFormat#setPattern
  231.      */
  232.     public void setLocale(Locale theLocale) {
  233.         locale = theLocale;
  234.     }
  235.  
  236.     /**
  237.      * Gets the locale. This locale is used for fetching default number or date
  238.      * format information.
  239.      */
  240.     public Locale getLocale() {
  241.         return locale;
  242.     }
  243.  
  244.  
  245.     /**
  246.      * Sets the pattern. See the class description.
  247.      */
  248.  
  249.     public void applyPattern(String newPattern) {
  250.             StringBuffer[] segments = new StringBuffer[4];
  251.             for (int i = 0; i < segments.length; ++i) {
  252.                 segments[i] = new StringBuffer();
  253.             }
  254.             int part = 0;
  255.             int formatNumber = 0;
  256.             boolean inQuote = false;
  257.             int braceStack = 0;
  258.             for (int i = 0; i < newPattern.length(); ++i) {
  259.                 char ch = newPattern.charAt(i);
  260.                 if (part == 0) {
  261.                     if (ch == '\'') {
  262.                         if (i + 1 < newPattern.length()
  263.                             && newPattern.charAt(i+1) == '\'') {
  264.                             segments[part].append(ch);    // handle doubles
  265.                             ++i;
  266.                         } else {
  267.                             inQuote = !inQuote;
  268.                         }
  269.                     } else if (ch == '{' && !inQuote) {
  270.                         part = 1;
  271.                     } else {
  272.                         segments[part].append(ch);
  273.                     }
  274.                 } else  if (inQuote) {                // just copy quotes in parts
  275.                     segments[part].append(ch);
  276.                     if (ch == '\'') {
  277.                         inQuote = false;
  278.                     }
  279.                 } else {
  280.                     switch (ch) {
  281.                     case ',':
  282.                         if (part < 3)
  283.                             part += 1;
  284.                         else
  285.                             segments[part].append(ch);
  286.                         break;
  287.                     case '{':
  288.                         ++braceStack;
  289.                         segments[part].append(ch);
  290.                         break;
  291.                     case '}':
  292.                         if (braceStack == 0) {
  293.                             part = 0;
  294.                             makeFormat(i, formatNumber, segments);
  295.                             formatNumber++;
  296.                         } else {
  297.                             --braceStack;
  298.                             segments[part].append(ch);
  299.                         }
  300.                         break;
  301.                     case '\'':
  302.                         inQuote = true;
  303.                         // fall through, so we keep quotes in other parts
  304.                     default:
  305.                         segments[part].append(ch);
  306.                         break;
  307.                     }
  308.                 }
  309.             }
  310.             pattern = segments[0].toString();
  311.     }
  312.  
  313.  
  314.     /**
  315.      * Gets the pattern. See the class description.
  316.      */
  317.  
  318.     public String toPattern() {
  319.         // later, make this more extensible
  320.         int lastOffset = 0;
  321.         StringBuffer result = new StringBuffer();
  322.         for (int i = 0; i <= maxOffset; ++i) {
  323.             copyAndFixQuotes(pattern, lastOffset, offsets[i],result);
  324.             lastOffset = offsets[i];
  325.             result.append('{');
  326.             result.append(argumentNumbers[i]);
  327.             if (formats[i] == null) {
  328.                 // do nothing, string format
  329.             } else if (formats[i] instanceof DecimalFormat) {
  330.                 if (formats[i].equals(NumberFormat.getInstance(locale))) {
  331.                     result.append(",number");
  332.                 } else if (formats[i].equals(
  333.                                              NumberFormat.getCurrencyInstance(locale))) {
  334.                     result.append(",number,currency");
  335.                 } else if (formats[i].equals(
  336.                                              NumberFormat.getPercentInstance(locale))) {
  337.                     result.append(",number,percent");
  338.                 } else if (formats[i].equals(getIntegerFormat(locale))) {
  339.                     result.append(",number,integer");
  340.                 } else {
  341.                     result.append(",number," +
  342.                                   ((DecimalFormat)formats[i]).toPattern());
  343.                 }
  344.             } else if (formats[i] instanceof SimpleDateFormat) {
  345.                 if (formats[i].equals(DateFormat.getDateInstance(
  346.                                                                DateFormat.DEFAULT,locale))) {
  347.                     result.append(",date");
  348.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  349.                                                                       DateFormat.SHORT,locale))) {
  350.                     result.append(",date,short");
  351.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  352.                                                                       DateFormat.DEFAULT,locale))) {
  353.                     result.append(",date,medium");
  354.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  355.                                                                       DateFormat.LONG,locale))) {
  356.                     result.append(",date,long");
  357.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  358.                                                                       DateFormat.FULL,locale))) {
  359.                     result.append(",date,full");
  360.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  361.                                                                       DateFormat.DEFAULT,locale))) {
  362.                     result.append(",time");
  363.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  364.                                                                       DateFormat.SHORT,locale))) {
  365.                     result.append(",time,short");
  366.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  367.                                                                       DateFormat.DEFAULT,locale))) {
  368.                     result.append(",time,medium");
  369.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  370.                                                                       DateFormat.LONG,locale))) {
  371.                     result.append(",time,long");
  372.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  373.                                                                       DateFormat.FULL,locale))) {
  374.                     result.append(",time,full");
  375.                 } else {
  376.                     result.append(",date,"
  377.                                   + ((SimpleDateFormat)formats[i]).toPattern());
  378.                 }
  379.             } else if (formats[i] instanceof ChoiceFormat) {
  380.                 result.append(",choice,"
  381.                               + ((ChoiceFormat)formats[i]).toPattern());
  382.             } else {
  383.                 //result.append(", unknown");
  384.             }
  385.             result.append('}');
  386.         }
  387.         copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
  388.         return result.toString();
  389.     }
  390.  
  391.     /**
  392.      * Sets formats to use on parameters.
  393.      * See the class description about format numbering.
  394.      */
  395.     public void setFormats(Format[] newFormats) {
  396.         try {
  397.             formats = (Format[]) newFormats.clone();
  398.         } catch (Exception e) {
  399.             return;    // should never occur!
  400.         }
  401.     }
  402.  
  403.     /**
  404.      * Sets formats individually to use on parameters.
  405.      * See the class description about format numbering.
  406.      */
  407.     public void setFormat(int variable, Format newFormat) {
  408.         formats[variable] = newFormat;
  409.     }
  410.  
  411.     /**
  412.      * Gets formats that were set with setFormats.
  413.      * See the class description about format numbering.
  414.      */
  415.     public Format[] getFormats() {
  416.         try {
  417.             return (Format[]) formats.clone();
  418.         } catch (Exception e) {
  419.             return formats;    // should never occur!
  420.         }
  421.     }
  422.  
  423.     /**
  424.      * Returns pattern with formatted objects.
  425.      * @param source an array of objects to be formatted & substituted.
  426.      * @param result where text is appended.
  427.      * @param ignore no useful status is returned.
  428.      */
  429.     public final StringBuffer format(Object[] source, StringBuffer result,
  430.                                      FieldPosition ignore)
  431.     {
  432.         return format(source,result,ignore, 0);
  433.     }
  434.  
  435.     /**
  436.      * Convenience routine.
  437.      * Avoids explicit creation of MessageFormat,
  438.      * but doesn't allow future optimizations.
  439.      */
  440.     public static String format(String pattern, Object[] arguments) {
  441.             MessageFormat temp = new MessageFormat(pattern);
  442.             return temp.format(arguments);
  443.     }
  444.  
  445.     // Overrides
  446.     public final StringBuffer format(Object source, StringBuffer result,
  447.                                      FieldPosition ignore)
  448.     {
  449.         return format((Object[])source,result,ignore, 0);
  450.     }
  451.  
  452.     /**
  453.      * Parses the string.
  454.      *
  455.      * <p>Caveats: The parse may fail in a number of circumstances.
  456.      * For example:
  457.      * <ul>
  458.      * <li>If one of the arguments does not occur in the pattern.
  459.      * <li>If the format of an argument is loses information, such as
  460.      *     with a choice format where a large number formats to "many".
  461.      * <li>Does not yet handle recursion (where
  462.      *     the substituted strings contain {n} references.)
  463.      * <li>Will not always find a match (or the correct match)
  464.      *     if some part of the parse is ambiguous.
  465.      *     For example, if the pattern "{1},{2}" is used with the
  466.      *     string arguments {"a,b", "c"}, it will format as "a,b,c".
  467.      *     When the result is parsed, it will return {"a", "b,c"}.
  468.      * <li>If a single argument is formatted twice in the string,
  469.      *     then the later parse wins.
  470.      * </ul>
  471.      */
  472.     public Object[] parse(String source, ParsePosition status) {
  473.         Object[] resultArray = new Object[10];
  474.         int patternOffset = 0;
  475.         int sourceOffset = status.index;
  476.         ParsePosition tempStatus = new ParsePosition(0);
  477.         for (int i = 0; i <= maxOffset; ++i) {
  478.             // match up to format
  479.             int len = offsets[i] - patternOffset;
  480.             if (len == 0 || pattern.regionMatches(patternOffset,
  481.                                                   source, sourceOffset, len)) {
  482.                 sourceOffset += len;
  483.                 patternOffset += len;
  484.             } else {
  485.                 return null; // leave index as is to signal error
  486.             }
  487.  
  488.             // now use format
  489.             if (formats[i] == null) {    // string format
  490.                 // if at end, use longest possible match
  491.                 // otherwise uses first match to intervening string
  492.                 // does NOT recursively try all possibilities
  493.                 int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
  494.                 int next = source.indexOf(
  495.                                           pattern.substring(patternOffset,tempLength),
  496.                                           sourceOffset);
  497.                 if (next < 0) {
  498.                     return null; // leave index as is to signal error
  499.                 } else {
  500.                     resultArray[argumentNumbers[i]]
  501.                         = source.substring(sourceOffset,next);
  502.                     sourceOffset = next;
  503.                 }
  504.             } else {
  505.                 tempStatus.index = sourceOffset;
  506.                 resultArray[argumentNumbers[i]]
  507.                     = formats[i].parseObject(source,tempStatus);
  508.                 if (tempStatus.index == sourceOffset) {
  509.                     return null; // leave index as is to signal error
  510.                 }
  511.                 sourceOffset = tempStatus.index; // update
  512.             }
  513.         }
  514.         int len = pattern.length() - patternOffset;
  515.         if (len == 0 || pattern.regionMatches(patternOffset,
  516.                                               source, sourceOffset, len)) {
  517.             status.index = sourceOffset + len;
  518.         } else {
  519.             return null; // leave index as is to signal error
  520.         }
  521.         return resultArray;
  522.     }
  523.  
  524.     /**
  525.      * Parses the string. Does not yet handle recursion (where
  526.      * the substituted strings contain {n} references.)
  527.      * @exception ParseException if the string can't be parsed.
  528.      */
  529.     public Object[] parse(String source) throws ParseException {
  530.         ParsePosition status  = new ParsePosition(0);
  531.         Object[] result = parse(source, status);
  532.         if (status.index == 0)  // unchanged, returned object is null
  533.             throw new ParseException("MessageFormat parse error!", 0);
  534.         return result;
  535.     }
  536.  
  537.     /**
  538.      * Parses the string. Does not yet handle recursion (where
  539.      * the substituted strings contain %n references.)
  540.      */
  541.     public Object parseObject (String text, ParsePosition status) {
  542.         return parse(text, status);
  543.     }
  544.  
  545.     /**
  546.      * Overrides Cloneable
  547.      */
  548.     public Object clone()
  549.     {
  550.         MessageFormat other = (MessageFormat) super.clone();
  551.  
  552.         // clone arrays. Can't do with utility because of bug in Cloneable
  553.         other.formats = (Format[]) formats.clone(); // shallow clone
  554.         for (int i = 0; i < formats.length; ++i) {
  555.             if (formats[i] != null)
  556.                 other.formats[i] = (Format)formats[i].clone();
  557.         }
  558.         // for primitives or immutables, shallow clone is enough
  559.         other.offsets = (int[]) offsets.clone();
  560.         other.argumentNumbers = (int[]) argumentNumbers.clone();
  561.  
  562.         return other;
  563.     }
  564.  
  565.     /**
  566.      * Equality comparision between two message format objects
  567.      */
  568.     public boolean equals(Object obj) {
  569.         if (this == obj)                      // quick check
  570.             return true;
  571.         if (getClass() != obj.getClass())
  572.             return false;
  573.         MessageFormat other = (MessageFormat) obj;
  574.         return (maxOffset == other.maxOffset
  575.                 && pattern.equals(other.pattern)
  576.             && Utility.objectEquals(locale, other.locale)    // does null check
  577.                 && Utility.arrayEquals(offsets,other.offsets)
  578.             && Utility.arrayEquals(argumentNumbers,other.argumentNumbers)
  579.             && Utility.arrayEquals(formats,other.formats));
  580.     }
  581.  
  582.     /**
  583.      * Generates a hash code for the message format object.
  584.      */
  585.     public int hashCode() {
  586.         return pattern.hashCode(); // enough for reasonable distribution
  587.     }
  588.  
  589.  
  590.     // ===========================privates============================
  591.  
  592.     // Mark : Is this the right fix?  (HS)
  593.     private Locale locale = Locale.getDefault();
  594.     private String pattern = "";
  595.     // later, allow more than ten items
  596.     private Format[] formats = new Format[10];
  597.     private int[] offsets = new int[10];
  598.     private int[] argumentNumbers = new int[10];
  599.     private int maxOffset = -1;
  600.  
  601.     /**
  602.      * Internal routine used by format.
  603.      * @param recursionProtection Initially zero. Bits 0..9 are used to indicate
  604.      * that a parameter has already been seen, to avoid recursion.  Currently
  605.      * unused.
  606.      */
  607.  
  608.     private StringBuffer format(Object[] arguments, StringBuffer result,
  609.                                 FieldPosition status, int recursionProtection) {
  610.         // note: this implementation assumes a fast substring & index.
  611.         // if this is not true, would be better to append chars one by one.
  612.         int lastOffset = 0;
  613.         for (int i = 0; i <= maxOffset; ++i) {
  614.             result.append(pattern.substring(lastOffset, offsets[i]));
  615.             lastOffset = offsets[i];
  616.             int argumentNumber = argumentNumbers[i];
  617.             if (argumentNumber >= arguments.length)
  618.                 throw new IllegalArgumentException("Argument # > Arg length");
  619.             // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
  620.             if (false) { // if (argRecursion == 3){
  621.                 // prevent loop!!!
  622.                 result.append('\uFFFD');
  623.             } else {
  624.                 Object obj = arguments[argumentNumber];
  625.                 String arg;
  626.                 boolean tryRecursion = false;
  627.                 if (formats[i] != null) {
  628.                     arg = formats[i].format(obj);
  629.                     tryRecursion = formats[i] instanceof ChoiceFormat;
  630.                 } else if (obj instanceof Number) {
  631.                     // format number if can
  632.                     arg = NumberFormat.getInstance(locale).format(obj);    // fix
  633.                 } else if (obj instanceof Date) {
  634.                     // format a Date if can
  635.                     arg = DateFormat.getDateTimeInstance(DateFormat.SHORT,
  636.                                                        DateFormat.SHORT,
  637.                                                        locale).format(obj);//fix
  638.                 } else if (obj instanceof String) {
  639.                     arg = (String) obj;
  640.                 } else {
  641.                     System.out.println("Unknown object of type:" +
  642.                                        obj.getClass().getName());
  643.                     throw new IllegalArgumentException("Unknown argument");
  644.                 }
  645.  
  646.                 // recurse if necessary
  647.                 if (tryRecursion && arg.indexOf('{') >= 0) {
  648.                     MessageFormat temp = new MessageFormat(arg);
  649.                     temp.format(arguments,result,status,recursionProtection);
  650.                 } else {
  651.                     result.append(arg);
  652.                 }
  653.             }
  654.         }
  655.         result.append(pattern.substring(lastOffset, pattern.length()));
  656.         return result;
  657.     }
  658.     private static final String[] typeList =
  659.     {"", "", "number", "", "date", "", "time", "", "choice"};
  660.     private static final String[] modifierList =
  661.     {"", "", "currency", "", "percent", "", "integer"};
  662.     private static final String[] dateModifierList =
  663.     {"", "", "short", "", "medium", "", "long", "", "full"};
  664.  
  665.     private void makeFormat(int position, int offsetNumber,
  666.                             StringBuffer[] segments)
  667.     {
  668.  
  669.         // get the number
  670.         int argumentNumber;
  671.         try {
  672.             argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized!
  673.             if (argumentNumber < 0 || argumentNumber > 9) {
  674.                 throw new NumberFormatException();
  675.             }
  676.             maxOffset = offsetNumber;
  677.             offsets[offsetNumber] = segments[0].length();
  678.             argumentNumbers[offsetNumber] = argumentNumber;
  679.         } catch (Exception e) {
  680.             throw new IllegalArgumentException("argument number too large at ");
  681.         }
  682.  
  683.         // now get the format
  684.         Format newFormat = null;
  685.         switch (findKeyword(segments[2].toString(), typeList)) {
  686.         case 0:
  687.             // string format
  688.             /*if (!segments[3].equals(""))
  689.               throw new IllegalArgumentException("can't modify string format, at ");
  690.               //*/
  691.         break;
  692.         case 1: case 2:// number
  693.             switch (findKeyword(segments[3].toString(), modifierList)) {
  694.             case 0: // default;
  695.                 newFormat = NumberFormat.getInstance(locale);
  696.                 break;
  697.             case 1: case 2:// currency
  698.                 newFormat = NumberFormat.getCurrencyInstance(locale);
  699.                 break;
  700.             case 3: case 4:// percent
  701.                 newFormat = NumberFormat.getPercentInstance(locale);
  702.                 break;
  703.             case 5: case 6:// integer
  704.                 newFormat = getIntegerFormat(locale);
  705.                 break;
  706.             default: // pattern
  707.                 newFormat = NumberFormat.getInstance(locale);
  708.                 try {
  709.                     ((DecimalFormat)newFormat).applyPattern(segments[3].toString());
  710.                 } catch (Exception e) {
  711.                     throw new IllegalArgumentException(
  712.                                              "Pattern incorrect or locale does not support formats, error at ");
  713.                 }
  714.                 break;
  715.             }
  716.             break;
  717.         case 3: case 4: // date
  718.             switch (findKeyword(segments[3].toString(), dateModifierList)) {
  719.             case 0: // default
  720.                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  721.                 break;
  722.             case 1: case 2: // short
  723.                 newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
  724.                 break;
  725.             case 3: case 4: // medium
  726.                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  727.                 break;
  728.             case 5: case 6: // long
  729.                 newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale);
  730.                 break;
  731.             case 7: case 8: // full
  732.                 newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale);
  733.                 break;
  734.             default:
  735.                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  736.                 try {
  737.                     ((SimpleDateFormat)newFormat).applyPattern(segments[3].toString());
  738.                 } catch (Exception e) {
  739.                     throw new IllegalArgumentException(
  740.                                              "Pattern incorrect or locale does not support formats, error at ");
  741.                 }
  742.                 break;
  743.             }
  744.             break;
  745.         case 5: case 6:// time
  746.             switch (findKeyword(segments[3].toString(), dateModifierList)) {
  747.             case 0: // default
  748.                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  749.                 break;
  750.             case 1: case 2: // short
  751.                 newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale);
  752.                 break;
  753.             case 3: case 4: // medium
  754.                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  755.                 break;
  756.             case 5: case 6: // long
  757.                 newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale);
  758.                 break;
  759.             case 7: case 8: // full
  760.                 newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale);
  761.                 break;
  762.             default:
  763.                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  764.                 try {
  765.                     ((SimpleDateFormat)newFormat).applyPattern(segments[3].toString());
  766.                 } catch (Exception e) {
  767.                     throw new IllegalArgumentException(
  768.                                              "Pattern incorrect or locale does not support formats, error at ");
  769.                 }
  770.                 break;
  771.             }
  772.             break;
  773.         case 7: case 8:// choice
  774.             try {
  775.                 newFormat = new ChoiceFormat(segments[3].toString());
  776.             } catch (Exception e) {
  777.                 throw new IllegalArgumentException(
  778.                                          "Choice Pattern incorrect, error at ");
  779.             }
  780.             break;
  781.         default:
  782.             throw new IllegalArgumentException("unknown format type at ");
  783.         }
  784.         formats[offsetNumber] = newFormat;
  785.         segments[1].setLength(0);    // throw away other segments
  786.         segments[2].setLength(0);
  787.         segments[3].setLength(0);
  788.     }
  789.  
  790.     private static final int findKeyword(String s, String[] list) {
  791.         s = s.trim().toLowerCase();
  792.         for (int i = 0; i < list.length; ++i) {
  793.             if (s.equals(list[i]))
  794.                 return i;
  795.         }
  796.         return -1;
  797.     }
  798.  
  799.     /**
  800.      * Convenience method that ought to be in NumberFormat
  801.      */
  802.     NumberFormat getIntegerFormat(Locale locale) {
  803.         NumberFormat temp = NumberFormat.getInstance(locale);
  804.         if (temp instanceof DecimalFormat) {
  805.             DecimalFormat temp2 = (DecimalFormat) temp;
  806.             temp2.setMaximumFractionDigits(0);
  807.             temp2.setDecimalSeparatorAlwaysShown(false);
  808.             temp2.setParseIntegerOnly(true);
  809.         }
  810.         return temp;
  811.     }
  812.  
  813.     private static final void copyAndFixQuotes(
  814.                                                String source, int start, int end, StringBuffer target) {
  815.         for (int i = start; i < end; ++i) {
  816.             char ch = source.charAt(i);
  817.             if (ch == '{') {
  818.                 target.append("'{'");
  819.             } else if (ch == '\'') {
  820.                 target.append("''");
  821.             } else {
  822.                 target.append(ch);
  823.             }
  824.         }
  825.     }
  826.  
  827. }
  828.