OSDN Git Service

libjava/ChangeLog:
[pf3gnuchains/gcc-fork.git] / libjava / classpath / java / text / MessageFormat.java
1 /* MessageFormat.java - Localized message formatting.
2    Copyright (C) 1999, 2001, 2002, 2004, 2005 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10  
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package java.text;
40
41 import gnu.java.text.FormatCharacterIterator;
42
43 import java.io.InvalidObjectException;
44 import java.util.Date;
45 import java.util.HashMap;
46 import java.util.Locale;
47 import java.util.Vector;
48
49 public class MessageFormat extends Format
50 {
51   /**
52    * @author Tom Tromey (tromey@cygnus.com)
53    * @author Jorge Aliss (jaliss@hotmail.com)
54    * @date March 3, 1999
55    */
56   /* Written using "Java Class Libraries", 2nd edition, plus online
57    * API docs for JDK 1.2 from http://www.javasoft.com.
58    * Status:  Believed complete and correct to 1.2, except serialization.
59    *          and parsing.
60    */
61   private static final class MessageFormatElement
62   {
63     // Argument number.
64     int argNumber;
65     // Formatter to be used.  This is the format set by setFormat.
66     Format setFormat;
67     // Formatter to be used based on the type.
68     Format format;
69
70     // Argument will be checked to make sure it is an instance of this
71     // class.
72     Class formatClass;
73
74     // Formatter type.
75     String type;
76     // Formatter style.
77     String style;
78
79     // Text to follow this element.
80     String trailer;
81
82     // Recompute the locale-based formatter.
83     void setLocale (Locale loc)
84     {
85       if (type != null)
86         {
87           if (type.equals("number"))
88             {
89               formatClass = java.lang.Number.class;
90
91               if (style == null)
92                 format = NumberFormat.getInstance(loc);
93               else if (style.equals("currency"))
94                 format = NumberFormat.getCurrencyInstance(loc);
95               else if (style.equals("percent"))
96                 format = NumberFormat.getPercentInstance(loc);
97               else if (style.equals("integer"))
98                 {
99                   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, StringBuilder 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                                        StringBuilder 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, StringBuilder 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     StringBuilder tempBuffer = new StringBuilder ();
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                                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     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 = 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 }