001/* MessageFormat.java - Localized message formatting.
002   Copyright (C) 1999, 2001, 2002, 2004, 2005, 2012 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    private Field()
168    {
169      super("");
170    }
171
172    protected Field(String s)
173    {
174      super(s);
175    }
176
177    /**
178     * invoked to resolve the true static constant by
179     * comparing the deserialized object to know name.
180     *
181     * @return object constant
182     */
183    protected Object readResolve() throws InvalidObjectException
184    {
185      if (getName().equals(ARGUMENT.getName()))
186        return ARGUMENT;
187
188      throw new InvalidObjectException("no such MessageFormat field called " + getName());
189    }
190
191  }
192
193  // Helper that returns the text up to the next format opener.  The
194  // text is put into BUFFER.  Returns index of character after end of
195  // string.  Throws IllegalArgumentException on error.
196  private static int scanString(String pat, int index, CPStringBuilder buffer)
197  {
198    int max = pat.length();
199    buffer.setLength(0);
200    boolean quoted = false;
201    for (; index < max; ++index)
202      {
203        char c = pat.charAt(index);
204        if (quoted)
205          {
206            // In a quoted context, a single quote ends the quoting.
207            if (c == '\'')
208              quoted = false;
209            else
210              buffer.append(c);
211          }
212        // Check for '', which is a single quote.
213        else if (c == '\'' && index + 1 < max && pat.charAt(index + 1) == '\'')
214          {
215            buffer.append(c);
216            ++index;
217          }
218        else if (c == '\'')
219          {
220            // Start quoting.
221            quoted = true;
222          }
223        else if (c == '{')
224          break;
225        else
226          buffer.append(c);
227      }
228    // Note that we explicitly allow an unterminated quote.  This is
229    // done for compatibility.
230    return index;
231  }
232
233  // This helper retrieves a single part of a format element.  Returns
234  // the index of the terminating character.
235  private static int scanFormatElement(String pat, int index,
236                                       CPStringBuilder buffer, char term)
237  {
238    int max = pat.length();
239    buffer.setLength(0);
240    int brace_depth = 1;
241    boolean quoted = false;
242
243    for (; index < max; ++index)
244      {
245        char c = pat.charAt(index);
246        // First see if we should turn off quoting.
247        if (quoted)
248          {
249            if (c == '\'')
250              quoted = false;
251            // In both cases we fall through to inserting the
252            // character here.
253          }
254        // See if we have just a plain quote to insert.
255        else if (c == '\'' && index + 1 < max
256                 && pat.charAt(index + 1) == '\'')
257          {
258            buffer.append(c);
259            ++index;
260          }
261        // See if quoting should turn on.
262        else if (c == '\'')
263          quoted = true;
264        else if (c == '{')
265          ++brace_depth;
266        else if (c == '}')
267          {
268            if (--brace_depth == 0)
269              break;
270          }
271        // Check for TERM after braces, because TERM might be `}'.
272        else if (c == term)
273          break;
274        // All characters, including opening and closing quotes, are
275        // inserted here.
276        buffer.append(c);
277      }
278    return index;
279  }
280
281  // This is used to parse a format element and whatever non-format
282  // text might trail it.
283  private static int scanFormat(String pat, int index, CPStringBuilder buffer,
284                                List<MessageFormatElement> elts, Locale locale)
285  {
286    MessageFormatElement mfe = new MessageFormatElement ();
287    elts.add(mfe);
288
289    int max = pat.length();
290
291    // Skip the opening `{'.
292    ++index;
293
294    // Fetch the argument number.
295    index = scanFormatElement (pat, index, buffer, ',');
296    try
297      {
298        mfe.argNumber = Integer.parseInt(buffer.toString());
299      }
300    catch (NumberFormatException nfx)
301      {
302        IllegalArgumentException iae = new IllegalArgumentException(pat);
303        iae.initCause(nfx);
304        throw iae;
305      }
306
307    // Extract the element format.
308    if (index < max && pat.charAt(index) == ',')
309      {
310        index = scanFormatElement (pat, index + 1, buffer, ',');
311        mfe.type = buffer.toString();
312
313        // Extract the style.
314        if (index < max && pat.charAt(index) == ',')
315          {
316            index = scanFormatElement (pat, index + 1, buffer, '}');
317            mfe.style = buffer.toString ();
318          }
319      }
320
321    // Advance past the last terminator.
322    if (index >= max || pat.charAt(index) != '}')
323      throw new IllegalArgumentException("Missing '}' at end of message format");
324    ++index;
325
326    // Now fetch trailing string.
327    index = scanString (pat, index, buffer);
328    mfe.trailer = buffer.toString ();
329
330    mfe.setLocale(locale);
331
332    return index;
333  }
334
335  /**
336   * Applies the specified pattern to this MessageFormat.
337   *
338   * @param newPattern The Pattern
339   */
340  public void applyPattern (String newPattern)
341  {
342    pattern = newPattern;
343
344    CPStringBuilder tempBuffer = new CPStringBuilder ();
345
346    int index = scanString (newPattern, 0, tempBuffer);
347    leader = tempBuffer.toString();
348
349    List<MessageFormatElement> elts = new ArrayList<MessageFormatElement>();
350    while (index < newPattern.length())
351      index = scanFormat (newPattern, index, tempBuffer, elts, locale);
352
353    elements = elts.toArray(new MessageFormatElement[elts.size()]);
354  }
355
356  /**
357   * Overrides Format.clone()
358   */
359  public Object clone ()
360  {
361    MessageFormat c = (MessageFormat) super.clone ();
362    c.elements = (MessageFormatElement[]) elements.clone ();
363    return c;
364  }
365
366  /**
367   * Overrides Format.equals(Object obj)
368   */
369  public boolean equals (Object obj)
370  {
371    if (! (obj instanceof MessageFormat))
372      return false;
373    MessageFormat mf = (MessageFormat) obj;
374    return (pattern.equals(mf.pattern)
375            && locale.equals(mf.locale));
376  }
377
378  /**
379   * A convinience method to format patterns.
380   *
381   * @param arguments The array containing the objects to be formatted.
382   */
383  public AttributedCharacterIterator formatToCharacterIterator (Object arguments)
384  {
385    Object[] arguments_array = (Object[])arguments;
386    FormatCharacterIterator iterator = new FormatCharacterIterator();
387
388    formatInternal(arguments_array, new StringBuffer(), null, iterator);
389
390    return iterator;
391  }
392
393  /**
394   * A convinience method to format patterns.
395   *
396   * @param pattern The pattern used when formatting.
397   * @param arguments The array containing the objects to be formatted.
398   */
399  public static String format (String pattern, Object... arguments)
400  {
401    MessageFormat mf = new MessageFormat (pattern);
402    StringBuffer sb = new StringBuffer ();
403    FieldPosition fp = new FieldPosition (NumberFormat.INTEGER_FIELD);
404    return mf.formatInternal(arguments, sb, fp, null).toString();
405  }
406
407  /**
408   * Returns the pattern with the formatted objects.
409   *
410   * @param arguments The array containing the objects to be formatted.
411   * @param appendBuf The StringBuffer where the text is appened.
412   * @param fp A FieldPosition object (it is ignored).
413   */
414  public final StringBuffer format (Object arguments[], StringBuffer appendBuf,
415                                    FieldPosition fp)
416  {
417    return formatInternal(arguments, appendBuf, fp, null);
418  }
419
420  private StringBuffer formatInternal (Object arguments[],
421                                       StringBuffer appendBuf,
422                                       FieldPosition fp,
423                                       FormatCharacterIterator output_iterator)
424  {
425    appendBuf.append(leader);
426    if (output_iterator != null)
427      output_iterator.append(leader);
428
429    for (int i = 0; i < elements.length; ++i)
430      {
431        Object thisArg = null;
432        boolean unavailable = false;
433        if (arguments == null || elements[i].argNumber >= arguments.length)
434          unavailable = true;
435        else
436          thisArg = arguments[elements[i].argNumber];
437
438        AttributedCharacterIterator iterator = null;
439
440        Format formatter = null;
441
442        if (fp != null && i == fp.getField() && fp.getFieldAttribute() == Field.ARGUMENT)
443          fp.setBeginIndex(appendBuf.length());
444
445        if (unavailable)
446          appendBuf.append("{" + elements[i].argNumber + "}");
447        else
448          {
449            if (elements[i].setFormat != null)
450              formatter = elements[i].setFormat;
451            else if (elements[i].format != null)
452              {
453                if (elements[i].formatClass != null
454                    && ! elements[i].formatClass.isInstance(thisArg))
455                  throw new IllegalArgumentException("Wrong format class");
456
457                formatter = elements[i].format;
458              }
459            else if (thisArg instanceof Number)
460              formatter = NumberFormat.getInstance(locale);
461            else if (thisArg instanceof Date)
462              formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
463            else
464              appendBuf.append(thisArg);
465          }
466
467        if (fp != null && fp.getField() == i && fp.getFieldAttribute() == Field.ARGUMENT)
468          fp.setEndIndex(appendBuf.length());
469
470        if (formatter != null)
471          {
472            // Special-case ChoiceFormat.
473            if (formatter instanceof ChoiceFormat)
474              {
475                StringBuffer buf = new StringBuffer ();
476                formatter.format(thisArg, buf, fp);
477                MessageFormat mf = new MessageFormat ();
478                mf.setLocale(locale);
479                mf.applyPattern(buf.toString());
480                mf.format(arguments, appendBuf, fp);
481              }
482            else
483              {
484                if (output_iterator != null)
485                  iterator = formatter.formatToCharacterIterator(thisArg);
486                else
487                  formatter.format(thisArg, appendBuf, fp);
488              }
489
490            elements[i].format = formatter;
491          }
492
493        if (output_iterator != null)
494          {
495            HashMap<MessageFormat.Field, Integer> hash_argument =
496              new HashMap<MessageFormat.Field, Integer>();
497            int position = output_iterator.getEndIndex();
498
499            hash_argument.put (MessageFormat.Field.ARGUMENT,
500                               Integer.valueOf(elements[i].argNumber));
501
502
503            if (iterator != null)
504              {
505                output_iterator.append(iterator);
506                output_iterator.addAttributes(hash_argument, position,
507                                              output_iterator.getEndIndex());
508              }
509            else
510              output_iterator.append(thisArg.toString(), hash_argument);
511
512            output_iterator.append(elements[i].trailer);
513          }
514
515        appendBuf.append(elements[i].trailer);
516      }
517
518    return appendBuf;
519  }
520
521  /**
522   * Returns the pattern with the formatted objects.  The first argument
523   * must be a array of Objects.
524   * This is equivalent to format((Object[]) objectArray, appendBuf, fpos)
525   *
526   * @param objectArray The object array to be formatted.
527   * @param appendBuf The StringBuffer where the text is appened.
528   * @param fpos A FieldPosition object (it is ignored).
529   */
530  public final StringBuffer format (Object objectArray, StringBuffer appendBuf,
531                                    FieldPosition fpos)
532  {
533    return format ((Object[])objectArray, appendBuf, fpos);
534  }
535
536  /**
537   * Returns an array with the Formats for
538   * the arguments.
539   */
540  public Format[] getFormats ()
541  {
542    Format[] f = new Format[elements.length];
543    for (int i = elements.length - 1; i >= 0; --i)
544      f[i] = elements[i].setFormat;
545    return f;
546  }
547
548  /**
549   * Returns the locale.
550   */
551  public Locale getLocale ()
552  {
553    return locale;
554  }
555
556  /**
557   * Overrides Format.hashCode()
558   */
559  public int hashCode ()
560  {
561    // FIXME: not a very good hash.
562    return pattern.hashCode() + locale.hashCode();
563  }
564
565  private MessageFormat ()
566  {
567  }
568
569  /**
570   * Creates a new MessageFormat object with
571   * the specified pattern
572   *
573   * @param pattern The Pattern
574   */
575  public MessageFormat(String pattern)
576  {
577    this(pattern, Locale.getDefault());
578  }
579
580  /**
581   * Creates a new MessageFormat object with
582   * the specified pattern
583   *
584   * @param pattern The Pattern
585   * @param locale The Locale to use
586   *
587   * @since 1.4
588   */
589  public MessageFormat(String pattern, Locale locale)
590  {
591    this.locale = locale;
592    applyPattern (pattern);
593  }
594
595  /**
596   * Parse a string <code>sourceStr</code> against the pattern specified
597   * to the MessageFormat constructor.
598   *
599   * @param sourceStr the string to be parsed.
600   * @param pos the current parse position (and eventually the error position).
601   * @return the array of parsed objects sorted according to their argument number
602   * in the pattern.
603   */
604  public Object[] parse (String sourceStr, ParsePosition pos)
605  {
606    // Check initial text.
607    int index = pos.getIndex();
608    if (! sourceStr.startsWith(leader, index))
609      {
610        pos.setErrorIndex(index);
611        return null;
612      }
613    index += leader.length();
614
615    ArrayList<Object> results = new ArrayList<Object>(elements.length);
616    // Now check each format.
617    for (int i = 0; i < elements.length; ++i)
618      {
619        Format formatter = null;
620        if (elements[i].setFormat != null)
621          formatter = elements[i].setFormat;
622        else if (elements[i].format != null)
623          formatter = elements[i].format;
624
625        Object value = null;
626        if (formatter instanceof ChoiceFormat)
627          {
628            // We must special-case a ChoiceFormat because it might
629            // have recursive formatting.
630            ChoiceFormat cf = (ChoiceFormat) formatter;
631            String[] formats = (String[]) cf.getFormats();
632            double[] limits = cf.getLimits();
633            MessageFormat subfmt = new MessageFormat ();
634            subfmt.setLocale(locale);
635            ParsePosition subpos = new ParsePosition (index);
636
637            int j;
638            for (j = 0; value == null && j < limits.length; ++j)
639              {
640                subfmt.applyPattern(formats[j]);
641                subpos.setIndex(index);
642                value = subfmt.parse(sourceStr, subpos);
643              }
644            if (value != null)
645              {
646                index = subpos.getIndex();
647                value = new Double (limits[j]);
648              }
649          }
650        else if (formatter != null)
651          {
652            pos.setIndex(index);
653            value = formatter.parseObject(sourceStr, pos);
654            if (value != null)
655              index = pos.getIndex();
656          }
657        else
658          {
659            // We have a String format.  This can lose in a number
660            // of ways, but we give it a shot.
661            int next_index;
662            if (elements[i].trailer.length() > 0)
663              next_index = sourceStr.indexOf(elements[i].trailer, index);
664            else
665              next_index = sourceStr.length();
666            if (next_index == -1)
667              {
668                pos.setErrorIndex(index);
669                return null;
670              }
671            value = sourceStr.substring(index, next_index);
672            index = next_index;
673          }
674
675        if (value == null
676            || ! sourceStr.startsWith(elements[i].trailer, index))
677          {
678            pos.setErrorIndex(index);
679            return null;
680          }
681
682        if (elements[i].argNumber >= results.size())
683          {
684            // Emulate padding behaviour of Vector.setSize() with ArrayList
685            results.ensureCapacity(elements[i].argNumber + 1);
686            for (int a = results.size(); a <= elements[i].argNumber; ++a)
687              results.add(a, null);
688          }
689        results.set(elements[i].argNumber, value);
690
691        index += elements[i].trailer.length();
692      }
693
694    return results.toArray(new Object[results.size()]);
695  }
696
697  public Object[] parse (String sourceStr) throws ParseException
698  {
699    ParsePosition pp = new ParsePosition (0);
700    Object[] r = parse (sourceStr, pp);
701    if (r == null)
702      throw new ParseException ("couldn't parse string", pp.getErrorIndex());
703    return r;
704  }
705
706  public Object parseObject (String sourceStr, ParsePosition pos)
707  {
708    return parse (sourceStr, pos);
709  }
710
711  /**
712   * Sets the format for the argument at an specified
713   * index.
714   *
715   * @param variableNum The index.
716   * @param newFormat The Format object.
717   */
718  public void setFormat (int variableNum, Format newFormat)
719  {
720    elements[variableNum].setFormat = newFormat;
721  }
722
723  /**
724   * Sets the formats for the arguments.
725   *
726   * @param newFormats An array of Format objects.
727   */
728  public void setFormats (Format[] newFormats)
729  {
730    if (newFormats.length < elements.length)
731      throw new IllegalArgumentException("Not enough format objects");
732
733    int len = Math.min(newFormats.length, elements.length);
734    for (int i = 0; i < len; ++i)
735      elements[i].setFormat = newFormats[i];
736  }
737
738  /**
739   * Sets the locale.
740   *
741   * @param loc A Locale
742   */
743  public void setLocale (Locale loc)
744  {
745    locale = loc;
746    if (elements != null)
747      {
748        for (int i = 0; i < elements.length; ++i)
749          elements[i].setLocale(loc);
750      }
751  }
752
753  /**
754   * Returns the pattern.
755   */
756  public String toPattern ()
757  {
758    return pattern;
759  }
760
761  /**
762   * Return the formatters used sorted by argument index. It uses the
763   * internal table to fill in this array: if a format has been
764   * set using <code>setFormat</code> or <code>setFormatByArgumentIndex</code>
765   * then it returns it at the right index. If not it uses the detected
766   * formatters during a <code>format</code> call. If nothing is known
767   * about that argument index it just puts null at that position.
768   * To get useful informations you may have to call <code>format</code>
769   * at least once.
770   *
771   * @return an array of formatters sorted by argument index.
772   */
773  public Format[] getFormatsByArgumentIndex()
774  {
775    int argNumMax = 0;
776    // First, find the greatest argument number.
777    for (int i=0;i<elements.length;i++)
778      if (elements[i].argNumber > argNumMax)
779        argNumMax = elements[i].argNumber;
780
781    Format[] formats = new Format[argNumMax];
782    for (int i=0;i<elements.length;i++)
783      {
784        if (elements[i].setFormat != null)
785          formats[elements[i].argNumber] = elements[i].setFormat;
786        else if (elements[i].format != null)
787          formats[elements[i].argNumber] = elements[i].format;
788      }
789    return formats;
790  }
791
792  /**
793   * Set the format to used using the argument index number.
794   *
795   * @param argumentIndex the argument index.
796   * @param newFormat the format to use for this argument.
797   */
798  public void setFormatByArgumentIndex(int argumentIndex,
799                                       Format newFormat)
800  {
801    for (int i=0;i<elements.length;i++)
802      {
803        if (elements[i].argNumber == argumentIndex)
804          elements[i].setFormat = newFormat;
805      }
806  }
807
808  /**
809   * Set the format for argument using a specified array of formatters
810   * which is sorted according to the argument index. If the number of
811   * elements in the array is fewer than the number of arguments only
812   * the arguments specified by the array are touched.
813   *
814   * @param newFormats array containing the new formats to set.
815   *
816   * @throws NullPointerException if newFormats is null
817   */
818  public void setFormatsByArgumentIndex(Format[] newFormats)
819  {
820    for (int i=0;i<newFormats.length;i++)
821      {
822        // Nothing better than that can exist here.
823        setFormatByArgumentIndex(i, newFormats[i]);
824      }
825  }
826
827  // The pattern string.
828  private String pattern;
829  // The locale.
830  private Locale locale;
831  // Variables.
832  private MessageFormatElement[] elements;
833  // Leader text.
834  private String leader;
835}