OSDN Git Service

Merged gcj-eclipse branch to trunk.
[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       else 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           else 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           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   private static final long serialVersionUID = 6479157306784022952L;
154
155   public static class Field extends Format.Field
156   {
157     static final long serialVersionUID = 7899943957617360810L;
158
159     /**
160      * This is the attribute set for all characters produced
161      * by MessageFormat during a formatting.
162      */
163     public static final MessageFormat.Field ARGUMENT = new MessageFormat.Field("argument");
164
165     // For deserialization
166     private Field()
167     {
168       super("");
169     }
170     
171     protected Field(String s)
172     {
173       super(s);
174     }
175
176     /**
177      * invoked to resolve the true static constant by
178      * comparing the deserialized object to know name.
179      *
180      * @return object constant
181      */
182     protected Object readResolve() throws InvalidObjectException
183     {
184       if (getName().equals(ARGUMENT.getName()))
185         return ARGUMENT;
186
187       throw new InvalidObjectException("no such MessageFormat field called " + getName());
188     }
189
190   }
191
192   // Helper that returns the text up to the next format opener.  The
193   // text is put into BUFFER.  Returns index of character after end of
194   // string.  Throws IllegalArgumentException on error.
195   private static int scanString(String pat, int index, StringBuffer buffer)
196   {
197     int max = pat.length();
198     buffer.setLength(0);
199     boolean quoted = false;
200     for (; index < max; ++index)
201       {
202         char c = pat.charAt(index);
203         if (quoted)
204           {
205             // In a quoted context, a single quote ends the quoting.
206             if (c == '\'')
207               quoted = false;
208             else
209               buffer.append(c);
210           }
211         // Check for '', which is a single quote.
212         else if (c == '\'' && index + 1 < max && pat.charAt(index + 1) == '\'')
213           {
214             buffer.append(c);
215             ++index;
216           }
217         else if (c == '\'')
218           {
219             // Start quoting.
220             quoted = true;
221           }
222         else if (c == '{')
223           break;
224         else
225           buffer.append(c);
226       }
227     // Note that we explicitly allow an unterminated quote.  This is
228     // done for compatibility.
229     return index;
230   }
231
232   // This helper retrieves a single part of a format element.  Returns
233   // the index of the terminating character.
234   private static int scanFormatElement(String pat, int index,
235                                        StringBuffer buffer, char term)
236   {
237     int max = pat.length();
238     buffer.setLength(0);
239     int brace_depth = 1;
240     boolean quoted = false;
241
242     for (; index < max; ++index)
243       {
244         char c = pat.charAt(index);
245         // First see if we should turn off quoting.
246         if (quoted)
247           {
248             if (c == '\'')
249               quoted = false;
250             // In both cases we fall through to inserting the
251             // character here.
252           }
253         // See if we have just a plain quote to insert.
254         else if (c == '\'' && index + 1 < max
255                  && pat.charAt(index + 1) == '\'')
256           {
257             buffer.append(c);
258             ++index;
259           }
260         // See if quoting should turn on.
261         else if (c == '\'')
262           quoted = true;
263         else if (c == '{')
264           ++brace_depth;
265         else if (c == '}')
266           {
267             if (--brace_depth == 0)
268               break;
269           }
270         // Check for TERM after braces, because TERM might be `}'.
271         else if (c == term)
272           break;
273         // All characters, including opening and closing quotes, are
274         // inserted here.
275         buffer.append(c);
276       }
277     return index;
278   }
279
280   // This is used to parse a format element and whatever non-format
281   // text might trail it.
282   private static int scanFormat(String pat, int index, StringBuffer buffer,
283                                 Vector elts, Locale locale)
284   {
285     MessageFormatElement mfe = new MessageFormatElement ();
286     elts.addElement(mfe);
287
288     int max = pat.length();
289
290     // Skip the opening `{'.
291     ++index;
292
293     // Fetch the argument number.
294     index = scanFormatElement (pat, index, buffer, ',');
295     try
296       {
297         mfe.argNumber = Integer.parseInt(buffer.toString());
298       }
299     catch (NumberFormatException nfx)
300       {
301         IllegalArgumentException iae = new IllegalArgumentException(pat);
302         iae.initCause(nfx);
303         throw iae;
304       }
305
306     // Extract the element format.
307     if (index < max && pat.charAt(index) == ',')
308       {
309         index = scanFormatElement (pat, index + 1, buffer, ',');
310         mfe.type = buffer.toString();
311
312         // Extract the style.
313         if (index < max && pat.charAt(index) == ',')
314           {
315             index = scanFormatElement (pat, index + 1, buffer, '}');
316             mfe.style = buffer.toString ();
317           }
318       }
319
320     // Advance past the last terminator.
321     if (index >= max || pat.charAt(index) != '}')
322       throw new IllegalArgumentException("Missing '}' at end of message format");
323     ++index;
324
325     // Now fetch trailing string.
326     index = scanString (pat, index, buffer);
327     mfe.trailer = buffer.toString ();
328
329     mfe.setLocale(locale);
330
331     return index;
332   }
333
334   /**
335    * Applies the specified pattern to this MessageFormat.
336    *
337    * @param newPattern The Pattern
338    */
339   public void applyPattern (String newPattern)
340   {
341     pattern = newPattern;
342
343     StringBuffer tempBuffer = new StringBuffer ();
344
345     int index = scanString (newPattern, 0, tempBuffer);
346     leader = tempBuffer.toString();
347
348     Vector elts = new Vector ();
349     while (index < newPattern.length())
350       index = scanFormat (newPattern, index, tempBuffer, elts, locale);
351
352     elements = new MessageFormatElement[elts.size()];
353     elts.copyInto(elements);
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 hash_argument = new HashMap();
496             int position = output_iterator.getEndIndex();
497             
498             hash_argument.put (MessageFormat.Field.ARGUMENT,
499                                new Integer(elements[i].argNumber));
500
501             
502             if (iterator != null)
503               {
504                 output_iterator.append(iterator);
505                 output_iterator.addAttributes(hash_argument, position, 
506                                               output_iterator.getEndIndex());
507               } 
508             else
509               output_iterator.append(thisArg.toString(), hash_argument);
510             
511             output_iterator.append(elements[i].trailer);
512           }
513         
514         appendBuf.append(elements[i].trailer);
515       }
516     
517     return appendBuf;
518   }
519
520   /**
521    * Returns the pattern with the formatted objects.  The first argument
522    * must be a array of Objects.
523    * This is equivalent to format((Object[]) objectArray, appendBuf, fpos)
524    *
525    * @param objectArray The object array to be formatted.
526    * @param appendBuf The StringBuffer where the text is appened.
527    * @param fpos A FieldPosition object (it is ignored).
528    */
529   public final StringBuffer format (Object objectArray, StringBuffer appendBuf,
530                                     FieldPosition fpos)
531   {
532     return format ((Object[])objectArray, appendBuf, fpos);
533   }
534
535   /**
536    * Returns an array with the Formats for
537    * the arguments.
538    */
539   public Format[] getFormats ()
540   {
541     Format[] f = new Format[elements.length];
542     for (int i = elements.length - 1; i >= 0; --i)
543       f[i] = elements[i].setFormat;
544     return f;
545   }
546
547   /**
548    * Returns the locale.
549    */
550   public Locale getLocale ()
551   {
552     return locale;
553   }
554
555   /**
556    * Overrides Format.hashCode()
557    */
558   public int hashCode ()
559   {
560     // FIXME: not a very good hash.
561     return pattern.hashCode() + locale.hashCode();
562   }
563
564   private MessageFormat ()
565   {
566   }
567
568   /**
569    * Creates a new MessageFormat object with
570    * the specified pattern
571    *
572    * @param pattern The Pattern
573    */
574   public MessageFormat(String pattern)
575   {
576     this(pattern, Locale.getDefault());
577   }
578
579   /**
580    * Creates a new MessageFormat object with
581    * the specified pattern
582    *
583    * @param pattern The Pattern
584    * @param locale The Locale to use
585    *
586    * @since 1.4
587    */
588   public MessageFormat(String pattern, Locale locale)
589   {
590     this.locale = locale;
591     applyPattern (pattern);
592   }
593
594   /**
595    * Parse a string <code>sourceStr</code> against the pattern specified
596    * to the MessageFormat constructor.
597    *
598    * @param sourceStr the string to be parsed.
599    * @param pos the current parse position (and eventually the error position).
600    * @return the array of parsed objects sorted according to their argument number
601    * in the pattern.
602    */ 
603   public Object[] parse (String sourceStr, ParsePosition pos)
604   {
605     // Check initial text.
606     int index = pos.getIndex();
607     if (! sourceStr.startsWith(leader, index))
608       {
609         pos.setErrorIndex(index);
610         return null;
611       }
612     index += leader.length();
613
614     Vector results = new Vector (elements.length, 1);
615     // Now check each format.
616     for (int i = 0; i < elements.length; ++i)
617       {
618         Format formatter = null;
619         if (elements[i].setFormat != null)
620           formatter = elements[i].setFormat;
621         else if (elements[i].format != null)
622           formatter = elements[i].format;
623
624         Object value = null;
625         if (formatter instanceof ChoiceFormat)
626           {
627             // We must special-case a ChoiceFormat because it might
628             // have recursive formatting.
629             ChoiceFormat cf = (ChoiceFormat) formatter;
630             String[] formats = (String[]) cf.getFormats();
631             double[] limits = (double[]) cf.getLimits();
632             MessageFormat subfmt = new MessageFormat ();
633             subfmt.setLocale(locale);
634             ParsePosition subpos = new ParsePosition (index);
635
636             int j;
637             for (j = 0; value == null && j < limits.length; ++j)
638               {
639                 subfmt.applyPattern(formats[j]);
640                 subpos.setIndex(index);
641                 value = subfmt.parse(sourceStr, subpos);
642               }
643             if (value != null)
644               {
645                 index = subpos.getIndex();
646                 value = new Double (limits[j]);
647               }
648           }
649         else if (formatter != null)
650           {
651             pos.setIndex(index);
652             value = formatter.parseObject(sourceStr, pos);
653             if (value != null)
654               index = pos.getIndex();
655           }
656         else
657           {
658             // We have a String format.  This can lose in a number
659             // of ways, but we give it a shot.
660             int next_index;
661             if (elements[i].trailer.length() > 0)
662               next_index = sourceStr.indexOf(elements[i].trailer, index);
663             else
664               next_index = sourceStr.length();
665             if (next_index == -1)
666               {
667                 pos.setErrorIndex(index);
668                 return null;
669               }
670             value = sourceStr.substring(index, next_index);
671             index = next_index;
672           }
673
674         if (value == null
675             || ! sourceStr.startsWith(elements[i].trailer, index))
676           {
677             pos.setErrorIndex(index);
678             return null;
679           }
680
681         if (elements[i].argNumber >= results.size())
682           results.setSize(elements[i].argNumber + 1);
683         results.setElementAt(value, elements[i].argNumber);
684
685         index += elements[i].trailer.length();
686       }
687
688     Object[] r = new Object[results.size()];
689     results.copyInto(r);
690     return r;
691   }
692
693   public Object[] parse (String sourceStr) throws ParseException
694   {
695     ParsePosition pp = new ParsePosition (0);
696     Object[] r = parse (sourceStr, pp);
697     if (r == null)
698       throw new ParseException ("couldn't parse string", pp.getErrorIndex());
699     return r;
700   }
701
702   public Object parseObject (String sourceStr, ParsePosition pos)
703   {
704     return parse (sourceStr, pos);
705   }
706
707   /**
708    * Sets the format for the argument at an specified
709    * index.
710    *
711    * @param variableNum The index.
712    * @param newFormat The Format object.
713    */
714   public void setFormat (int variableNum, Format newFormat)
715   {
716     elements[variableNum].setFormat = newFormat;
717   }
718
719   /**
720    * Sets the formats for the arguments.
721    *
722    * @param newFormats An array of Format objects.
723    */
724   public void setFormats (Format[] newFormats)
725   {
726     if (newFormats.length < elements.length)
727       throw new IllegalArgumentException("Not enough format objects");
728
729     int len = Math.min(newFormats.length, elements.length);
730     for (int i = 0; i < len; ++i)
731       elements[i].setFormat = newFormats[i];
732   }
733
734   /**
735    * Sets the locale.
736    *
737    * @param loc A Locale
738    */
739   public void setLocale (Locale loc)
740   {
741     locale = loc;
742     if (elements != null)
743       {
744         for (int i = 0; i < elements.length; ++i)
745           elements[i].setLocale(loc);
746       }
747   }
748
749   /**
750    * Returns the pattern.
751    */
752   public String toPattern ()
753   {
754     return pattern;
755   }
756
757   /**
758    * Return the formatters used sorted by argument index. It uses the
759    * internal table to fill in this array: if a format has been
760    * set using <code>setFormat</code> or <code>setFormatByArgumentIndex</code>
761    * then it returns it at the right index. If not it uses the detected
762    * formatters during a <code>format</code> call. If nothing is known
763    * about that argument index it just puts null at that position.
764    * To get useful informations you may have to call <code>format</code>
765    * at least once.
766    *
767    * @return an array of formatters sorted by argument index.
768    */
769   public Format[] getFormatsByArgumentIndex()
770   {
771     int argNumMax = 0;
772     // First, find the greatest argument number.
773     for (int i=0;i<elements.length;i++)
774       if (elements[i].argNumber > argNumMax)
775         argNumMax = elements[i].argNumber;
776
777     Format[] formats = new Format[argNumMax];
778     for (int i=0;i<elements.length;i++)
779       {
780         if (elements[i].setFormat != null)
781           formats[elements[i].argNumber] = elements[i].setFormat;
782         else if (elements[i].format != null)
783           formats[elements[i].argNumber] = elements[i].format;
784       }
785     return formats;
786   }
787
788   /**
789    * Set the format to used using the argument index number.
790    *
791    * @param argumentIndex the argument index.
792    * @param newFormat the format to use for this argument.
793    */
794   public void setFormatByArgumentIndex(int argumentIndex,
795                                        Format newFormat)
796   {
797     for (int i=0;i<elements.length;i++)
798       {
799         if (elements[i].argNumber == argumentIndex)
800           elements[i].setFormat = newFormat;
801       }
802   }
803
804   /**
805    * Set the format for argument using a specified array of formatters
806    * which is sorted according to the argument index. If the number of
807    * elements in the array is fewer than the number of arguments only
808    * the arguments specified by the array are touched.
809    *
810    * @param newFormats array containing the new formats to set.
811    *
812    * @throws NullPointerException if newFormats is null
813    */
814   public void setFormatsByArgumentIndex(Format[] newFormats)
815   {
816     for (int i=0;i<newFormats.length;i++)
817       {
818         // Nothing better than that can exist here.
819         setFormatByArgumentIndex(i, newFormats[i]);
820       }
821   }
822
823   // The pattern string.
824   private String pattern;
825   // The locale.
826   private Locale locale;
827   // Variables.
828   private MessageFormatElement[] elements;
829   // Leader text.
830   private String leader;
831 }