001/* MessageFormat.java - Localized message formatting.
002   Copyright (C) 1999, 2001, 2002, 2004, 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010 
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.text;
040
041import gnu.java.lang.CPStringBuilder;
042
043import gnu.java.text.FormatCharacterIterator;
044
045import java.io.InvalidObjectException;
046
047import java.util.ArrayList;
048import java.util.Date;
049import java.util.HashMap;
050import java.util.List;
051import java.util.Locale;
052
053public class MessageFormat extends Format
054{
055  /**
056   * @author Tom Tromey (tromey@cygnus.com)
057   * @author Jorge Aliss (jaliss@hotmail.com)
058   * @date March 3, 1999
059   */
060  /* Written using "Java Class Libraries", 2nd edition, plus online
061   * API docs for JDK 1.2 from http://www.javasoft.com.
062   * Status:  Believed complete and correct to 1.2, except serialization.
063   *          and parsing.
064   */
065  private static final class MessageFormatElement
066  {
067    // Argument number.
068    int argNumber;
069    // Formatter to be used.  This is the format set by setFormat.
070    Format setFormat;
071    // Formatter to be used based on the type.
072    Format format;
073
074    // Argument will be checked to make sure it is an instance of this
075    // class.
076    Class<?> formatClass;
077
078    // Formatter type.
079    String type;
080    // Formatter style.
081    String style;
082
083    // Text to follow this element.
084    String trailer;
085
086    // Recompute the locale-based formatter.
087    void setLocale (Locale loc)
088    {
089      if (type != null)
090        {
091          if (type.equals("number"))
092            {
093              formatClass = java.lang.Number.class;
094
095              if (style == null)
096                format = NumberFormat.getInstance(loc);
097              else if (style.equals("currency"))
098                format = NumberFormat.getCurrencyInstance(loc);
099              else if (style.equals("percent"))
100                format = NumberFormat.getPercentInstance(loc);
101              else if (style.equals("integer"))
102                format = NumberFormat.getIntegerInstance(loc);
103              else
104                {
105                  format = NumberFormat.getNumberInstance(loc);
106                  DecimalFormat df = (DecimalFormat) format;
107                  df.applyPattern(style);
108                }
109            }
110          else if (type.equals("time") || type.equals("date"))
111            {
112              formatClass = java.util.Date.class;
113
114              int val = DateFormat.DEFAULT;
115              boolean styleIsPattern = false;
116              if (style != null)
117                {
118                  if (style.equals("short"))
119                    val = DateFormat.SHORT;
120                  else if (style.equals("medium"))
121                    val = DateFormat.MEDIUM;
122                  else if (style.equals("long"))
123                    val = DateFormat.LONG;
124                  else if (style.equals("full"))
125                    val = DateFormat.FULL;
126                  else
127                    styleIsPattern = true;
128                }
129          
130              if (type.equals("time"))
131                format = DateFormat.getTimeInstance(val, loc);
132              else
133                format = DateFormat.getDateInstance(val, loc);
134
135              if (styleIsPattern)
136                {
137                  SimpleDateFormat sdf = (SimpleDateFormat) format;
138                  sdf.applyPattern(style);
139                }
140            }
141          else if (type.equals("choice"))
142            {
143              formatClass = java.lang.Number.class;
144
145              if (style == null)
146                throw new
147                IllegalArgumentException ("style required for choice format");
148              format = new ChoiceFormat (style);
149            }
150        }
151    }
152  }
153
154  private static final long serialVersionUID = 6479157306784022952L;
155
156  public static class Field extends Format.Field
157  {
158    static final long serialVersionUID = 7899943957617360810L;
159
160    /**
161     * This is the attribute set for all characters produced
162     * by MessageFormat during a formatting.
163     */
164    public static final MessageFormat.Field ARGUMENT = new MessageFormat.Field("argument");
165
166    // For deserialization
167    @SuppressWarnings("unused")
168    private Field()
169    {
170      super("");
171    }
172    
173    protected Field(String s)
174    {
175      super(s);
176    }
177
178    /**
179     * invoked to resolve the true static constant by
180     * comparing the deserialized object to know name.
181     *
182     * @return object constant
183     */
184    protected Object readResolve() throws InvalidObjectException
185    {
186      if (getName().equals(ARGUMENT.getName()))
187        return ARGUMENT;
188
189      throw new InvalidObjectException("no such MessageFormat field called " + getName());
190    }
191
192  }
193
194  // Helper that returns the text up to the next format opener.  The
195  // text is put into BUFFER.  Returns index of character after end of
196  // string.  Throws IllegalArgumentException on error.
197  private static int scanString(String pat, int index, CPStringBuilder buffer)
198  {
199    int max = pat.length();
200    buffer.setLength(0);
201    boolean quoted = false;
202    for (; index < max; ++index)
203      {
204        char c = pat.charAt(index);
205        if (quoted)
206          {
207            // In a quoted context, a single quote ends the quoting.
208            if (c == '\'')
209              quoted = false;
210            else
211              buffer.append(c);
212          }
213        // Check for '', which is a single quote.
214        else if (c == '\'' && index + 1 < max && pat.charAt(index + 1) == '\'')
215          {
216            buffer.append(c);
217            ++index;
218          }
219        else if (c == '\'')
220          {
221            // Start quoting.
222            quoted = true;
223          }
224        else if (c == '{')
225          break;
226        else
227          buffer.append(c);
228      }
229    // Note that we explicitly allow an unterminated quote.  This is
230    // done for compatibility.
231    return index;
232  }
233
234  // This helper retrieves a single part of a format element.  Returns
235  // the index of the terminating character.
236  private static int scanFormatElement(String pat, int index,
237                                       CPStringBuilder buffer, char term)
238  {
239    int max = pat.length();
240    buffer.setLength(0);
241    int brace_depth = 1;
242    boolean quoted = false;
243
244    for (; index < max; ++index)
245      {
246        char c = pat.charAt(index);
247        // First see if we should turn off quoting.
248        if (quoted)
249          {
250            if (c == '\'')
251              quoted = false;
252            // In both cases we fall through to inserting the
253            // character here.
254          }
255        // See if we have just a plain quote to insert.
256        else if (c == '\'' && index + 1 < max
257                 && pat.charAt(index + 1) == '\'')
258          {
259            buffer.append(c);
260            ++index;
261          }
262        // See if quoting should turn on.
263        else if (c == '\'')
264          quoted = true;
265        else if (c == '{')
266          ++brace_depth;
267        else if (c == '}')
268          {
269            if (--brace_depth == 0)
270              break;
271          }
272        // Check for TERM after braces, because TERM might be `}'.
273        else if (c == term)
274          break;
275        // All characters, including opening and closing quotes, are
276        // inserted here.
277        buffer.append(c);
278      }
279    return index;
280  }
281
282  // This is used to parse a format element and whatever non-format
283  // text might trail it.
284  private static int scanFormat(String pat, int index, CPStringBuilder buffer,
285                                List<MessageFormatElement> elts, Locale locale)
286  {
287    MessageFormatElement mfe = new MessageFormatElement ();
288    elts.add(mfe);
289
290    int max = pat.length();
291
292    // Skip the opening `{'.
293    ++index;
294
295    // Fetch the argument number.
296    index = scanFormatElement (pat, index, buffer, ',');
297    try
298      {
299        mfe.argNumber = Integer.parseInt(buffer.toString());
300      }
301    catch (NumberFormatException nfx)
302      {
303        IllegalArgumentException iae = new IllegalArgumentException(pat);
304        iae.initCause(nfx);
305        throw iae;
306      }
307
308    // Extract the element format.
309    if (index < max && pat.charAt(index) == ',')
310      {
311        index = scanFormatElement (pat, index + 1, buffer, ',');
312        mfe.type = buffer.toString();
313
314        // Extract the style.
315        if (index < max && pat.charAt(index) == ',')
316          {
317            index = scanFormatElement (pat, index + 1, buffer, '}');
318            mfe.style = buffer.toString ();
319          }
320      }
321
322    // Advance past the last terminator.
323    if (index >= max || pat.charAt(index) != '}')
324      throw new IllegalArgumentException("Missing '}' at end of message format");
325    ++index;
326
327    // Now fetch trailing string.
328    index = scanString (pat, index, buffer);
329    mfe.trailer = buffer.toString ();
330
331    mfe.setLocale(locale);
332
333    return index;
334  }
335
336  /**
337   * Applies the specified pattern to this MessageFormat.
338   *
339   * @param newPattern The Pattern
340   */
341  public void applyPattern (String newPattern)
342  {
343    pattern = newPattern;
344
345    CPStringBuilder tempBuffer = new CPStringBuilder ();
346
347    int index = scanString (newPattern, 0, tempBuffer);
348    leader = tempBuffer.toString();
349
350    List<MessageFormatElement> elts = new ArrayList<MessageFormatElement>();
351    while (index < newPattern.length())
352      index = scanFormat (newPattern, index, tempBuffer, elts, locale);
353
354    elements = elts.toArray(new MessageFormatElement[elts.size()]);
355  }
356
357  /**
358   * Overrides Format.clone()
359   */
360  public Object clone ()
361  {
362    MessageFormat c = (MessageFormat) super.clone ();
363    c.elements = (MessageFormatElement[]) elements.clone ();
364    return c;
365  }
366
367  /**
368   * Overrides Format.equals(Object obj)
369   */
370  public boolean equals (Object obj)
371  {
372    if (! (obj instanceof MessageFormat))
373      return false;
374    MessageFormat mf = (MessageFormat) obj;
375    return (pattern.equals(mf.pattern)
376            && locale.equals(mf.locale));
377  }
378
379  /**
380   * A convinience method to format patterns.
381   *
382   * @param arguments The array containing the objects to be formatted.
383   */
384  public AttributedCharacterIterator formatToCharacterIterator (Object arguments)
385  {
386    Object[] arguments_array = (Object[])arguments;
387    FormatCharacterIterator iterator = new FormatCharacterIterator();
388    
389    formatInternal(arguments_array, new StringBuffer(), null, iterator);
390  
391    return iterator;
392  }
393
394  /**
395   * A convinience method to format patterns.
396   *
397   * @param pattern The pattern used when formatting.
398   * @param arguments The array containing the objects to be formatted.
399   */
400  public static String format (String pattern, Object... arguments)
401  {
402    MessageFormat mf = new MessageFormat (pattern);
403    StringBuffer sb = new StringBuffer ();
404    FieldPosition fp = new FieldPosition (NumberFormat.INTEGER_FIELD);
405    return mf.formatInternal(arguments, sb, fp, null).toString();
406  }
407
408  /**
409   * Returns the pattern with the formatted objects.
410   *
411   * @param arguments The array containing the objects to be formatted.
412   * @param appendBuf The StringBuffer where the text is appened.
413   * @param fp A FieldPosition object (it is ignored).
414   */
415  public final StringBuffer format (Object arguments[], StringBuffer appendBuf,
416                                    FieldPosition fp)
417  {
418    return formatInternal(arguments, appendBuf, fp, null);
419  }
420
421  private StringBuffer formatInternal (Object arguments[],
422                                       StringBuffer appendBuf,
423                                       FieldPosition fp,
424                                       FormatCharacterIterator output_iterator)
425  {
426    appendBuf.append(leader);
427    if (output_iterator != null)
428      output_iterator.append(leader);
429
430    for (int i = 0; i < elements.length; ++i)
431      {
432        Object thisArg = null;
433        boolean unavailable = false;
434        if (arguments == null || elements[i].argNumber >= arguments.length)
435          unavailable = true;
436        else
437          thisArg = arguments[elements[i].argNumber];
438
439        AttributedCharacterIterator iterator = null;
440
441        Format formatter = null;
442
443        if (fp != null && i == fp.getField() && fp.getFieldAttribute() == Field.ARGUMENT)
444          fp.setBeginIndex(appendBuf.length());
445
446        if (unavailable)
447          appendBuf.append("{" + elements[i].argNumber + "}");
448        else
449          {
450            if (elements[i].setFormat != null)
451              formatter = elements[i].setFormat;
452            else if (elements[i].format != null)
453              {
454                if (elements[i].formatClass != null
455                    && ! elements[i].formatClass.isInstance(thisArg))
456                  throw new IllegalArgumentException("Wrong format class");
457            
458                formatter = elements[i].format;
459              }
460            else if (thisArg instanceof Number)
461              formatter = NumberFormat.getInstance(locale);
462            else if (thisArg instanceof Date)
463              formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
464            else
465              appendBuf.append(thisArg);
466          }
467
468        if (fp != null && fp.getField() == i && fp.getFieldAttribute() == Field.ARGUMENT)
469          fp.setEndIndex(appendBuf.length());
470
471        if (formatter != null)
472          {
473            // Special-case ChoiceFormat.
474            if (formatter instanceof ChoiceFormat)
475              {
476                StringBuffer buf = new StringBuffer ();
477                formatter.format(thisArg, buf, fp);
478                MessageFormat mf = new MessageFormat ();
479                mf.setLocale(locale);
480                mf.applyPattern(buf.toString());
481                mf.format(arguments, appendBuf, fp);
482              }
483            else
484              {
485                if (output_iterator != null)
486                  iterator = formatter.formatToCharacterIterator(thisArg);
487                else
488                  formatter.format(thisArg, appendBuf, fp);
489              }
490
491            elements[i].format = formatter;
492          }
493
494        if (output_iterator != null)
495          {
496            HashMap<MessageFormat.Field, Integer> hash_argument =
497              new HashMap<MessageFormat.Field, Integer>();
498            int position = output_iterator.getEndIndex();
499            
500            hash_argument.put (MessageFormat.Field.ARGUMENT,
501                               Integer.valueOf(elements[i].argNumber));
502
503            
504            if (iterator != null)
505              {
506                output_iterator.append(iterator);
507                output_iterator.addAttributes(hash_argument, position, 
508                                              output_iterator.getEndIndex());
509              } 
510            else
511              output_iterator.append(thisArg.toString(), hash_argument);
512            
513            output_iterator.append(elements[i].trailer);
514          }
515        
516        appendBuf.append(elements[i].trailer);
517      }
518    
519    return appendBuf;
520  }
521
522  /**
523   * Returns the pattern with the formatted objects.  The first argument
524   * must be a array of Objects.
525   * This is equivalent to format((Object[]) objectArray, appendBuf, fpos)
526   *
527   * @param objectArray The object array to be formatted.
528   * @param appendBuf The StringBuffer where the text is appened.
529   * @param fpos A FieldPosition object (it is ignored).
530   */
531  public final StringBuffer format (Object objectArray, StringBuffer appendBuf,
532                                    FieldPosition fpos)
533  {
534    return format ((Object[])objectArray, appendBuf, fpos);
535  }
536
537  /**
538   * Returns an array with the Formats for
539   * the arguments.
540   */
541  public Format[] getFormats ()
542  {
543    Format[] f = new Format[elements.length];
544    for (int i = elements.length - 1; i >= 0; --i)
545      f[i] = elements[i].setFormat;
546    return f;
547  }
548
549  /**
550   * Returns the locale.
551   */
552  public Locale getLocale ()
553  {
554    return locale;
555  }
556
557  /**
558   * Overrides Format.hashCode()
559   */
560  public int hashCode ()
561  {
562    // FIXME: not a very good hash.
563    return pattern.hashCode() + locale.hashCode();
564  }
565
566  private MessageFormat ()
567  {
568  }
569
570  /**
571   * Creates a new MessageFormat object with
572   * the specified pattern
573   *
574   * @param pattern The Pattern
575   */
576  public MessageFormat(String pattern)
577  {
578    this(pattern, Locale.getDefault());
579  }
580
581  /**
582   * Creates a new MessageFormat object with
583   * the specified pattern
584   *
585   * @param pattern The Pattern
586   * @param locale The Locale to use
587   *
588   * @since 1.4
589   */
590  public MessageFormat(String pattern, Locale locale)
591  {
592    this.locale = locale;
593    applyPattern (pattern);
594  }
595
596  /**
597   * Parse a string <code>sourceStr</code> against the pattern specified
598   * to the MessageFormat constructor.
599   *
600   * @param sourceStr the string to be parsed.
601   * @param pos the current parse position (and eventually the error position).
602   * @return the array of parsed objects sorted according to their argument number
603   * in the pattern.
604   */ 
605  public Object[] parse (String sourceStr, ParsePosition pos)
606  {
607    // Check initial text.
608    int index = pos.getIndex();
609    if (! sourceStr.startsWith(leader, index))
610      {
611        pos.setErrorIndex(index);
612        return null;
613      }
614    index += leader.length();
615
616    ArrayList<Object> results = new ArrayList<Object>(elements.length);
617    // Now check each format.
618    for (int i = 0; i < elements.length; ++i)
619      {
620        Format formatter = null;
621        if (elements[i].setFormat != null)
622          formatter = elements[i].setFormat;
623        else if (elements[i].format != null)
624          formatter = elements[i].format;
625
626        Object value = null;
627        if (formatter instanceof ChoiceFormat)
628          {
629            // We must special-case a ChoiceFormat because it might
630            // have recursive formatting.
631            ChoiceFormat cf = (ChoiceFormat) formatter;
632            String[] formats = (String[]) cf.getFormats();
633            double[] limits = cf.getLimits();
634            MessageFormat subfmt = new MessageFormat ();
635            subfmt.setLocale(locale);
636            ParsePosition subpos = new ParsePosition (index);
637
638            int j;
639            for (j = 0; value == null && j < limits.length; ++j)
640              {
641                subfmt.applyPattern(formats[j]);
642                subpos.setIndex(index);
643                value = subfmt.parse(sourceStr, subpos);
644              }
645            if (value != null)
646              {
647                index = subpos.getIndex();
648                value = new Double (limits[j]);
649              }
650          }
651        else if (formatter != null)
652          {
653            pos.setIndex(index);
654            value = formatter.parseObject(sourceStr, pos);
655            if (value != null)
656              index = pos.getIndex();
657          }
658        else
659          {
660            // We have a String format.  This can lose in a number
661            // of ways, but we give it a shot.
662            int next_index;
663            if (elements[i].trailer.length() > 0)
664              next_index = sourceStr.indexOf(elements[i].trailer, index);
665            else
666              next_index = sourceStr.length();
667            if (next_index == -1)
668              {
669                pos.setErrorIndex(index);
670                return null;
671              }
672            value = sourceStr.substring(index, next_index);
673            index = next_index;
674          }
675
676        if (value == null
677            || ! sourceStr.startsWith(elements[i].trailer, index))
678          {
679            pos.setErrorIndex(index);
680            return null;
681          }
682
683        if (elements[i].argNumber >= results.size())
684          {
685            // Emulate padding behaviour of Vector.setSize() with ArrayList
686            results.ensureCapacity(elements[i].argNumber + 1);
687            for (int a = results.size(); a <= elements[i].argNumber; ++a)
688              results.add(a, null);
689          }
690        results.set(elements[i].argNumber, value);
691
692        index += elements[i].trailer.length();
693      }
694
695    return results.toArray(new Object[results.size()]);
696  }
697
698  public Object[] parse (String sourceStr) throws ParseException
699  {
700    ParsePosition pp = new ParsePosition (0);
701    Object[] r = parse (sourceStr, pp);
702    if (r == null)
703      throw new ParseException ("couldn't parse string", pp.getErrorIndex());
704    return r;
705  }
706
707  public Object parseObject (String sourceStr, ParsePosition pos)
708  {
709    return parse (sourceStr, pos);
710  }
711
712  /**
713   * Sets the format for the argument at an specified
714   * index.
715   *
716   * @param variableNum The index.
717   * @param newFormat The Format object.
718   */
719  public void setFormat (int variableNum, Format newFormat)
720  {
721    elements[variableNum].setFormat = newFormat;
722  }
723
724  /**
725   * Sets the formats for the arguments.
726   *
727   * @param newFormats An array of Format objects.
728   */
729  public void setFormats (Format[] newFormats)
730  {
731    if (newFormats.length < elements.length)
732      throw new IllegalArgumentException("Not enough format objects");
733
734    int len = Math.min(newFormats.length, elements.length);
735    for (int i = 0; i < len; ++i)
736      elements[i].setFormat = newFormats[i];
737  }
738
739  /**
740   * Sets the locale.
741   *
742   * @param loc A Locale
743   */
744  public void setLocale (Locale loc)
745  {
746    locale = loc;
747    if (elements != null)
748      {
749        for (int i = 0; i < elements.length; ++i)
750          elements[i].setLocale(loc);
751      }
752  }
753
754  /**
755   * Returns the pattern.
756   */
757  public String toPattern ()
758  {
759    return pattern;
760  }
761
762  /**
763   * Return the formatters used sorted by argument index. It uses the
764   * internal table to fill in this array: if a format has been
765   * set using <code>setFormat</code> or <code>setFormatByArgumentIndex</code>
766   * then it returns it at the right index. If not it uses the detected
767   * formatters during a <code>format</code> call. If nothing is known
768   * about that argument index it just puts null at that position.
769   * To get useful informations you may have to call <code>format</code>
770   * at least once.
771   *
772   * @return an array of formatters sorted by argument index.
773   */
774  public Format[] getFormatsByArgumentIndex()
775  {
776    int argNumMax = 0;
777    // First, find the greatest argument number.
778    for (int i=0;i<elements.length;i++)
779      if (elements[i].argNumber > argNumMax)
780        argNumMax = elements[i].argNumber;
781
782    Format[] formats = new Format[argNumMax];
783    for (int i=0;i<elements.length;i++)
784      {
785        if (elements[i].setFormat != null)
786          formats[elements[i].argNumber] = elements[i].setFormat;
787        else if (elements[i].format != null)
788          formats[elements[i].argNumber] = elements[i].format;
789      }
790    return formats;
791  }
792
793  /**
794   * Set the format to used using the argument index number.
795   *
796   * @param argumentIndex the argument index.
797   * @param newFormat the format to use for this argument.
798   */
799  public void setFormatByArgumentIndex(int argumentIndex,
800                                       Format newFormat)
801  {
802    for (int i=0;i<elements.length;i++)
803      {
804        if (elements[i].argNumber == argumentIndex)
805          elements[i].setFormat = newFormat;
806      }
807  }
808
809  /**
810   * Set the format for argument using a specified array of formatters
811   * which is sorted according to the argument index. If the number of
812   * elements in the array is fewer than the number of arguments only
813   * the arguments specified by the array are touched.
814   *
815   * @param newFormats array containing the new formats to set.
816   *
817   * @throws NullPointerException if newFormats is null
818   */
819  public void setFormatsByArgumentIndex(Format[] newFormats)
820  {
821    for (int i=0;i<newFormats.length;i++)
822      {
823        // Nothing better than that can exist here.
824        setFormatByArgumentIndex(i, newFormats[i]);
825      }
826  }
827
828  // The pattern string.
829  private String pattern;
830  // The locale.
831  private Locale locale;
832  // Variables.
833  private MessageFormatElement[] elements;
834  // Leader text.
835  private String leader;
836}