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 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 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;
131 if (type.equals("time"))
132 format = DateFormat.getTimeInstance(val, loc);
134 format = DateFormat.getDateInstance(val, loc);
138 SimpleDateFormat sdf = (SimpleDateFormat) format;
139 sdf.applyPattern(style);
142 else if (type.equals("choice"))
144 formatClass = java.lang.Number.class;
148 IllegalArgumentException ("style required for choice format");
149 format = new ChoiceFormat (style);
155 private static final long serialVersionUID = 6479157306784022952L;
157 public static class Field extends Format.Field
159 static final long serialVersionUID = 7899943957617360810L;
162 * This is the attribute set for all characters produced
163 * by MessageFormat during a formatting.
165 public static final MessageFormat.Field ARGUMENT = new MessageFormat.Field("argument");
167 // For deserialization
173 protected Field(String s)
179 * invoked to resolve the true static constant by
180 * comparing the deserialized object to know name.
182 * @return object constant
184 protected Object readResolve() throws InvalidObjectException
186 if (getName().equals(ARGUMENT.getName()))
189 throw new InvalidObjectException("no such MessageFormat field called " + getName());
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)
199 int max = pat.length();
201 boolean quoted = false;
202 for (; index < max; ++index)
204 char c = pat.charAt(index);
207 // In a quoted context, a single quote ends the quoting.
213 // Check for '', which is a single quote.
214 else if (c == '\'' && index + 1 < max && pat.charAt(index + 1) == '\'')
229 // Note that we explicitly allow an unterminated quote. This is
230 // done for compatibility.
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)
239 int max = pat.length();
242 boolean quoted = false;
244 for (; index < max; ++index)
246 char c = pat.charAt(index);
247 // First see if we should turn off quoting.
252 // In both cases we fall through to inserting the
255 // See if we have just a plain quote to insert.
256 else if (c == '\'' && index + 1 < max
257 && pat.charAt(index + 1) == '\'')
262 // See if quoting should turn on.
269 if (--brace_depth == 0)
272 // Check for TERM after braces, because TERM might be `}'.
275 // All characters, including opening and closing quotes, are
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)
287 MessageFormatElement mfe = new MessageFormatElement ();
288 elts.addElement(mfe);
290 int max = pat.length();
292 // Skip the opening `{'.
295 // Fetch the argument number.
296 index = scanFormatElement (pat, index, buffer, ',');
299 mfe.argNumber = Integer.parseInt(buffer.toString());
301 catch (NumberFormatException nfx)
303 IllegalArgumentException iae = new IllegalArgumentException(pat);
308 // Extract the element format.
309 if (index < max && pat.charAt(index) == ',')
311 index = scanFormatElement (pat, index + 1, buffer, ',');
312 mfe.type = buffer.toString();
314 // Extract the style.
315 if (index < max && pat.charAt(index) == ',')
317 index = scanFormatElement (pat, index + 1, buffer, '}');
318 mfe.style = buffer.toString ();
322 // Advance past the last terminator.
323 if (index >= max || pat.charAt(index) != '}')
324 throw new IllegalArgumentException("Missing '}' at end of message format");
327 // Now fetch trailing string.
328 index = scanString (pat, index, buffer);
329 mfe.trailer = buffer.toString ();
331 mfe.setLocale(locale);
337 * Applies the specified pattern to this MessageFormat.
339 * @param newPattern The Pattern
341 public void applyPattern (String newPattern)
343 pattern = newPattern;
345 StringBuilder tempBuffer = new StringBuilder ();
347 int index = scanString (newPattern, 0, tempBuffer);
348 leader = tempBuffer.toString();
350 Vector elts = new Vector ();
351 while (index < newPattern.length())
352 index = scanFormat (newPattern, index, tempBuffer, elts, locale);
354 elements = new MessageFormatElement[elts.size()];
355 elts.copyInto(elements);
359 * Overrides Format.clone()
361 public Object clone ()
363 MessageFormat c = (MessageFormat) super.clone ();
364 c.elements = (MessageFormatElement[]) elements.clone ();
369 * Overrides Format.equals(Object obj)
371 public boolean equals (Object obj)
373 if (! (obj instanceof MessageFormat))
375 MessageFormat mf = (MessageFormat) obj;
376 return (pattern.equals(mf.pattern)
377 && locale.equals(mf.locale));
381 * A convinience method to format patterns.
383 * @param arguments The array containing the objects to be formatted.
385 public AttributedCharacterIterator formatToCharacterIterator (Object arguments)
387 Object[] arguments_array = (Object[])arguments;
388 FormatCharacterIterator iterator = new FormatCharacterIterator();
390 formatInternal(arguments_array, new StringBuffer(), null, iterator);
396 * A convinience method to format patterns.
398 * @param pattern The pattern used when formatting.
399 * @param arguments The array containing the objects to be formatted.
401 public static String format (String pattern, Object... arguments)
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();
410 * Returns the pattern with the formatted objects.
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).
416 public final StringBuffer format (Object arguments[], StringBuffer appendBuf,
419 return formatInternal(arguments, appendBuf, fp, null);
422 private StringBuffer formatInternal (Object arguments[],
423 StringBuffer appendBuf,
425 FormatCharacterIterator output_iterator)
427 appendBuf.append(leader);
428 if (output_iterator != null)
429 output_iterator.append(leader);
431 for (int i = 0; i < elements.length; ++i)
433 Object thisArg = null;
434 boolean unavailable = false;
435 if (arguments == null || elements[i].argNumber >= arguments.length)
438 thisArg = arguments[elements[i].argNumber];
440 AttributedCharacterIterator iterator = null;
442 Format formatter = null;
444 if (fp != null && i == fp.getField() && fp.getFieldAttribute() == Field.ARGUMENT)
445 fp.setBeginIndex(appendBuf.length());
448 appendBuf.append("{" + elements[i].argNumber + "}");
451 if (elements[i].setFormat != null)
452 formatter = elements[i].setFormat;
453 else if (elements[i].format != null)
455 if (elements[i].formatClass != null
456 && ! elements[i].formatClass.isInstance(thisArg))
457 throw new IllegalArgumentException("Wrong format class");
459 formatter = elements[i].format;
461 else if (thisArg instanceof Number)
462 formatter = NumberFormat.getInstance(locale);
463 else if (thisArg instanceof Date)
464 formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
466 appendBuf.append(thisArg);
469 if (fp != null && fp.getField() == i && fp.getFieldAttribute() == Field.ARGUMENT)
470 fp.setEndIndex(appendBuf.length());
472 if (formatter != null)
474 // Special-case ChoiceFormat.
475 if (formatter instanceof ChoiceFormat)
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);
486 if (output_iterator != null)
487 iterator = formatter.formatToCharacterIterator(thisArg);
489 formatter.format(thisArg, appendBuf, fp);
492 elements[i].format = formatter;
495 if (output_iterator != null)
497 HashMap hash_argument = new HashMap();
498 int position = output_iterator.getEndIndex();
500 hash_argument.put (MessageFormat.Field.ARGUMENT,
501 Integer.valueOf(elements[i].argNumber));
504 if (iterator != null)
506 output_iterator.append(iterator);
507 output_iterator.addAttributes(hash_argument, position,
508 output_iterator.getEndIndex());
511 output_iterator.append(thisArg.toString(), hash_argument);
513 output_iterator.append(elements[i].trailer);
516 appendBuf.append(elements[i].trailer);
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)
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).
531 public final StringBuffer format (Object objectArray, StringBuffer appendBuf,
534 return format ((Object[])objectArray, appendBuf, fpos);
538 * Returns an array with the Formats for
541 public Format[] getFormats ()
543 Format[] f = new Format[elements.length];
544 for (int i = elements.length - 1; i >= 0; --i)
545 f[i] = elements[i].setFormat;
550 * Returns the locale.
552 public Locale getLocale ()
558 * Overrides Format.hashCode()
560 public int hashCode ()
562 // FIXME: not a very good hash.
563 return pattern.hashCode() + locale.hashCode();
566 private MessageFormat ()
571 * Creates a new MessageFormat object with
572 * the specified pattern
574 * @param pattern The Pattern
576 public MessageFormat(String pattern)
578 this(pattern, Locale.getDefault());
582 * Creates a new MessageFormat object with
583 * the specified pattern
585 * @param pattern The Pattern
586 * @param locale The Locale to use
590 public MessageFormat(String pattern, Locale locale)
592 this.locale = locale;
593 applyPattern (pattern);
597 * Parse a string <code>sourceStr</code> against the pattern specified
598 * to the MessageFormat constructor.
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
605 public Object[] parse (String sourceStr, ParsePosition pos)
607 // Check initial text.
608 int index = pos.getIndex();
609 if (! sourceStr.startsWith(leader, index))
611 pos.setErrorIndex(index);
614 index += leader.length();
616 Vector results = new Vector (elements.length, 1);
617 // Now check each format.
618 for (int i = 0; i < elements.length; ++i)
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;
627 if (formatter instanceof ChoiceFormat)
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);
639 for (j = 0; value == null && j < limits.length; ++j)
641 subfmt.applyPattern(formats[j]);
642 subpos.setIndex(index);
643 value = subfmt.parse(sourceStr, subpos);
647 index = subpos.getIndex();
648 value = new Double (limits[j]);
651 else if (formatter != null)
654 value = formatter.parseObject(sourceStr, pos);
656 index = pos.getIndex();
660 // We have a String format. This can lose in a number
661 // of ways, but we give it a shot.
663 if (elements[i].trailer.length() > 0)
664 next_index = sourceStr.indexOf(elements[i].trailer, index);
666 next_index = sourceStr.length();
667 if (next_index == -1)
669 pos.setErrorIndex(index);
672 value = sourceStr.substring(index, next_index);
677 || ! sourceStr.startsWith(elements[i].trailer, index))
679 pos.setErrorIndex(index);
683 if (elements[i].argNumber >= results.size())
684 results.setSize(elements[i].argNumber + 1);
685 results.setElementAt(value, elements[i].argNumber);
687 index += elements[i].trailer.length();
690 Object[] r = new Object[results.size()];
695 public Object[] parse (String sourceStr) throws ParseException
697 ParsePosition pp = new ParsePosition (0);
698 Object[] r = parse (sourceStr, pp);
700 throw new ParseException ("couldn't parse string", pp.getErrorIndex());
704 public Object parseObject (String sourceStr, ParsePosition pos)
706 return parse (sourceStr, pos);
710 * Sets the format for the argument at an specified
713 * @param variableNum The index.
714 * @param newFormat The Format object.
716 public void setFormat (int variableNum, Format newFormat)
718 elements[variableNum].setFormat = newFormat;
722 * Sets the formats for the arguments.
724 * @param newFormats An array of Format objects.
726 public void setFormats (Format[] newFormats)
728 if (newFormats.length < elements.length)
729 throw new IllegalArgumentException("Not enough format objects");
731 int len = Math.min(newFormats.length, elements.length);
732 for (int i = 0; i < len; ++i)
733 elements[i].setFormat = newFormats[i];
739 * @param loc A Locale
741 public void setLocale (Locale loc)
744 if (elements != null)
746 for (int i = 0; i < elements.length; ++i)
747 elements[i].setLocale(loc);
752 * Returns the pattern.
754 public String toPattern ()
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>
769 * @return an array of formatters sorted by argument index.
771 public Format[] getFormatsByArgumentIndex()
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;
779 Format[] formats = new Format[argNumMax];
780 for (int i=0;i<elements.length;i++)
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;
791 * Set the format to used using the argument index number.
793 * @param argumentIndex the argument index.
794 * @param newFormat the format to use for this argument.
796 public void setFormatByArgumentIndex(int argumentIndex,
799 for (int i=0;i<elements.length;i++)
801 if (elements[i].argNumber == argumentIndex)
802 elements[i].setFormat = newFormat;
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.
812 * @param newFormats array containing the new formats to set.
814 * @throws NullPointerException if newFormats is null
816 public void setFormatsByArgumentIndex(Format[] newFormats)
818 for (int i=0;i<newFormats.length;i++)
820 // Nothing better than that can exist here.
821 setFormatByArgumentIndex(i, newFormats[i]);
825 // The pattern string.
826 private String pattern;
828 private Locale locale;
830 private MessageFormatElement[] elements;
832 private String leader;