001    /* MessageFormat.java - Localized message formatting.
002       Copyright (C) 1999, 2001, 2002, 2004, 2005 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010     
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.text;
040    
041    import gnu.java.text.FormatCharacterIterator;
042    
043    import java.io.InvalidObjectException;
044    import java.util.Date;
045    import java.util.HashMap;
046    import java.util.Locale;
047    import java.util.Vector;
048    
049    public class MessageFormat extends Format
050    {
051      /**
052       * @author Tom Tromey (tromey@cygnus.com)
053       * @author Jorge Aliss (jaliss@hotmail.com)
054       * @date March 3, 1999
055       */
056      /* Written using "Java Class Libraries", 2nd edition, plus online
057       * API docs for JDK 1.2 from http://www.javasoft.com.
058       * Status:  Believed complete and correct to 1.2, except serialization.
059       *          and parsing.
060       */
061      private static final class MessageFormatElement
062      {
063        // Argument number.
064        int argNumber;
065        // Formatter to be used.  This is the format set by setFormat.
066        Format setFormat;
067        // Formatter to be used based on the type.
068        Format format;
069    
070        // Argument will be checked to make sure it is an instance of this
071        // class.
072        Class formatClass;
073    
074        // Formatter type.
075        String type;
076        // Formatter style.
077        String style;
078    
079        // Text to follow this element.
080        String trailer;
081    
082        // Recompute the locale-based formatter.
083        void setLocale (Locale loc)
084        {
085          if (type != null)
086            {
087              if (type.equals("number"))
088                {
089                  formatClass = java.lang.Number.class;
090    
091                  if (style == null)
092                    format = NumberFormat.getInstance(loc);
093                  else if (style.equals("currency"))
094                    format = NumberFormat.getCurrencyInstance(loc);
095                  else if (style.equals("percent"))
096                    format = NumberFormat.getPercentInstance(loc);
097                  else if (style.equals("integer"))
098                    {
099                      NumberFormat nf = NumberFormat.getNumberInstance(loc);
100                      nf.setMaximumFractionDigits(0);
101                      nf.setGroupingUsed(false);
102                      format = nf;
103                    }
104                  else
105                    {
106                      format = NumberFormat.getNumberInstance(loc);
107                      DecimalFormat df = (DecimalFormat) format;
108                      df.applyPattern(style);
109                    }
110                }
111              else if (type.equals("time") || type.equals("date"))
112                {
113                  formatClass = java.util.Date.class;
114    
115                  int val = DateFormat.DEFAULT;
116                  boolean styleIsPattern = false;
117                  if (style != null)
118                    {
119                      if (style.equals("short"))
120                        val = DateFormat.SHORT;
121                      else if (style.equals("medium"))
122                        val = DateFormat.MEDIUM;
123                      else if (style.equals("long"))
124                        val = DateFormat.LONG;
125                      else if (style.equals("full"))
126                        val = DateFormat.FULL;
127                      else
128                        styleIsPattern = true;
129                    }
130              
131                  if (type.equals("time"))
132                    format = DateFormat.getTimeInstance(val, loc);
133                  else
134                    format = DateFormat.getDateInstance(val, loc);
135    
136                  if (styleIsPattern)
137                    {
138                      SimpleDateFormat sdf = (SimpleDateFormat) format;
139                      sdf.applyPattern(style);
140                    }
141                }
142              else if (type.equals("choice"))
143                {
144                  formatClass = java.lang.Number.class;
145    
146                  if (style == null)
147                    throw new
148                    IllegalArgumentException ("style required for choice format");
149                  format = new ChoiceFormat (style);
150                }
151            }
152        }
153      }
154    
155      private static final long serialVersionUID = 6479157306784022952L;
156    
157      public static class Field extends Format.Field
158      {
159        static final long serialVersionUID = 7899943957617360810L;
160    
161        /**
162         * This is the attribute set for all characters produced
163         * by MessageFormat during a formatting.
164         */
165        public static final MessageFormat.Field ARGUMENT = new MessageFormat.Field("argument");
166    
167        // For deserialization
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, StringBuffer 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                                           StringBuffer 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, StringBuffer buffer,
285                                    Vector elts, Locale locale)
286      {
287        MessageFormatElement mfe = new MessageFormatElement ();
288        elts.addElement(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        StringBuffer tempBuffer = new StringBuffer ();
346    
347        int index = scanString (newPattern, 0, tempBuffer);
348        leader = tempBuffer.toString();
349    
350        Vector elts = new Vector ();
351        while (index < newPattern.length())
352          index = scanFormat (newPattern, index, tempBuffer, elts, locale);
353    
354        elements = new MessageFormatElement[elts.size()];
355        elts.copyInto(elements);
356      }
357    
358      /**
359       * Overrides Format.clone()
360       */
361      public Object clone ()
362      {
363        MessageFormat c = (MessageFormat) super.clone ();
364        c.elements = (MessageFormatElement[]) elements.clone ();
365        return c;
366      }
367    
368      /**
369       * Overrides Format.equals(Object obj)
370       */
371      public boolean equals (Object obj)
372      {
373        if (! (obj instanceof MessageFormat))
374          return false;
375        MessageFormat mf = (MessageFormat) obj;
376        return (pattern.equals(mf.pattern)
377                && locale.equals(mf.locale));
378      }
379    
380      /**
381       * A convinience method to format patterns.
382       *
383       * @param arguments The array containing the objects to be formatted.
384       */
385      public AttributedCharacterIterator formatToCharacterIterator (Object arguments)
386      {
387        Object[] arguments_array = (Object[])arguments;
388        FormatCharacterIterator iterator = new FormatCharacterIterator();
389        
390        formatInternal(arguments_array, new StringBuffer(), null, iterator);
391      
392        return iterator;
393      }
394    
395      /**
396       * A convinience method to format patterns.
397       *
398       * @param pattern The pattern used when formatting.
399       * @param arguments The array containing the objects to be formatted.
400       */
401      public static String format (String pattern, Object... arguments)
402      {
403        MessageFormat mf = new MessageFormat (pattern);
404        StringBuffer sb = new StringBuffer ();
405        FieldPosition fp = new FieldPosition (NumberFormat.INTEGER_FIELD);
406        return mf.formatInternal(arguments, sb, fp, null).toString();
407      }
408    
409      /**
410       * Returns the pattern with the formatted objects.
411       *
412       * @param arguments The array containing the objects to be formatted.
413       * @param appendBuf The StringBuffer where the text is appened.
414       * @param fp A FieldPosition object (it is ignored).
415       */
416      public final StringBuffer format (Object arguments[], StringBuffer appendBuf,
417                                        FieldPosition fp)
418      {
419        return formatInternal(arguments, appendBuf, fp, null);
420      }
421    
422      private StringBuffer formatInternal (Object arguments[],
423                                           StringBuffer appendBuf,
424                                           FieldPosition fp,
425                                           FormatCharacterIterator output_iterator)
426      {
427        appendBuf.append(leader);
428        if (output_iterator != null)
429          output_iterator.append(leader);
430    
431        for (int i = 0; i < elements.length; ++i)
432          {
433            Object thisArg = null;
434            boolean unavailable = false;
435            if (arguments == null || elements[i].argNumber >= arguments.length)
436              unavailable = true;
437            else
438              thisArg = arguments[elements[i].argNumber];
439    
440            AttributedCharacterIterator iterator = null;
441    
442            Format formatter = null;
443    
444            if (fp != null && i == fp.getField() && fp.getFieldAttribute() == Field.ARGUMENT)
445              fp.setBeginIndex(appendBuf.length());
446    
447            if (unavailable)
448              appendBuf.append("{" + elements[i].argNumber + "}");
449            else
450              {
451                if (elements[i].setFormat != null)
452                  formatter = elements[i].setFormat;
453                else if (elements[i].format != null)
454                  {
455                    if (elements[i].formatClass != null
456                        && ! elements[i].formatClass.isInstance(thisArg))
457                      throw new IllegalArgumentException("Wrong format class");
458                
459                    formatter = elements[i].format;
460                  }
461                else if (thisArg instanceof Number)
462                  formatter = NumberFormat.getInstance(locale);
463                else if (thisArg instanceof Date)
464                  formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
465                else
466                  appendBuf.append(thisArg);
467              }
468    
469            if (fp != null && fp.getField() == i && fp.getFieldAttribute() == Field.ARGUMENT)
470              fp.setEndIndex(appendBuf.length());
471    
472            if (formatter != null)
473              {
474                // Special-case ChoiceFormat.
475                if (formatter instanceof ChoiceFormat)
476                  {
477                    StringBuffer buf = new StringBuffer ();
478                    formatter.format(thisArg, buf, fp);
479                    MessageFormat mf = new MessageFormat ();
480                    mf.setLocale(locale);
481                    mf.applyPattern(buf.toString());
482                    mf.format(arguments, appendBuf, fp);
483                  }
484                else
485                  {
486                    if (output_iterator != null)
487                      iterator = formatter.formatToCharacterIterator(thisArg);
488                    else
489                      formatter.format(thisArg, appendBuf, fp);
490                  }
491    
492                elements[i].format = formatter;
493              }
494    
495            if (output_iterator != null)
496              {
497                HashMap hash_argument = new HashMap();
498                int position = output_iterator.getEndIndex();
499                
500                hash_argument.put (MessageFormat.Field.ARGUMENT,
501                                   new Integer(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        Vector results = new Vector (elements.length, 1);
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 = (double[]) 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              results.setSize(elements[i].argNumber + 1);
685            results.setElementAt(value, elements[i].argNumber);
686    
687            index += elements[i].trailer.length();
688          }
689    
690        Object[] r = new Object[results.size()];
691        results.copyInto(r);
692        return r;
693      }
694    
695      public Object[] parse (String sourceStr) throws ParseException
696      {
697        ParsePosition pp = new ParsePosition (0);
698        Object[] r = parse (sourceStr, pp);
699        if (r == null)
700          throw new ParseException ("couldn't parse string", pp.getErrorIndex());
701        return r;
702      }
703    
704      public Object parseObject (String sourceStr, ParsePosition pos)
705      {
706        return parse (sourceStr, pos);
707      }
708    
709      /**
710       * Sets the format for the argument at an specified
711       * index.
712       *
713       * @param variableNum The index.
714       * @param newFormat The Format object.
715       */
716      public void setFormat (int variableNum, Format newFormat)
717      {
718        elements[variableNum].setFormat = newFormat;
719      }
720    
721      /**
722       * Sets the formats for the arguments.
723       *
724       * @param newFormats An array of Format objects.
725       */
726      public void setFormats (Format[] newFormats)
727      {
728        if (newFormats.length < elements.length)
729          throw new IllegalArgumentException("Not enough format objects");
730    
731        int len = Math.min(newFormats.length, elements.length);
732        for (int i = 0; i < len; ++i)
733          elements[i].setFormat = newFormats[i];
734      }
735    
736      /**
737       * Sets the locale.
738       *
739       * @param loc A Locale
740       */
741      public void setLocale (Locale loc)
742      {
743        locale = loc;
744        if (elements != null)
745          {
746            for (int i = 0; i < elements.length; ++i)
747              elements[i].setLocale(loc);
748          }
749      }
750    
751      /**
752       * Returns the pattern.
753       */
754      public String toPattern ()
755      {
756        return pattern;
757      }
758    
759      /**
760       * Return the formatters used sorted by argument index. It uses the
761       * internal table to fill in this array: if a format has been
762       * set using <code>setFormat</code> or <code>setFormatByArgumentIndex</code>
763       * then it returns it at the right index. If not it uses the detected
764       * formatters during a <code>format</code> call. If nothing is known
765       * about that argument index it just puts null at that position.
766       * To get useful informations you may have to call <code>format</code>
767       * at least once.
768       *
769       * @return an array of formatters sorted by argument index.
770       */
771      public Format[] getFormatsByArgumentIndex()
772      {
773        int argNumMax = 0;
774        // First, find the greatest argument number.
775        for (int i=0;i<elements.length;i++)
776          if (elements[i].argNumber > argNumMax)
777            argNumMax = elements[i].argNumber;
778    
779        Format[] formats = new Format[argNumMax];
780        for (int i=0;i<elements.length;i++)
781          {
782            if (elements[i].setFormat != null)
783              formats[elements[i].argNumber] = elements[i].setFormat;
784            else if (elements[i].format != null)
785              formats[elements[i].argNumber] = elements[i].format;
786          }
787        return formats;
788      }
789    
790      /**
791       * Set the format to used using the argument index number.
792       *
793       * @param argumentIndex the argument index.
794       * @param newFormat the format to use for this argument.
795       */
796      public void setFormatByArgumentIndex(int argumentIndex,
797                                           Format newFormat)
798      {
799        for (int i=0;i<elements.length;i++)
800          {
801            if (elements[i].argNumber == argumentIndex)
802              elements[i].setFormat = newFormat;
803          }
804      }
805    
806      /**
807       * Set the format for argument using a specified array of formatters
808       * which is sorted according to the argument index. If the number of
809       * elements in the array is fewer than the number of arguments only
810       * the arguments specified by the array are touched.
811       *
812       * @param newFormats array containing the new formats to set.
813       *
814       * @throws NullPointerException if newFormats is null
815       */
816      public void setFormatsByArgumentIndex(Format[] newFormats)
817      {
818        for (int i=0;i<newFormats.length;i++)
819          {
820            // Nothing better than that can exist here.
821            setFormatByArgumentIndex(i, newFormats[i]);
822          }
823      }
824    
825      // The pattern string.
826      private String pattern;
827      // The locale.
828      private Locale locale;
829      // Variables.
830      private MessageFormatElement[] elements;
831      // Leader text.
832      private String leader;
833    }