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)
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., 51 Franklin Street, Fifth Floor, Boston, MA
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
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. */
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
52 * @author Tom Tromey (tromey@cygnus.com)
53 * @author Jorge Aliss (jaliss@hotmail.com)
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.
61 private static final class MessageFormatElement
65 // Formatter to be used. This is the format set by setFormat.
67 // Formatter to be used based on the type.
70 // Argument will be checked to make sure it is an instance of this
79 // Text to follow this element.
82 // Recompute the locale-based formatter.
83 void setLocale (Locale loc)
87 else if (type.equals("number"))
89 formatClass = java.lang.Number.class;
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);
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 boolean styleIsPattern = false;
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;
128 styleIsPattern = true;
130 if (type.equals("time"))
131 format = DateFormat.getTimeInstance(val, loc);
133 format = DateFormat.getDateInstance(val, loc);
137 SimpleDateFormat sdf = (SimpleDateFormat) format;
138 sdf.applyPattern(style);
141 else if (type.equals("choice"))
143 formatClass = java.lang.Number.class;
147 IllegalArgumentException ("style required for choice format");
148 format = new ChoiceFormat (style);
153 private static final long serialVersionUID = 6479157306784022952L;
155 public static class Field extends Format.Field
157 static final long serialVersionUID = 7899943957617360810L;
160 * This is the attribute set for all characters produced
161 * by MessageFormat during a formatting.
163 public static final MessageFormat.Field ARGUMENT = new MessageFormat.Field("argument");
165 // For deserialization
171 protected Field(String s)
177 * invoked to resolve the true static constant by
178 * comparing the deserialized object to know name.
180 * @return object constant
182 protected Object readResolve() throws InvalidObjectException
184 if (getName().equals(ARGUMENT.getName()))
187 throw new InvalidObjectException("no such MessageFormat field called " + getName());
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)
197 int max = pat.length();
199 boolean quoted = false;
200 for (; index < max; ++index)
202 char c = pat.charAt(index);
205 // In a quoted context, a single quote ends the quoting.
211 // Check for '', which is a single quote.
212 else if (c == '\'' && index + 1 < max && pat.charAt(index + 1) == '\'')
227 // Note that we explicitly allow an unterminated quote. This is
228 // done for compatibility.
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)
237 int max = pat.length();
240 boolean quoted = false;
242 for (; index < max; ++index)
244 char c = pat.charAt(index);
245 // First see if we should turn off quoting.
250 // In both cases we fall through to inserting the
253 // See if we have just a plain quote to insert.
254 else if (c == '\'' && index + 1 < max
255 && pat.charAt(index + 1) == '\'')
260 // See if quoting should turn on.
267 if (--brace_depth == 0)
270 // Check for TERM after braces, because TERM might be `}'.
273 // All characters, including opening and closing quotes, are
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)
285 MessageFormatElement mfe = new MessageFormatElement ();
286 elts.addElement(mfe);
288 int max = pat.length();
290 // Skip the opening `{'.
293 // Fetch the argument number.
294 index = scanFormatElement (pat, index, buffer, ',');
297 mfe.argNumber = Integer.parseInt(buffer.toString());
299 catch (NumberFormatException nfx)
301 IllegalArgumentException iae = new IllegalArgumentException(pat);
306 // Extract the element format.
307 if (index < max && pat.charAt(index) == ',')
309 index = scanFormatElement (pat, index + 1, buffer, ',');
310 mfe.type = buffer.toString();
312 // Extract the style.
313 if (index < max && pat.charAt(index) == ',')
315 index = scanFormatElement (pat, index + 1, buffer, '}');
316 mfe.style = buffer.toString ();
320 // Advance past the last terminator.
321 if (index >= max || pat.charAt(index) != '}')
322 throw new IllegalArgumentException("Missing '}' at end of message format");
325 // Now fetch trailing string.
326 index = scanString (pat, index, buffer);
327 mfe.trailer = buffer.toString ();
329 mfe.setLocale(locale);
335 * Applies the specified pattern to this MessageFormat.
337 * @param newPattern The Pattern
339 public void applyPattern (String newPattern)
341 pattern = newPattern;
343 StringBuffer tempBuffer = new StringBuffer ();
345 int index = scanString (newPattern, 0, tempBuffer);
346 leader = tempBuffer.toString();
348 Vector elts = new Vector ();
349 while (index < newPattern.length())
350 index = scanFormat (newPattern, index, tempBuffer, elts, locale);
352 elements = new MessageFormatElement[elts.size()];
353 elts.copyInto(elements);
357 * Overrides Format.clone()
359 public Object clone ()
361 MessageFormat c = (MessageFormat) super.clone ();
362 c.elements = (MessageFormatElement[]) elements.clone ();
367 * Overrides Format.equals(Object obj)
369 public boolean equals (Object obj)
371 if (! (obj instanceof MessageFormat))
373 MessageFormat mf = (MessageFormat) obj;
374 return (pattern.equals(mf.pattern)
375 && locale.equals(mf.locale));
379 * A convinience method to format patterns.
381 * @param arguments The array containing the objects to be formatted.
383 public AttributedCharacterIterator formatToCharacterIterator (Object arguments)
385 Object[] arguments_array = (Object[])arguments;
386 FormatCharacterIterator iterator = new FormatCharacterIterator();
388 formatInternal(arguments_array, new StringBuffer(), null, iterator);
394 * A convinience method to format patterns.
396 * @param pattern The pattern used when formatting.
397 * @param arguments The array containing the objects to be formatted.
399 public static String format (String pattern, Object... arguments)
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();
408 * Returns the pattern with the formatted objects.
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).
414 public final StringBuffer format (Object arguments[], StringBuffer appendBuf,
417 return formatInternal(arguments, appendBuf, fp, null);
420 private StringBuffer formatInternal (Object arguments[],
421 StringBuffer appendBuf,
423 FormatCharacterIterator output_iterator)
425 appendBuf.append(leader);
426 if (output_iterator != null)
427 output_iterator.append(leader);
429 for (int i = 0; i < elements.length; ++i)
431 Object thisArg = null;
432 boolean unavailable = false;
433 if (arguments == null || elements[i].argNumber >= arguments.length)
436 thisArg = arguments[elements[i].argNumber];
438 AttributedCharacterIterator iterator = null;
440 Format formatter = null;
442 if (fp != null && i == fp.getField() && fp.getFieldAttribute() == Field.ARGUMENT)
443 fp.setBeginIndex(appendBuf.length());
446 appendBuf.append("{" + elements[i].argNumber + "}");
449 if (elements[i].setFormat != null)
450 formatter = elements[i].setFormat;
451 else if (elements[i].format != null)
453 if (elements[i].formatClass != null
454 && ! elements[i].formatClass.isInstance(thisArg))
455 throw new IllegalArgumentException("Wrong format class");
457 formatter = elements[i].format;
459 else if (thisArg instanceof Number)
460 formatter = NumberFormat.getInstance(locale);
461 else if (thisArg instanceof Date)
462 formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
464 appendBuf.append(thisArg);
467 if (fp != null && fp.getField() == i && fp.getFieldAttribute() == Field.ARGUMENT)
468 fp.setEndIndex(appendBuf.length());
470 if (formatter != null)
472 // Special-case ChoiceFormat.
473 if (formatter instanceof ChoiceFormat)
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);
484 if (output_iterator != null)
485 iterator = formatter.formatToCharacterIterator(thisArg);
487 formatter.format(thisArg, appendBuf, fp);
490 elements[i].format = formatter;
493 if (output_iterator != null)
495 HashMap hash_argument = new HashMap();
496 int position = output_iterator.getEndIndex();
498 hash_argument.put (MessageFormat.Field.ARGUMENT,
499 new Integer(elements[i].argNumber));
502 if (iterator != null)
504 output_iterator.append(iterator);
505 output_iterator.addAttributes(hash_argument, position,
506 output_iterator.getEndIndex());
509 output_iterator.append(thisArg.toString(), hash_argument);
511 output_iterator.append(elements[i].trailer);
514 appendBuf.append(elements[i].trailer);
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)
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).
529 public final StringBuffer format (Object objectArray, StringBuffer appendBuf,
532 return format ((Object[])objectArray, appendBuf, fpos);
536 * Returns an array with the Formats for
539 public Format[] getFormats ()
541 Format[] f = new Format[elements.length];
542 for (int i = elements.length - 1; i >= 0; --i)
543 f[i] = elements[i].setFormat;
548 * Returns the locale.
550 public Locale getLocale ()
556 * Overrides Format.hashCode()
558 public int hashCode ()
560 // FIXME: not a very good hash.
561 return pattern.hashCode() + locale.hashCode();
564 private MessageFormat ()
569 * Creates a new MessageFormat object with
570 * the specified pattern
572 * @param pattern The Pattern
574 public MessageFormat(String pattern)
576 this(pattern, Locale.getDefault());
580 * Creates a new MessageFormat object with
581 * the specified pattern
583 * @param pattern The Pattern
584 * @param locale The Locale to use
588 public MessageFormat(String pattern, Locale locale)
590 this.locale = locale;
591 applyPattern (pattern);
595 * Parse a string <code>sourceStr</code> against the pattern specified
596 * to the MessageFormat constructor.
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
603 public Object[] parse (String sourceStr, ParsePosition pos)
605 // Check initial text.
606 int index = pos.getIndex();
607 if (! sourceStr.startsWith(leader, index))
609 pos.setErrorIndex(index);
612 index += leader.length();
614 Vector results = new Vector (elements.length, 1);
615 // Now check each format.
616 for (int i = 0; i < elements.length; ++i)
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;
625 if (formatter instanceof ChoiceFormat)
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);
637 for (j = 0; value == null && j < limits.length; ++j)
639 subfmt.applyPattern(formats[j]);
640 subpos.setIndex(index);
641 value = subfmt.parse(sourceStr, subpos);
645 index = subpos.getIndex();
646 value = new Double (limits[j]);
649 else if (formatter != null)
652 value = formatter.parseObject(sourceStr, pos);
654 index = pos.getIndex();
658 // We have a String format. This can lose in a number
659 // of ways, but we give it a shot.
661 if (elements[i].trailer.length() > 0)
662 next_index = sourceStr.indexOf(elements[i].trailer, index);
664 next_index = sourceStr.length();
665 if (next_index == -1)
667 pos.setErrorIndex(index);
670 value = sourceStr.substring(index, next_index);
675 || ! sourceStr.startsWith(elements[i].trailer, index))
677 pos.setErrorIndex(index);
681 if (elements[i].argNumber >= results.size())
682 results.setSize(elements[i].argNumber + 1);
683 results.setElementAt(value, elements[i].argNumber);
685 index += elements[i].trailer.length();
688 Object[] r = new Object[results.size()];
693 public Object[] parse (String sourceStr) throws ParseException
695 ParsePosition pp = new ParsePosition (0);
696 Object[] r = parse (sourceStr, pp);
698 throw new ParseException ("couldn't parse string", pp.getErrorIndex());
702 public Object parseObject (String sourceStr, ParsePosition pos)
704 return parse (sourceStr, pos);
708 * Sets the format for the argument at an specified
711 * @param variableNum The index.
712 * @param newFormat The Format object.
714 public void setFormat (int variableNum, Format newFormat)
716 elements[variableNum].setFormat = newFormat;
720 * Sets the formats for the arguments.
722 * @param newFormats An array of Format objects.
724 public void setFormats (Format[] newFormats)
726 if (newFormats.length < elements.length)
727 throw new IllegalArgumentException("Not enough format objects");
729 int len = Math.min(newFormats.length, elements.length);
730 for (int i = 0; i < len; ++i)
731 elements[i].setFormat = newFormats[i];
737 * @param loc A Locale
739 public void setLocale (Locale loc)
742 if (elements != null)
744 for (int i = 0; i < elements.length; ++i)
745 elements[i].setLocale(loc);
750 * Returns the pattern.
752 public String toPattern ()
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>
767 * @return an array of formatters sorted by argument index.
769 public Format[] getFormatsByArgumentIndex()
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;
777 Format[] formats = new Format[argNumMax];
778 for (int i=0;i<elements.length;i++)
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;
789 * Set the format to used using the argument index number.
791 * @param argumentIndex the argument index.
792 * @param newFormat the format to use for this argument.
794 public void setFormatByArgumentIndex(int argumentIndex,
797 for (int i=0;i<elements.length;i++)
799 if (elements[i].argNumber == argumentIndex)
800 elements[i].setFormat = newFormat;
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.
810 * @param newFormats array containing the new formats to set.
812 * @throws NullPointerException if newFormats is null
814 public void setFormatsByArgumentIndex(Format[] newFormats)
816 for (int i=0;i<newFormats.length;i++)
818 // Nothing better than that can exist here.
819 setFormatByArgumentIndex(i, newFormats[i]);
823 // The pattern string.
824 private String pattern;
826 private Locale locale;
828 private MessageFormatElement[] elements;
830 private String leader;