1 /* MessageFormat.java - Localized message formatting.
2    Copyright (C) 1999, 2001, 2002 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., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 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 java.util.Date;
42 import java.util.Locale;
43 import java.util.Vector;
44 
45 /**
46  * @author Tom Tromey <tromey@cygnus.com>
47  * @author Jorge Aliss <jaliss@hotmail.com>
48  * @date March 3, 1999
49  */
50 /* Written using "Java Class Libraries", 2nd edition, plus online
51  * API docs for JDK 1.2 from http://www.javasoft.com.
52  * Status:  Believed complete and correct to 1.2, except serialization.
53  *          and parsing.
54  */
55 final class MessageFormatElement
56 {
57   // Argument number.
58   int argNumber;
59   // Formatter to be used.  This is the format set by setFormat.
60   Format setFormat;
61   // Formatter to be used based on the type.
62   Format format;
63 
64   // Argument will be checked to make sure it is an instance of this
65   // class.
66   Class formatClass;
67 
68   // Formatter type.
69   String type;
70   // Formatter style.
71   String style;
72 
73   // Text to follow this element.
74   String trailer;
75 
76   // Recompute the locale-based formatter.
setLocale(Locale loc)77   void setLocale (Locale loc)
78   {
79     if (type == null)
80       ;
81     else if (type.equals("number"))
82       {
83 	formatClass = java.lang.Number.class;
84 
85 	if (style == null)
86 	  format = NumberFormat.getInstance(loc);
87 	else if (style.equals("currency"))
88 	  format = NumberFormat.getCurrencyInstance(loc);
89 	else if (style.equals("percent"))
90 	  format = NumberFormat.getPercentInstance(loc);
91 	else if (style.equals("integer"))
92 	  {
93 	    NumberFormat nf = NumberFormat.getNumberInstance(loc);
94 	    nf.setMaximumFractionDigits(0);
95 	    nf.setGroupingUsed(false);
96 	    format = nf;
97 	  }
98 	else
99 	  {
100 	    format = NumberFormat.getNumberInstance(loc);
101 	    DecimalFormat df = (DecimalFormat) format;
102 	    df.applyPattern(style);
103 	  }
104       }
105     else if (type.equals("time") || type.equals("date"))
106       {
107 	formatClass = java.util.Date.class;
108 
109 	int val = DateFormat.DEFAULT;
110 	if (style == null)
111 	  ;
112 	else if (style.equals("short"))
113 	  val = DateFormat.SHORT;
114 	else if (style.equals("medium"))
115 	  val = DateFormat.MEDIUM;
116 	else if (style.equals("long"))
117 	  val = DateFormat.LONG;
118 	else if (style.equals("full"))
119 	  val = DateFormat.FULL;
120 
121 	if (type.equals("time"))
122 	  format = DateFormat.getTimeInstance(val, loc);
123 	else
124 	  format = DateFormat.getDateInstance(val, loc);
125 
126 	if (style != null && val == DateFormat.DEFAULT)
127 	  {
128 	    SimpleDateFormat sdf = (SimpleDateFormat) format;
129 	    sdf.applyPattern(style);
130 	  }
131       }
132     else if (type.equals("choice"))
133       {
134 	formatClass = java.lang.Number.class;
135 
136 	if (style == null)
137 	  throw new
138 	    IllegalArgumentException ("style required for choice format");
139 	format = new ChoiceFormat (style);
140       }
141   }
142 }
143 
144 public class MessageFormat extends Format
145 {
146   private static final long serialVersionUID = 6479157306784022952L;
147 
148   // Helper that returns the text up to the next format opener.  The
149   // text is put into BUFFER.  Returns index of character after end of
150   // string.  Throws IllegalArgumentException on error.
scanString(String pat, int index, StringBuffer buffer)151   private static final int scanString (String pat, int index,
152 				       StringBuffer buffer)
153   {
154     int max = pat.length();
155     buffer.setLength(0);
156     for (; index < max; ++index)
157       {
158 	char c = pat.charAt(index);
159 	if (c == '\'' && index + 2 < max && pat.charAt(index + 2) == '\'')
160 	  {
161 	    buffer.append(pat.charAt(index + 1));
162 	    index += 2;
163 	  }
164 	else if (c == '\'' && index + 1 < max
165 		 && pat.charAt(index + 1) == '\'')
166 	  {
167 	    buffer.append(c);
168 	    ++index;
169 	  }
170 	else if (c == '{')
171 	  break;
172 	else if (c == '}')
173 	  throw new IllegalArgumentException("Found '}' without '{'");
174 	else
175 	  buffer.append(c);
176       }
177     return index;
178   }
179 
180   // This helper retrieves a single part of a format element.  Returns
181   // the index of the terminating character.
scanFormatElement(String pat, int index, StringBuffer buffer, char term)182   private static final int scanFormatElement (String pat, int index,
183 					      StringBuffer buffer,
184 					      char term)
185   {
186     int max = pat.length();
187     buffer.setLength(0);
188     int brace_depth = 1;
189 
190     for (; index < max; ++index)
191       {
192 	char c = pat.charAt(index);
193 	if (c == '\'' && index + 2 < max && pat.charAt(index + 2) == '\'')
194 	  {
195 	    buffer.append(c);
196 	    buffer.append(pat.charAt(index + 1));
197 	    buffer.append(c);
198 	    index += 2;
199 	  }
200 	else if (c == '\'' && index + 1 < max
201 		 && pat.charAt(index + 1) == '\'')
202 	  {
203 	    buffer.append(c);
204 	    ++index;
205 	  }
206 	else if (c == '{')
207 	  {
208 	    buffer.append(c);
209 	    ++brace_depth;
210 	  }
211 	else if (c == '}')
212 	  {
213 	    if (--brace_depth == 0)
214 	      break;
215 	    buffer.append(c);
216 	  }
217 	// Check for TERM after braces, because TERM might be `}'.
218 	else if (c == term)
219 	  break;
220 	else
221 	  buffer.append(c);
222       }
223     return index;
224   }
225 
226   // This is used to parse a format element and whatever non-format
227   // text might trail it.
scanFormat(String pat, int index, StringBuffer buffer, Vector elts, Locale locale)228   private static final int scanFormat (String pat, int index,
229 				       StringBuffer buffer, Vector elts,
230 				       Locale locale)
231   {
232     MessageFormatElement mfe = new MessageFormatElement ();
233     elts.addElement(mfe);
234 
235     int max = pat.length();
236 
237     // Skip the opening `{'.
238     ++index;
239 
240     // Fetch the argument number.
241     index = scanFormatElement (pat, index, buffer, ',');
242     try
243       {
244 	mfe.argNumber = Integer.parseInt(buffer.toString());
245       }
246     catch (NumberFormatException nfx)
247       {
248 	throw new IllegalArgumentException("Failed to parse integer string");
249       }
250 
251     // Extract the element format.
252     if (index < max && pat.charAt(index) == ',')
253       {
254 	index = scanFormatElement (pat, index + 1, buffer, ',');
255 	mfe.type = buffer.toString();
256 
257 	// Extract the style.
258 	if (index < max && pat.charAt(index) == ',')
259 	  {
260 	    index = scanFormatElement (pat, index + 1, buffer, '}');
261 	    mfe.style = buffer.toString ();
262 	  }
263       }
264 
265     // Advance past the last terminator.
266     if (index >= max || pat.charAt(index) != '}')
267       throw new IllegalArgumentException("Missing '}' at end of message format");
268     ++index;
269 
270     // Now fetch trailing string.
271     index = scanString (pat, index, buffer);
272     mfe.trailer = buffer.toString ();
273 
274     mfe.setLocale(locale);
275 
276     return index;
277   }
278 
279   /**
280    * Applies the specified pattern to this MessageFormat.
281    *
282    * @param aPattern The Pattern
283    */
applyPattern(String newPattern)284   public void applyPattern (String newPattern)
285   {
286     pattern = newPattern;
287 
288     StringBuffer tempBuffer = new StringBuffer ();
289 
290     int index = scanString (newPattern, 0, tempBuffer);
291     leader = tempBuffer.toString();
292 
293     Vector elts = new Vector ();
294     while (index < newPattern.length())
295       index = scanFormat (newPattern, index, tempBuffer, elts, locale);
296 
297     elements = new MessageFormatElement[elts.size()];
298     elts.copyInto(elements);
299   }
300 
301   /**
302    * Overrides Format.clone()
303    */
clone()304   public Object clone ()
305   {
306     MessageFormat c = (MessageFormat) super.clone ();
307     c.elements = (MessageFormatElement[]) elements.clone ();
308     return c;
309   }
310 
311   /**
312    * Overrides Format.equals(Object obj)
313    */
equals(Object obj)314   public boolean equals (Object obj)
315   {
316     if (! (obj instanceof MessageFormat))
317       return false;
318     MessageFormat mf = (MessageFormat) obj;
319     return (pattern.equals(mf.pattern)
320 	    && locale.equals(mf.locale));
321   }
322 
323   /**
324    * A convinience method to format patterns.
325    *
326    * @param aPattern The pattern used when formatting.
327    * @param arguments The array containing the objects to be formatted.
328    */
format(String pattern, Object arguments[])329   public static String format (String pattern, Object arguments[])
330   {
331     MessageFormat mf = new MessageFormat (pattern);
332     StringBuffer sb = new StringBuffer ();
333     FieldPosition fp = new FieldPosition (NumberFormat.INTEGER_FIELD);
334     return mf.format(arguments, sb, fp).toString();
335   }
336 
337   /**
338    * Returns the pattern with the formatted objects.
339    *
340    * @param source The array containing the objects to be formatted.
341    * @param result The StringBuffer where the text is appened.
342    * @param fp A FieldPosition object (it is ignored).
343    */
format(Object arguments[], StringBuffer appendBuf, FieldPosition ignore)344   public final StringBuffer format (Object arguments[], StringBuffer appendBuf,
345 				    FieldPosition ignore)
346   {
347     appendBuf.append(leader);
348 
349     for (int i = 0; i < elements.length; ++i)
350       {
351 	if (elements[i].argNumber >= arguments.length)
352 	  throw new IllegalArgumentException("Not enough arguments given");
353 
354 	Object thisArg = arguments[elements[i].argNumber];
355 
356 	Format formatter = null;
357 	if (elements[i].setFormat != null)
358 	  formatter = elements[i].setFormat;
359 	else if (elements[i].format != null)
360 	  {
361 	    if (elements[i].formatClass != null
362 		&& ! elements[i].formatClass.isInstance(thisArg))
363 	      throw new IllegalArgumentException("Wrong format class");
364 
365 	    formatter = elements[i].format;
366 	  }
367 	else if (thisArg instanceof Number)
368 	  formatter = NumberFormat.getInstance(locale);
369 	else if (thisArg instanceof Date)
370 	  formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
371 	else
372 	  appendBuf.append(thisArg);
373 
374 	if (formatter != null)
375 	  {
376 	    // Special-case ChoiceFormat.
377 	    if (formatter instanceof ChoiceFormat)
378 	      {
379 		StringBuffer buf = new StringBuffer ();
380 		formatter.format(thisArg, buf, ignore);
381 		MessageFormat mf = new MessageFormat ();
382 		mf.setLocale(locale);
383 		mf.applyPattern(buf.toString());
384 		mf.format(arguments, appendBuf, ignore);
385 	      }
386 	    else
387 	      formatter.format(thisArg, appendBuf, ignore);
388 	  }
389 
390 	appendBuf.append(elements[i].trailer);
391       }
392 
393     return appendBuf;
394   }
395 
396   /**
397    * Returns the pattern with the formatted objects.
398    *
399    * @param source The object to be formatted.
400    * @param result The StringBuffer where the text is appened.
401    * @param fp A FieldPosition object (it is ignored).
402    */
format(Object singleArg, StringBuffer appendBuf, FieldPosition ignore)403   public final StringBuffer format (Object singleArg, StringBuffer appendBuf,
404 				    FieldPosition ignore)
405   {
406     Object[] args;
407 
408     if (singleArg instanceof Object[])
409       {
410 	// This isn't specified in any manual, but it follows the
411 	// JDK implementation.
412 	args = (Object[]) singleArg;
413       }
414     else
415       {
416 	args = new Object[1];
417 	args[0] = singleArg;
418       }
419     return format (args, appendBuf, ignore);
420   }
421 
422   /**
423    * Returns an array with the Formats for
424    * the arguments.
425    */
getFormats()426   public Format[] getFormats ()
427   {
428     Format[] f = new Format[elements.length];
429     for (int i = elements.length - 1; i >= 0; --i)
430       f[i] = elements[i].setFormat;
431     return f;
432   }
433 
434   /**
435    * Returns the locale.
436    */
getLocale()437   public Locale getLocale ()
438   {
439     return locale;
440   }
441 
442   /**
443    * Overrides Format.hashCode()
444    */
hashCode()445   public int hashCode ()
446   {
447     // FIXME: not a very good hash.
448     return pattern.hashCode() + locale.hashCode();
449   }
450 
MessageFormat()451   private MessageFormat ()
452   {
453   }
454 
455   /**
456    * Creates a new MessageFormat object with
457    * the specified pattern
458    *
459    * @param pattern The Pattern
460    */
MessageFormat(String pattern)461   public MessageFormat(String pattern)
462   {
463     this(pattern, Locale.getDefault());
464   }
465 
466   /**
467    * Creates a new MessageFormat object with
468    * the specified pattern
469    *
470    * @param pattern The Pattern
471    * @param locale The Locale to use
472    *
473    * @since 1.4
474    */
MessageFormat(String pattern, Locale locale)475   public MessageFormat(String pattern, Locale locale)
476   {
477     this.locale = locale;
478     applyPattern (pattern);
479   }
480 
parse(String sourceStr, ParsePosition pos)481   public Object[] parse (String sourceStr, ParsePosition pos)
482   {
483     // Check initial text.
484     int index = pos.getIndex();
485     if (! sourceStr.startsWith(leader, index))
486       {
487 	pos.setErrorIndex(index);
488 	return null;
489       }
490     index += leader.length();
491 
492     Vector results = new Vector (elements.length, 1);
493     // Now check each format.
494     for (int i = 0; i < elements.length; ++i)
495       {
496 	Format formatter = null;
497 	if (elements[i].setFormat != null)
498 	  formatter = elements[i].setFormat;
499 	else if (elements[i].format != null)
500 	  formatter = elements[i].format;
501 
502 	Object value = null;
503 	if (formatter instanceof ChoiceFormat)
504 	  {
505 	    // We must special-case a ChoiceFormat because it might
506 	    // have recursive formatting.
507 	    ChoiceFormat cf = (ChoiceFormat) formatter;
508 	    String[] formats = (String[]) cf.getFormats();
509 	    double[] limits = (double[]) cf.getLimits();
510 	    MessageFormat subfmt = new MessageFormat ();
511 	    subfmt.setLocale(locale);
512 	    ParsePosition subpos = new ParsePosition (index);
513 
514 	    int j;
515 	    for (j = 0; value == null && j < limits.length; ++j)
516 	      {
517 		subfmt.applyPattern(formats[j]);
518 		subpos.setIndex(index);
519 		value = subfmt.parse(sourceStr, subpos);
520 	      }
521 	    if (value != null)
522 	      {
523 		index = subpos.getIndex();
524 		value = new Double (limits[j]);
525 	      }
526 	  }
527 	else if (formatter != null)
528 	  {
529 	    pos.setIndex(index);
530 	    value = formatter.parseObject(sourceStr, pos);
531 	    if (value != null)
532 	      index = pos.getIndex();
533 	  }
534 	else
535 	  {
536 	    // We have a String format.  This can lose in a number
537 	    // of ways, but we give it a shot.
538 	    int next_index = sourceStr.indexOf(elements[i].trailer, index);
539 	    if (next_index == -1)
540 	      {
541 		pos.setErrorIndex(index);
542 		return null;
543 	      }
544 	    value = sourceStr.substring(index, next_index);
545 	    index = next_index;
546 	  }
547 
548 	if (value == null
549 	    || ! sourceStr.startsWith(elements[i].trailer, index))
550 	  {
551 	    pos.setErrorIndex(index);
552 	    return null;
553 	  }
554 
555 	if (elements[i].argNumber >= results.size())
556 	  results.setSize(elements[i].argNumber + 1);
557 	results.setElementAt(value, elements[i].argNumber);
558 
559 	index += elements[i].trailer.length();
560       }
561 
562     Object[] r = new Object[results.size()];
563     results.copyInto(r);
564     return r;
565   }
566 
parse(String sourceStr)567   public Object[] parse (String sourceStr) throws ParseException
568   {
569     ParsePosition pp = new ParsePosition (0);
570     Object[] r = parse (sourceStr, pp);
571     if (r == null)
572       throw new ParseException ("couldn't parse string", pp.getErrorIndex());
573     return r;
574   }
575 
parseObject(String sourceStr, ParsePosition pos)576   public Object parseObject (String sourceStr, ParsePosition pos)
577   {
578     return parse (sourceStr, pos);
579   }
580 
581   /**
582    * Sets the format for the argument at an specified
583    * index.
584    *
585    * @param index The index.
586    * @format The Format object.
587    */
setFormat(int variableNum, Format newFormat)588   public void setFormat (int variableNum, Format newFormat)
589   {
590     elements[variableNum].setFormat = newFormat;
591   }
592 
593   /**
594    * Sets the formats for the arguments.
595    *
596    * @param formats An array of Format objects.
597    */
setFormats(Format[] newFormats)598   public void setFormats (Format[] newFormats)
599   {
600     if (newFormats.length < elements.length)
601       throw new IllegalArgumentException("Not enough format objects");
602 
603     int len = Math.min(newFormats.length, elements.length);
604     for (int i = 0; i < len; ++i)
605       elements[i].setFormat = newFormats[i];
606   }
607 
608   /**
609    * Sets the locale.
610    *
611    * @param locale A Locale
612    */
setLocale(Locale loc)613   public void setLocale (Locale loc)
614   {
615     locale = loc;
616     if (elements != null)
617       {
618 	for (int i = 0; i < elements.length; ++i)
619 	  elements[i].setLocale(loc);
620       }
621   }
622 
623   /**
624    * Returns the pattern.
625    */
toPattern()626   public String toPattern ()
627   {
628     return pattern;
629   }
630 
631   // The pattern string.
632   private String pattern;
633   // The locale.
634   private Locale locale;
635   // Variables.
636   private MessageFormatElement[] elements;
637   // Leader text.
638   private String leader;
639 }
640