Merge from the pain train
[official-gcc.git] / libjava / java / text / MessageFormat.java
blob6c1b0508bdaa98948f52322c94e962933d15d1e1
1 /* MessageFormat.java - Localized message formatting.
2 Copyright (C) 1999, 2001, 2002, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
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.
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. */
39 package java.text;
41 import gnu.java.text.FormatCharacterIterator;
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;
49 public class MessageFormat extends Format
51 /**
52 * @author Tom Tromey (tromey@cygnus.com)
53 * @author Jorge Aliss (jaliss@hotmail.com)
54 * @date March 3, 1999
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.
61 private static final class MessageFormatElement
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;
70 // Argument will be checked to make sure it is an instance of this
71 // class.
72 Class formatClass;
74 // Formatter type.
75 String type;
76 // Formatter style.
77 String style;
79 // Text to follow this element.
80 String trailer;
82 // Recompute the locale-based formatter.
83 void setLocale (Locale loc)
85 if (type == null)
87 else if (type.equals("number"))
89 formatClass = java.lang.Number.class;
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"))
99 NumberFormat nf = NumberFormat.getNumberInstance(loc);
100 nf.setMaximumFractionDigits(0);
101 nf.setGroupingUsed(false);
102 format = nf;
104 else
106 format = NumberFormat.getNumberInstance(loc);
107 DecimalFormat df = (DecimalFormat) format;
108 df.applyPattern(style);
111 else if (type.equals("time") || type.equals("date"))
113 formatClass = java.util.Date.class;
115 int val = DateFormat.DEFAULT;
116 if (style == null)
118 else 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;
127 if (type.equals("time"))
128 format = DateFormat.getTimeInstance(val, loc);
129 else
130 format = DateFormat.getDateInstance(val, loc);
132 if (style != null && val == DateFormat.DEFAULT)
134 SimpleDateFormat sdf = (SimpleDateFormat) format;
135 sdf.applyPattern(style);
138 else if (type.equals("choice"))
140 formatClass = java.lang.Number.class;
142 if (style == null)
143 throw new
144 IllegalArgumentException ("style required for choice format");
145 format = new ChoiceFormat (style);
150 private static final long serialVersionUID = 6479157306784022952L;
152 public static class Field extends Format.Field
154 static final long serialVersionUID = 7899943957617360810L;
157 * This is the attribute set for all characters produced
158 * by MessageFormat during a formatting.
160 public static final MessageFormat.Field ARGUMENT = new MessageFormat.Field("argument");
162 // For deserialization
163 private Field()
165 super("");
168 protected Field(String s)
170 super(s);
174 * invoked to resolve the true static constant by
175 * comparing the deserialized object to know name.
177 * @return object constant
179 protected Object readResolve() throws InvalidObjectException
181 if (getName().equals(ARGUMENT.getName()))
182 return ARGUMENT;
184 throw new InvalidObjectException("no such MessageFormat field called " + getName());
189 // Helper that returns the text up to the next format opener. The
190 // text is put into BUFFER. Returns index of character after end of
191 // string. Throws IllegalArgumentException on error.
192 private static int scanString(String pat, int index, StringBuffer buffer)
194 int max = pat.length();
195 buffer.setLength(0);
196 boolean quoted = false;
197 for (; index < max; ++index)
199 char c = pat.charAt(index);
200 if (quoted)
202 // In a quoted context, a single quote ends the quoting.
203 if (c == '\'')
204 quoted = false;
205 else
206 buffer.append(c);
208 // Check for '', which is a single quote.
209 else if (c == '\'' && index + 1 < max && pat.charAt(index + 1) == '\'')
211 buffer.append(c);
212 ++index;
214 else if (c == '\'')
216 // Start quoting.
217 quoted = true;
219 else if (c == '{')
220 break;
221 else
222 buffer.append(c);
224 // Note that we explicitly allow an unterminated quote. This is
225 // done for compatibility.
226 return index;
229 // This helper retrieves a single part of a format element. Returns
230 // the index of the terminating character.
231 private static int scanFormatElement(String pat, int index,
232 StringBuffer buffer, char term)
234 int max = pat.length();
235 buffer.setLength(0);
236 int brace_depth = 1;
237 boolean quoted = false;
239 for (; index < max; ++index)
241 char c = pat.charAt(index);
242 // First see if we should turn off quoting.
243 if (quoted)
245 if (c == '\'')
246 quoted = false;
247 // In both cases we fall through to inserting the
248 // character here.
250 // See if we have just a plain quote to insert.
251 else if (c == '\'' && index + 1 < max
252 && pat.charAt(index + 1) == '\'')
254 buffer.append(c);
255 ++index;
257 // See if quoting should turn on.
258 else if (c == '\'')
259 quoted = true;
260 else if (c == '{')
261 ++brace_depth;
262 else if (c == '}')
264 if (--brace_depth == 0)
265 break;
267 // Check for TERM after braces, because TERM might be `}'.
268 else if (c == term)
269 break;
270 // All characters, including opening and closing quotes, are
271 // inserted here.
272 buffer.append(c);
274 return index;
277 // This is used to parse a format element and whatever non-format
278 // text might trail it.
279 private static int scanFormat(String pat, int index, StringBuffer buffer,
280 Vector elts, Locale locale)
282 MessageFormatElement mfe = new MessageFormatElement ();
283 elts.addElement(mfe);
285 int max = pat.length();
287 // Skip the opening `{'.
288 ++index;
290 // Fetch the argument number.
291 index = scanFormatElement (pat, index, buffer, ',');
294 mfe.argNumber = Integer.parseInt(buffer.toString());
296 catch (NumberFormatException nfx)
298 IllegalArgumentException iae = new IllegalArgumentException(pat);
299 iae.initCause(nfx);
300 throw iae;
303 // Extract the element format.
304 if (index < max && pat.charAt(index) == ',')
306 index = scanFormatElement (pat, index + 1, buffer, ',');
307 mfe.type = buffer.toString();
309 // Extract the style.
310 if (index < max && pat.charAt(index) == ',')
312 index = scanFormatElement (pat, index + 1, buffer, '}');
313 mfe.style = buffer.toString ();
317 // Advance past the last terminator.
318 if (index >= max || pat.charAt(index) != '}')
319 throw new IllegalArgumentException("Missing '}' at end of message format");
320 ++index;
322 // Now fetch trailing string.
323 index = scanString (pat, index, buffer);
324 mfe.trailer = buffer.toString ();
326 mfe.setLocale(locale);
328 return index;
332 * Applies the specified pattern to this MessageFormat.
334 * @param aPattern The Pattern
336 public void applyPattern (String newPattern)
338 pattern = newPattern;
340 StringBuffer tempBuffer = new StringBuffer ();
342 int index = scanString (newPattern, 0, tempBuffer);
343 leader = tempBuffer.toString();
345 Vector elts = new Vector ();
346 while (index < newPattern.length())
347 index = scanFormat (newPattern, index, tempBuffer, elts, locale);
349 elements = new MessageFormatElement[elts.size()];
350 elts.copyInto(elements);
354 * Overrides Format.clone()
356 public Object clone ()
358 MessageFormat c = (MessageFormat) super.clone ();
359 c.elements = (MessageFormatElement[]) elements.clone ();
360 return c;
364 * Overrides Format.equals(Object obj)
366 public boolean equals (Object obj)
368 if (! (obj instanceof MessageFormat))
369 return false;
370 MessageFormat mf = (MessageFormat) obj;
371 return (pattern.equals(mf.pattern)
372 && locale.equals(mf.locale));
376 * A convinience method to format patterns.
378 * @param aPattern The pattern used when formatting.
379 * @param arguments The array containing the objects to be formatted.
381 public AttributedCharacterIterator formatToCharacterIterator (Object arguments)
383 Object[] arguments_array = (Object[])arguments;
384 FormatCharacterIterator iterator = new FormatCharacterIterator();
386 formatInternal(arguments_array, new StringBuffer(), null, iterator);
388 return iterator;
392 * A convinience method to format patterns.
394 * @param aPattern The pattern used when formatting.
395 * @param arguments The array containing the objects to be formatted.
397 public static String format (String pattern, Object arguments[])
399 MessageFormat mf = new MessageFormat (pattern);
400 StringBuffer sb = new StringBuffer ();
401 FieldPosition fp = new FieldPosition (NumberFormat.INTEGER_FIELD);
402 return mf.formatInternal(arguments, sb, fp, null).toString();
406 * Returns the pattern with the formatted objects.
408 * @param source The array containing the objects to be formatted.
409 * @param result The StringBuffer where the text is appened.
410 * @param fp A FieldPosition object (it is ignored).
412 public final StringBuffer format (Object arguments[], StringBuffer appendBuf,
413 FieldPosition fp)
415 return formatInternal(arguments, appendBuf, fp, null);
418 private StringBuffer formatInternal (Object arguments[],
419 StringBuffer appendBuf,
420 FieldPosition fp,
421 FormatCharacterIterator output_iterator)
423 appendBuf.append(leader);
424 if (output_iterator != null)
425 output_iterator.append(leader);
427 for (int i = 0; i < elements.length; ++i)
429 Object thisArg = null;
430 boolean unavailable = false;
431 if (arguments == null || elements[i].argNumber >= arguments.length)
432 unavailable = true;
433 else
434 thisArg = arguments[elements[i].argNumber];
436 AttributedCharacterIterator iterator = null;
438 Format formatter = null;
440 if (fp != null && i == fp.getField() && fp.getFieldAttribute() == Field.ARGUMENT)
441 fp.setBeginIndex(appendBuf.length());
443 if (unavailable)
444 appendBuf.append("{" + elements[i].argNumber + "}");
445 else
447 if (elements[i].setFormat != null)
448 formatter = elements[i].setFormat;
449 else if (elements[i].format != null)
451 if (elements[i].formatClass != null
452 && ! elements[i].formatClass.isInstance(thisArg))
453 throw new IllegalArgumentException("Wrong format class");
455 formatter = elements[i].format;
457 else if (thisArg instanceof Number)
458 formatter = NumberFormat.getInstance(locale);
459 else if (thisArg instanceof Date)
460 formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
461 else
462 appendBuf.append(thisArg);
465 if (fp != null && fp.getField() == i && fp.getFieldAttribute() == Field.ARGUMENT)
466 fp.setEndIndex(appendBuf.length());
468 if (formatter != null)
470 // Special-case ChoiceFormat.
471 if (formatter instanceof ChoiceFormat)
473 StringBuffer buf = new StringBuffer ();
474 formatter.format(thisArg, buf, fp);
475 MessageFormat mf = new MessageFormat ();
476 mf.setLocale(locale);
477 mf.applyPattern(buf.toString());
478 mf.format(arguments, appendBuf, fp);
480 else
482 if (output_iterator != null)
483 iterator = formatter.formatToCharacterIterator(thisArg);
484 else
485 formatter.format(thisArg, appendBuf, fp);
488 elements[i].format = formatter;
491 if (output_iterator != null)
493 HashMap hash_argument = new HashMap();
494 int position = output_iterator.getEndIndex();
496 hash_argument.put (MessageFormat.Field.ARGUMENT,
497 new Integer(elements[i].argNumber));
500 if (iterator != null)
502 output_iterator.append(iterator);
503 output_iterator.addAttributes(hash_argument, position,
504 output_iterator.getEndIndex());
506 else
507 output_iterator.append(thisArg.toString(), hash_argument);
509 output_iterator.append(elements[i].trailer);
512 appendBuf.append(elements[i].trailer);
515 return appendBuf;
519 * Returns the pattern with the formatted objects. The first argument
520 * must be a array of Objects.
521 * This is equivalent to format((Object[]) objectArray, appendBuf, fpos)
523 * @param objectArray The object array to be formatted.
524 * @param appendBuf The StringBuffer where the text is appened.
525 * @param fpos A FieldPosition object (it is ignored).
527 public final StringBuffer format (Object objectArray, StringBuffer appendBuf,
528 FieldPosition fpos)
530 return format ((Object[])objectArray, appendBuf, fpos);
534 * Returns an array with the Formats for
535 * the arguments.
537 public Format[] getFormats ()
539 Format[] f = new Format[elements.length];
540 for (int i = elements.length - 1; i >= 0; --i)
541 f[i] = elements[i].setFormat;
542 return f;
546 * Returns the locale.
548 public Locale getLocale ()
550 return locale;
554 * Overrides Format.hashCode()
556 public int hashCode ()
558 // FIXME: not a very good hash.
559 return pattern.hashCode() + locale.hashCode();
562 private MessageFormat ()
567 * Creates a new MessageFormat object with
568 * the specified pattern
570 * @param pattern The Pattern
572 public MessageFormat(String pattern)
574 this(pattern, Locale.getDefault());
578 * Creates a new MessageFormat object with
579 * the specified pattern
581 * @param pattern The Pattern
582 * @param locale The Locale to use
584 * @since 1.4
586 public MessageFormat(String pattern, Locale locale)
588 this.locale = locale;
589 applyPattern (pattern);
593 * Parse a string <code>sourceStr</code> against the pattern specified
594 * to the MessageFormat constructor.
596 * @param sourceStr the string to be parsed.
597 * @param pos the current parse position (and eventually the error position).
598 * @return the array of parsed objects sorted according to their argument number
599 * in the pattern.
601 public Object[] parse (String sourceStr, ParsePosition pos)
603 // Check initial text.
604 int index = pos.getIndex();
605 if (! sourceStr.startsWith(leader, index))
607 pos.setErrorIndex(index);
608 return null;
610 index += leader.length();
612 Vector results = new Vector (elements.length, 1);
613 // Now check each format.
614 for (int i = 0; i < elements.length; ++i)
616 Format formatter = null;
617 if (elements[i].setFormat != null)
618 formatter = elements[i].setFormat;
619 else if (elements[i].format != null)
620 formatter = elements[i].format;
622 Object value = null;
623 if (formatter instanceof ChoiceFormat)
625 // We must special-case a ChoiceFormat because it might
626 // have recursive formatting.
627 ChoiceFormat cf = (ChoiceFormat) formatter;
628 String[] formats = (String[]) cf.getFormats();
629 double[] limits = (double[]) cf.getLimits();
630 MessageFormat subfmt = new MessageFormat ();
631 subfmt.setLocale(locale);
632 ParsePosition subpos = new ParsePosition (index);
634 int j;
635 for (j = 0; value == null && j < limits.length; ++j)
637 subfmt.applyPattern(formats[j]);
638 subpos.setIndex(index);
639 value = subfmt.parse(sourceStr, subpos);
641 if (value != null)
643 index = subpos.getIndex();
644 value = new Double (limits[j]);
647 else if (formatter != null)
649 pos.setIndex(index);
650 value = formatter.parseObject(sourceStr, pos);
651 if (value != null)
652 index = pos.getIndex();
654 else
656 // We have a String format. This can lose in a number
657 // of ways, but we give it a shot.
658 int next_index = sourceStr.indexOf(elements[i].trailer, index);
659 if (next_index == -1)
661 pos.setErrorIndex(index);
662 return null;
664 value = sourceStr.substring(index, next_index);
665 index = next_index;
668 if (value == null
669 || ! sourceStr.startsWith(elements[i].trailer, index))
671 pos.setErrorIndex(index);
672 return null;
675 if (elements[i].argNumber >= results.size())
676 results.setSize(elements[i].argNumber + 1);
677 results.setElementAt(value, elements[i].argNumber);
679 index += elements[i].trailer.length();
682 Object[] r = new Object[results.size()];
683 results.copyInto(r);
684 return r;
687 public Object[] parse (String sourceStr) throws ParseException
689 ParsePosition pp = new ParsePosition (0);
690 Object[] r = parse (sourceStr, pp);
691 if (r == null)
692 throw new ParseException ("couldn't parse string", pp.getErrorIndex());
693 return r;
696 public Object parseObject (String sourceStr, ParsePosition pos)
698 return parse (sourceStr, pos);
702 * Sets the format for the argument at an specified
703 * index.
705 * @param index The index.
706 * @format The Format object.
708 public void setFormat (int variableNum, Format newFormat)
710 elements[variableNum].setFormat = newFormat;
714 * Sets the formats for the arguments.
716 * @param formats An array of Format objects.
718 public void setFormats (Format[] newFormats)
720 if (newFormats.length < elements.length)
721 throw new IllegalArgumentException("Not enough format objects");
723 int len = Math.min(newFormats.length, elements.length);
724 for (int i = 0; i < len; ++i)
725 elements[i].setFormat = newFormats[i];
729 * Sets the locale.
731 * @param locale A Locale
733 public void setLocale (Locale loc)
735 locale = loc;
736 if (elements != null)
738 for (int i = 0; i < elements.length; ++i)
739 elements[i].setLocale(loc);
744 * Returns the pattern.
746 public String toPattern ()
748 return pattern;
752 * Return the formatters used sorted by argument index. It uses the
753 * internal table to fill in this array: if a format has been
754 * set using <code>setFormat</code> or <code>setFormatByArgumentIndex</code>
755 * then it returns it at the right index. If not it uses the detected
756 * formatters during a <code>format</code> call. If nothing is known
757 * about that argument index it just puts null at that position.
758 * To get useful informations you may have to call <code>format</code>
759 * at least once.
761 * @return an array of formatters sorted by argument index.
763 public Format[] getFormatsByArgumentIndex()
765 int argNumMax = 0;
766 // First, find the greatest argument number.
767 for (int i=0;i<elements.length;i++)
768 if (elements[i].argNumber > argNumMax)
769 argNumMax = elements[i].argNumber;
771 Format[] formats = new Format[argNumMax];
772 for (int i=0;i<elements.length;i++)
774 if (elements[i].setFormat != null)
775 formats[elements[i].argNumber] = elements[i].setFormat;
776 else if (elements[i].format != null)
777 formats[elements[i].argNumber] = elements[i].format;
779 return formats;
783 * Set the format to used using the argument index number.
785 * @param argumentIndex the argument index.
786 * @param newFormat the format to use for this argument.
788 public void setFormatByArgumentIndex(int argumentIndex,
789 Format newFormat)
791 for (int i=0;i<elements.length;i++)
793 if (elements[i].argNumber == argumentIndex)
794 elements[i].setFormat = newFormat;
799 * Set the format for argument using a specified array of formatters
800 * which is sorted according to the argument index. If the number of
801 * elements in the array is fewer than the number of arguments only
802 * the arguments specified by the array are touched.
804 * @param newFormats array containing the new formats to set.
806 * @throws NullPointerException if newFormats is null
808 public void setFormatsByArgumentIndex(Format[] newFormats)
810 for (int i=0;i<newFormats.length;i++)
812 // Nothing better than that can exist here.
813 setFormatByArgumentIndex(i, newFormats[i]);
817 // The pattern string.
818 private String pattern;
819 // The locale.
820 private Locale locale;
821 // Variables.
822 private MessageFormatElement[] elements;
823 // Leader text.
824 private String leader;