Import GNU Classpath (20121202).
[official-gcc.git] / libjava / classpath / java / text / MessageFormat.java
blob0e04b2b447a02c4330cd929600f8a166a11833c0
1 /* MessageFormat.java - Localized message formatting.
2 Copyright (C) 1999, 2001, 2002, 2004, 2005, 2012 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., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 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.lang.CPStringBuilder;
43 import gnu.java.text.FormatCharacterIterator;
45 import java.io.InvalidObjectException;
47 import java.util.ArrayList;
48 import java.util.Date;
49 import java.util.HashMap;
50 import java.util.List;
51 import java.util.Locale;
53 public class MessageFormat extends Format
55 /**
56 * @author Tom Tromey (tromey@cygnus.com)
57 * @author Jorge Aliss (jaliss@hotmail.com)
58 * @date March 3, 1999
60 /* Written using "Java Class Libraries", 2nd edition, plus online
61 * API docs for JDK 1.2 from http://www.javasoft.com.
62 * Status: Believed complete and correct to 1.2, except serialization.
63 * and parsing.
65 private static final class MessageFormatElement
67 // Argument number.
68 int argNumber;
69 // Formatter to be used. This is the format set by setFormat.
70 Format setFormat;
71 // Formatter to be used based on the type.
72 Format format;
74 // Argument will be checked to make sure it is an instance of this
75 // class.
76 Class<?> formatClass;
78 // Formatter type.
79 String type;
80 // Formatter style.
81 String style;
83 // Text to follow this element.
84 String trailer;
86 // Recompute the locale-based formatter.
87 void setLocale (Locale loc)
89 if (type != null)
91 if (type.equals("number"))
93 formatClass = java.lang.Number.class;
95 if (style == null)
96 format = NumberFormat.getInstance(loc);
97 else if (style.equals("currency"))
98 format = NumberFormat.getCurrencyInstance(loc);
99 else if (style.equals("percent"))
100 format = NumberFormat.getPercentInstance(loc);
101 else if (style.equals("integer"))
102 format = NumberFormat.getIntegerInstance(loc);
103 else
105 format = NumberFormat.getNumberInstance(loc);
106 DecimalFormat df = (DecimalFormat) format;
107 df.applyPattern(style);
110 else if (type.equals("time") || type.equals("date"))
112 formatClass = java.util.Date.class;
114 int val = DateFormat.DEFAULT;
115 boolean styleIsPattern = false;
116 if (style != null)
118 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;
126 else
127 styleIsPattern = true;
130 if (type.equals("time"))
131 format = DateFormat.getTimeInstance(val, loc);
132 else
133 format = DateFormat.getDateInstance(val, loc);
135 if (styleIsPattern)
137 SimpleDateFormat sdf = (SimpleDateFormat) format;
138 sdf.applyPattern(style);
141 else if (type.equals("choice"))
143 formatClass = java.lang.Number.class;
145 if (style == null)
146 throw new
147 IllegalArgumentException ("style required for choice format");
148 format = new ChoiceFormat (style);
154 private static final long serialVersionUID = 6479157306784022952L;
156 public static class Field extends Format.Field
158 static final long serialVersionUID = 7899943957617360810L;
161 * This is the attribute set for all characters produced
162 * by MessageFormat during a formatting.
164 public static final MessageFormat.Field ARGUMENT = new MessageFormat.Field("argument");
166 // For deserialization
167 private Field()
169 super("");
172 protected Field(String s)
174 super(s);
178 * invoked to resolve the true static constant by
179 * comparing the deserialized object to know name.
181 * @return object constant
183 protected Object readResolve() throws InvalidObjectException
185 if (getName().equals(ARGUMENT.getName()))
186 return ARGUMENT;
188 throw new InvalidObjectException("no such MessageFormat field called " + getName());
193 // Helper that returns the text up to the next format opener. The
194 // text is put into BUFFER. Returns index of character after end of
195 // string. Throws IllegalArgumentException on error.
196 private static int scanString(String pat, int index, CPStringBuilder buffer)
198 int max = pat.length();
199 buffer.setLength(0);
200 boolean quoted = false;
201 for (; index < max; ++index)
203 char c = pat.charAt(index);
204 if (quoted)
206 // In a quoted context, a single quote ends the quoting.
207 if (c == '\'')
208 quoted = false;
209 else
210 buffer.append(c);
212 // Check for '', which is a single quote.
213 else if (c == '\'' && index + 1 < max && pat.charAt(index + 1) == '\'')
215 buffer.append(c);
216 ++index;
218 else if (c == '\'')
220 // Start quoting.
221 quoted = true;
223 else if (c == '{')
224 break;
225 else
226 buffer.append(c);
228 // Note that we explicitly allow an unterminated quote. This is
229 // done for compatibility.
230 return index;
233 // This helper retrieves a single part of a format element. Returns
234 // the index of the terminating character.
235 private static int scanFormatElement(String pat, int index,
236 CPStringBuilder buffer, char term)
238 int max = pat.length();
239 buffer.setLength(0);
240 int brace_depth = 1;
241 boolean quoted = false;
243 for (; index < max; ++index)
245 char c = pat.charAt(index);
246 // First see if we should turn off quoting.
247 if (quoted)
249 if (c == '\'')
250 quoted = false;
251 // In both cases we fall through to inserting the
252 // character here.
254 // See if we have just a plain quote to insert.
255 else if (c == '\'' && index + 1 < max
256 && pat.charAt(index + 1) == '\'')
258 buffer.append(c);
259 ++index;
261 // See if quoting should turn on.
262 else if (c == '\'')
263 quoted = true;
264 else if (c == '{')
265 ++brace_depth;
266 else if (c == '}')
268 if (--brace_depth == 0)
269 break;
271 // Check for TERM after braces, because TERM might be `}'.
272 else if (c == term)
273 break;
274 // All characters, including opening and closing quotes, are
275 // inserted here.
276 buffer.append(c);
278 return index;
281 // This is used to parse a format element and whatever non-format
282 // text might trail it.
283 private static int scanFormat(String pat, int index, CPStringBuilder buffer,
284 List<MessageFormatElement> elts, Locale locale)
286 MessageFormatElement mfe = new MessageFormatElement ();
287 elts.add(mfe);
289 int max = pat.length();
291 // Skip the opening `{'.
292 ++index;
294 // Fetch the argument number.
295 index = scanFormatElement (pat, index, buffer, ',');
298 mfe.argNumber = Integer.parseInt(buffer.toString());
300 catch (NumberFormatException nfx)
302 IllegalArgumentException iae = new IllegalArgumentException(pat);
303 iae.initCause(nfx);
304 throw iae;
307 // Extract the element format.
308 if (index < max && pat.charAt(index) == ',')
310 index = scanFormatElement (pat, index + 1, buffer, ',');
311 mfe.type = buffer.toString();
313 // Extract the style.
314 if (index < max && pat.charAt(index) == ',')
316 index = scanFormatElement (pat, index + 1, buffer, '}');
317 mfe.style = buffer.toString ();
321 // Advance past the last terminator.
322 if (index >= max || pat.charAt(index) != '}')
323 throw new IllegalArgumentException("Missing '}' at end of message format");
324 ++index;
326 // Now fetch trailing string.
327 index = scanString (pat, index, buffer);
328 mfe.trailer = buffer.toString ();
330 mfe.setLocale(locale);
332 return index;
336 * Applies the specified pattern to this MessageFormat.
338 * @param newPattern The Pattern
340 public void applyPattern (String newPattern)
342 pattern = newPattern;
344 CPStringBuilder tempBuffer = new CPStringBuilder ();
346 int index = scanString (newPattern, 0, tempBuffer);
347 leader = tempBuffer.toString();
349 List<MessageFormatElement> elts = new ArrayList<MessageFormatElement>();
350 while (index < newPattern.length())
351 index = scanFormat (newPattern, index, tempBuffer, elts, locale);
353 elements = elts.toArray(new MessageFormatElement[elts.size()]);
357 * Overrides Format.clone()
359 public Object clone ()
361 MessageFormat c = (MessageFormat) super.clone ();
362 c.elements = (MessageFormatElement[]) elements.clone ();
363 return c;
367 * Overrides Format.equals(Object obj)
369 public boolean equals (Object obj)
371 if (! (obj instanceof MessageFormat))
372 return false;
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);
390 return 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,
415 FieldPosition fp)
417 return formatInternal(arguments, appendBuf, fp, null);
420 private StringBuffer formatInternal (Object arguments[],
421 StringBuffer appendBuf,
422 FieldPosition fp,
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)
434 unavailable = true;
435 else
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());
445 if (unavailable)
446 appendBuf.append("{" + elements[i].argNumber + "}");
447 else
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);
463 else
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);
482 else
484 if (output_iterator != null)
485 iterator = formatter.formatToCharacterIterator(thisArg);
486 else
487 formatter.format(thisArg, appendBuf, fp);
490 elements[i].format = formatter;
493 if (output_iterator != null)
495 HashMap<MessageFormat.Field, Integer> hash_argument =
496 new HashMap<MessageFormat.Field, Integer>();
497 int position = output_iterator.getEndIndex();
499 hash_argument.put (MessageFormat.Field.ARGUMENT,
500 Integer.valueOf(elements[i].argNumber));
503 if (iterator != null)
505 output_iterator.append(iterator);
506 output_iterator.addAttributes(hash_argument, position,
507 output_iterator.getEndIndex());
509 else
510 output_iterator.append(thisArg.toString(), hash_argument);
512 output_iterator.append(elements[i].trailer);
515 appendBuf.append(elements[i].trailer);
518 return appendBuf;
522 * Returns the pattern with the formatted objects. The first argument
523 * must be a array of Objects.
524 * This is equivalent to format((Object[]) objectArray, appendBuf, fpos)
526 * @param objectArray The object array to be formatted.
527 * @param appendBuf The StringBuffer where the text is appened.
528 * @param fpos A FieldPosition object (it is ignored).
530 public final StringBuffer format (Object objectArray, StringBuffer appendBuf,
531 FieldPosition fpos)
533 return format ((Object[])objectArray, appendBuf, fpos);
537 * Returns an array with the Formats for
538 * the arguments.
540 public Format[] getFormats ()
542 Format[] f = new Format[elements.length];
543 for (int i = elements.length - 1; i >= 0; --i)
544 f[i] = elements[i].setFormat;
545 return f;
549 * Returns the locale.
551 public Locale getLocale ()
553 return locale;
557 * Overrides Format.hashCode()
559 public int hashCode ()
561 // FIXME: not a very good hash.
562 return pattern.hashCode() + locale.hashCode();
565 private MessageFormat ()
570 * Creates a new MessageFormat object with
571 * the specified pattern
573 * @param pattern The Pattern
575 public MessageFormat(String pattern)
577 this(pattern, Locale.getDefault());
581 * Creates a new MessageFormat object with
582 * the specified pattern
584 * @param pattern The Pattern
585 * @param locale The Locale to use
587 * @since 1.4
589 public MessageFormat(String pattern, Locale locale)
591 this.locale = locale;
592 applyPattern (pattern);
596 * Parse a string <code>sourceStr</code> against the pattern specified
597 * to the MessageFormat constructor.
599 * @param sourceStr the string to be parsed.
600 * @param pos the current parse position (and eventually the error position).
601 * @return the array of parsed objects sorted according to their argument number
602 * in the pattern.
604 public Object[] parse (String sourceStr, ParsePosition pos)
606 // Check initial text.
607 int index = pos.getIndex();
608 if (! sourceStr.startsWith(leader, index))
610 pos.setErrorIndex(index);
611 return null;
613 index += leader.length();
615 ArrayList<Object> results = new ArrayList<Object>(elements.length);
616 // Now check each format.
617 for (int i = 0; i < elements.length; ++i)
619 Format formatter = null;
620 if (elements[i].setFormat != null)
621 formatter = elements[i].setFormat;
622 else if (elements[i].format != null)
623 formatter = elements[i].format;
625 Object value = null;
626 if (formatter instanceof ChoiceFormat)
628 // We must special-case a ChoiceFormat because it might
629 // have recursive formatting.
630 ChoiceFormat cf = (ChoiceFormat) formatter;
631 String[] formats = (String[]) cf.getFormats();
632 double[] limits = cf.getLimits();
633 MessageFormat subfmt = new MessageFormat ();
634 subfmt.setLocale(locale);
635 ParsePosition subpos = new ParsePosition (index);
637 int j;
638 for (j = 0; value == null && j < limits.length; ++j)
640 subfmt.applyPattern(formats[j]);
641 subpos.setIndex(index);
642 value = subfmt.parse(sourceStr, subpos);
644 if (value != null)
646 index = subpos.getIndex();
647 value = new Double (limits[j]);
650 else if (formatter != null)
652 pos.setIndex(index);
653 value = formatter.parseObject(sourceStr, pos);
654 if (value != null)
655 index = pos.getIndex();
657 else
659 // We have a String format. This can lose in a number
660 // of ways, but we give it a shot.
661 int next_index;
662 if (elements[i].trailer.length() > 0)
663 next_index = sourceStr.indexOf(elements[i].trailer, index);
664 else
665 next_index = sourceStr.length();
666 if (next_index == -1)
668 pos.setErrorIndex(index);
669 return null;
671 value = sourceStr.substring(index, next_index);
672 index = next_index;
675 if (value == null
676 || ! sourceStr.startsWith(elements[i].trailer, index))
678 pos.setErrorIndex(index);
679 return null;
682 if (elements[i].argNumber >= results.size())
684 // Emulate padding behaviour of Vector.setSize() with ArrayList
685 results.ensureCapacity(elements[i].argNumber + 1);
686 for (int a = results.size(); a <= elements[i].argNumber; ++a)
687 results.add(a, null);
689 results.set(elements[i].argNumber, value);
691 index += elements[i].trailer.length();
694 return results.toArray(new Object[results.size()]);
697 public Object[] parse (String sourceStr) throws ParseException
699 ParsePosition pp = new ParsePosition (0);
700 Object[] r = parse (sourceStr, pp);
701 if (r == null)
702 throw new ParseException ("couldn't parse string", pp.getErrorIndex());
703 return r;
706 public Object parseObject (String sourceStr, ParsePosition pos)
708 return parse (sourceStr, pos);
712 * Sets the format for the argument at an specified
713 * index.
715 * @param variableNum The index.
716 * @param newFormat The Format object.
718 public void setFormat (int variableNum, Format newFormat)
720 elements[variableNum].setFormat = newFormat;
724 * Sets the formats for the arguments.
726 * @param newFormats An array of Format objects.
728 public void setFormats (Format[] newFormats)
730 if (newFormats.length < elements.length)
731 throw new IllegalArgumentException("Not enough format objects");
733 int len = Math.min(newFormats.length, elements.length);
734 for (int i = 0; i < len; ++i)
735 elements[i].setFormat = newFormats[i];
739 * Sets the locale.
741 * @param loc A Locale
743 public void setLocale (Locale loc)
745 locale = loc;
746 if (elements != null)
748 for (int i = 0; i < elements.length; ++i)
749 elements[i].setLocale(loc);
754 * Returns the pattern.
756 public String toPattern ()
758 return pattern;
762 * Return the formatters used sorted by argument index. It uses the
763 * internal table to fill in this array: if a format has been
764 * set using <code>setFormat</code> or <code>setFormatByArgumentIndex</code>
765 * then it returns it at the right index. If not it uses the detected
766 * formatters during a <code>format</code> call. If nothing is known
767 * about that argument index it just puts null at that position.
768 * To get useful informations you may have to call <code>format</code>
769 * at least once.
771 * @return an array of formatters sorted by argument index.
773 public Format[] getFormatsByArgumentIndex()
775 int argNumMax = 0;
776 // First, find the greatest argument number.
777 for (int i=0;i<elements.length;i++)
778 if (elements[i].argNumber > argNumMax)
779 argNumMax = elements[i].argNumber;
781 Format[] formats = new Format[argNumMax];
782 for (int i=0;i<elements.length;i++)
784 if (elements[i].setFormat != null)
785 formats[elements[i].argNumber] = elements[i].setFormat;
786 else if (elements[i].format != null)
787 formats[elements[i].argNumber] = elements[i].format;
789 return formats;
793 * Set the format to used using the argument index number.
795 * @param argumentIndex the argument index.
796 * @param newFormat the format to use for this argument.
798 public void setFormatByArgumentIndex(int argumentIndex,
799 Format newFormat)
801 for (int i=0;i<elements.length;i++)
803 if (elements[i].argNumber == argumentIndex)
804 elements[i].setFormat = newFormat;
809 * Set the format for argument using a specified array of formatters
810 * which is sorted according to the argument index. If the number of
811 * elements in the array is fewer than the number of arguments only
812 * the arguments specified by the array are touched.
814 * @param newFormats array containing the new formats to set.
816 * @throws NullPointerException if newFormats is null
818 public void setFormatsByArgumentIndex(Format[] newFormats)
820 for (int i=0;i<newFormats.length;i++)
822 // Nothing better than that can exist here.
823 setFormatByArgumentIndex(i, newFormats[i]);
827 // The pattern string.
828 private String pattern;
829 // The locale.
830 private Locale locale;
831 // Variables.
832 private MessageFormatElement[] elements;
833 // Leader text.
834 private String leader;