diff options
Diffstat (limited to 'libjava/java/text')
-rw-r--r-- | libjava/java/text/BreakIterator.java | 160 | ||||
-rw-r--r-- | libjava/java/text/CharacterIterator.java | 36 | ||||
-rw-r--r-- | libjava/java/text/ChoiceFormat.java | 310 | ||||
-rw-r--r-- | libjava/java/text/DateFormat.java | 301 | ||||
-rw-r--r-- | libjava/java/text/DateFormatSymbols.java | 265 | ||||
-rw-r--r-- | libjava/java/text/DecimalFormat.java | 983 | ||||
-rw-r--r-- | libjava/java/text/DecimalFormatSymbols.java | 293 | ||||
-rw-r--r-- | libjava/java/text/FieldPosition.java | 65 | ||||
-rw-r--r-- | libjava/java/text/Format.java | 51 | ||||
-rw-r--r-- | libjava/java/text/MessageFormat.java | 543 | ||||
-rw-r--r-- | libjava/java/text/NumberFormat.java | 236 | ||||
-rw-r--r-- | libjava/java/text/ParseException.java | 34 | ||||
-rw-r--r-- | libjava/java/text/ParsePosition.java | 59 | ||||
-rw-r--r-- | libjava/java/text/SimpleDateFormat.java | 522 | ||||
-rw-r--r-- | libjava/java/text/StringCharacterIterator.java | 142 |
15 files changed, 4000 insertions, 0 deletions
diff --git a/libjava/java/text/BreakIterator.java b/libjava/java/text/BreakIterator.java new file mode 100644 index 0000000..0e09d45 --- /dev/null +++ b/libjava/java/text/BreakIterator.java @@ -0,0 +1,160 @@ +// BreakIterator.java - Iterate over logical breaks in text. + +/* Copyright (C) 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +import java.io.Serializable; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * @author Tom Tromey <tromey@cygnus.com> + * @date March 19, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: Believed complete and correct to 1.1. + */ + +public abstract class BreakIterator implements Cloneable, Serializable +{ + // The value was discovered by writing a test program. + public static final int DONE = -1; + + protected BreakIterator () + { + } + + public abstract int current (); + public abstract int first (); + public abstract int following (int pos); + + public static synchronized Locale[] getAvailableLocales () + { + // FIXME. + return null; + } + + private static BreakIterator getInstance (String type, Locale loc) + { + String className; + try + { + ResourceBundle res + = ResourceBundle.getBundle("gnu.gcj.text.LocaleData", loc); + className = res.getString(type); + } + catch (MissingResourceException x) + { + return null; + } + try + { + Class k = Class.forName(className); + return (BreakIterator) k.newInstance(); + } + catch (ClassNotFoundException x1) + { + return null; + } + catch (InstantiationException x2) + { + return null; + } + catch (IllegalAccessException x3) + { + return null; + } + } + + public static BreakIterator getCharacterInstance () + { + return getCharacterInstance (Locale.getDefault()); + } + + public static BreakIterator getCharacterInstance (Locale loc) + { + BreakIterator r = getInstance ("CharacterIterator", loc); + if (r == null) + r = new gnu.gcj.text.CharacterBreakIterator (); + return r; + } + + public static BreakIterator getLineInstance () + { + return getLineInstance (Locale.getDefault()); + } + + public static BreakIterator getLineInstance (Locale loc) + { + BreakIterator r = getInstance ("LineIterator", loc); + if (r == null) + r = new gnu.gcj.text.LineBreakIterator (); + return r; + } + + public static BreakIterator getSentenceInstance () + { + return getSentenceInstance (Locale.getDefault()); + } + + public static BreakIterator getSentenceInstance (Locale loc) + { + BreakIterator r = getInstance ("SentenceIterator", loc); + if (r == null) + r = new gnu.gcj.text.SentenceBreakIterator (); + return r; + } + + public abstract CharacterIterator getText (); + + public static BreakIterator getWordInstance () + { + return getWordInstance (Locale.getDefault()); + } + + public static BreakIterator getWordInstance (Locale loc) + { + BreakIterator r = getInstance ("WordIterator", loc); + if (r == null) + r = new gnu.gcj.text.WordBreakIterator (); + return r; + } + + public boolean isBoundary (int pos) + { + if (pos == 0) + return true; + return following (pos - 1) == pos; + } + + public abstract int last (); + public abstract int next (); + public abstract int next (int n); + + public int preceding (int pos) + { + if (following (pos) == DONE) + last (); + while (previous () >= pos) + ; + return current (); + } + + public abstract int previous (); + + public void setText (String newText) + { + setText (new StringCharacterIterator (newText)); + } + + public abstract void setText (CharacterIterator newText); +} diff --git a/libjava/java/text/CharacterIterator.java b/libjava/java/text/CharacterIterator.java new file mode 100644 index 0000000..d34e988 --- /dev/null +++ b/libjava/java/text/CharacterIterator.java @@ -0,0 +1,36 @@ +// CharacterIterator.java - Protocol for iterating over Unicode characters. + +/* Copyright (C) 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +/** + * @author Tom Tromey <tromey@cygnus.com> + * @date February 22, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: Believed complete and correct to 1.1. + */ + +public interface CharacterIterator extends Cloneable +{ + public abstract Object clone (); + public abstract char current (); + public abstract char first (); + public abstract int getBeginIndex (); + public abstract int getEndIndex (); + public abstract int getIndex (); + public abstract char last (); + public abstract char next (); + public abstract char previous (); + public abstract char setIndex (int idx); + + public static final char DONE = '\uffff'; +} diff --git a/libjava/java/text/ChoiceFormat.java b/libjava/java/text/ChoiceFormat.java new file mode 100644 index 0000000..bed62a7 --- /dev/null +++ b/libjava/java/text/ChoiceFormat.java @@ -0,0 +1,310 @@ +// ChoiceFormat.java - Formatter for `switch'-like string substitution. + +/* Copyright (C) 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +import java.util.Vector; + +/** + * @author Tom Tromey <tromey@cygnus.com> + * @date March 9, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 from http://www.javasoft.com. + * Status: Believed complete and correct to 1.1. + */ + +public class ChoiceFormat extends NumberFormat +{ + // Note: we assume the same kind of quoting rules apply here. + // This isn't explicitly documented. But for instance we accept + // '#' as a literal hash in a format string. + public void applyPattern (String newPattern) + { + int index = 0, max = newPattern.length(); + Vector stringVec = new Vector (); + Vector limitVec = new Vector (); + StringBuffer buf = new StringBuffer (); + + while (true) + { + // Find end of double. + int dstart = index; + while (index < max) + { + char c = newPattern.charAt(index); + if (c == '#' || c == '\u2064' || c == '<') + break; + ++index; + } + + if (index == max) + throw new IllegalArgumentException ("unexpected end of text"); + Double d = new Double (newPattern.substring(dstart, index)); + + if (newPattern.charAt(index) == '<') + d = new Double (nextDouble (d.doubleValue())); + + limitVec.addElement(d); + + // Scan text. + ++index; + buf.setLength(0); + while (index < max) + { + char c = newPattern.charAt(index); + if (c == '\'' && index < max + 1 + && newPattern.charAt(index + 1) == '\'') + { + buf.append(c); + ++index; + } + else if (c == '\'' && index < max + 2) + { + buf.append(newPattern.charAt(index + 1)); + index += 2; + } + else if (c == '|') + break; + else + buf.append(c); + ++index; + } + + stringVec.addElement(buf.toString()); + if (index == max) + break; + ++index; + } + + strings = new String[stringVec.size()]; + stringVec.copyInto(strings); + + limits = new double[limitVec.size()]; + for (int i = 0; i < limits.length; ++i) + { + Double d = (Double) limitVec.elementAt(i); + limits[i] = d.doubleValue(); + } + } + + public ChoiceFormat (String newPattern) + { + super (); + applyPattern (newPattern); + } + + public ChoiceFormat (double[] limits, String[] strings) + { + super (); + setChoices (limits, strings); + } + + public Object clone () + { + return new ChoiceFormat (limits, strings); + } + + public boolean equals (Object obj) + { + if (! (obj instanceof ChoiceFormat)) + return false; + ChoiceFormat cf = (ChoiceFormat) obj; + if (limits.length != cf.limits.length) + return false; + for (int i = limits.length - 1; i >= 0; --i) + { + if (limits[i] != cf.limits[i] + || !strings[i].equals(cf.strings[i])) + return false; + } + return true; + } + + public StringBuffer format (long num, StringBuffer appendBuf, + FieldPosition pos) + { + return format ((double) num, appendBuf, pos); + } + + public StringBuffer format (double num, StringBuffer appendBuf, + FieldPosition pos) + { + if (limits.length == 0) + return appendBuf; + + int index = 0; + if (! Double.isNaN(num) && num >= limits[0]) + { + for (; index < limits.length - 1; ++index) + { + if (limits[index] <= num + && index != limits.length - 2 + && num < limits[index + 1]) + break; + } + } + + return appendBuf.append(strings[index]); + } + + public Object[] getFormats () + { + return (Object[]) strings.clone(); + } + + public double[] getLimits () + { + return (double[]) limits.clone(); + } + + public int hashCode () + { + int hash = 0; + for (int i = 0; i < limits.length; ++i) + { + long v = Double.doubleToLongBits(limits[i]); + hash ^= (v ^ (v >>> 32)); + hash ^= strings[i].hashCode(); + } + return hash; + } + + public static final double nextDouble (double d) + { + return nextDouble (d, true); + } + + public static final double nextDouble (double d, boolean next) + { + if (Double.isInfinite(d) || Double.isNaN(d)) + return d; + + long bits = Double.doubleToLongBits(d); + + long mantMask = (1L << mantissaBits) - 1; + long mantissa = bits & mantMask; + + long expMask = (1L << exponentBits) - 1; + long exponent = (bits >>> mantissaBits) & expMask; + + if (next ^ (bits < 0)) // Increment magnitude + { + if (mantissa == (1L << mantissaBits) - 1) + { + mantissa = 0L; + exponent++; + + // Check for absolute overflow. + if (exponent >= (1L << mantissaBits)) + return (bits > 0) ? Double.POSITIVE_INFINITY + : Double.NEGATIVE_INFINITY; + } + else + mantissa++; + } + else // Decrement magnitude + { + if (exponent == 0L && mantissa == 0L) + { + // The only case where there is a change of sign + return next ? Double.MIN_VALUE : -Double.MIN_VALUE; + } + else + { + if (mantissa == 0L) + { + mantissa = (1L << mantissaBits) - 1; + exponent--; + } + else + mantissa--; + } + } + + long result = bits < 0 ? 1 : 0; + result = (result << exponentBits) | exponent; + result = (result << mantissaBits) | mantissa; + return Double.longBitsToDouble(result); + } + + public Number parse (String sourceStr, ParsePosition pos) + { + int index = pos.getIndex(); + for (int i = 0; i < limits.length; ++i) + { + if (sourceStr.startsWith(strings[i], index)) + { + pos.setIndex(index + strings[i].length()); + return new Double (limits[i]); + } + } + pos.setErrorIndex(index); + return new Double (Double.NaN); + } + + public static final double previousDouble (double d) + { + return nextDouble (d, false); + } + + public void setChoices (double[] limits, String[] strings) + { + if (limits == null || strings == null) + throw new NullPointerException (); + if (limits.length != strings.length) + throw new IllegalArgumentException (); + this.strings = (String[]) strings.clone(); + this.limits = (double[]) limits.clone(); + } + + private final void quoteString (StringBuffer dest, String text) + { + int max = text.length(); + for (int i = 0; i < max; ++i) + { + char c = text.charAt(i); + if (c == '\'') + { + dest.append(c); + dest.append(c); + } + else if (c == '#' || c == '|' || c == '\u2064' || c == '<') + { + dest.append('\''); + dest.append(c); + dest.append('\''); + } + else + dest.append(c); + } + } + + public String toPattern () + { + StringBuffer result = new StringBuffer (); + for (int i = 0; i < limits.length; ++i) + { + result.append(limits[i]); + result.append('#'); + quoteString (result, strings[i]); + } + return result.toString(); + } + + // Formats and limits. + private String[] strings; + private double[] limits; + + // Number of mantissa bits in double. + private static final int mantissaBits = 52; + // Number of exponent bits in a double. + private static final int exponentBits = 11; +} diff --git a/libjava/java/text/DateFormat.java b/libjava/java/text/DateFormat.java new file mode 100644 index 0000000..bfd6b01 --- /dev/null +++ b/libjava/java/text/DateFormat.java @@ -0,0 +1,301 @@ +/* Copyright (C) 1998, 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +import java.util.*; + +/** + * @author Per Bothner <bothner@cygnus.com> + * @date October 25, 1998. + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: Mostly complete; search for FIXME to see omissions. + */ + +public abstract class DateFormat extends Format implements Cloneable +{ + protected Calendar calendar; + protected NumberFormat numberFormat; + + // (Values determined using a test program.) + public final static int FULL = 0; + public final static int LONG = 1; + public final static int MEDIUM = 2; + public final static int SHORT = 3; + public final static int DEFAULT = MEDIUM; + + public final static int ERA_FIELD = 0; + public final static int YEAR_FIELD = 1; + public final static int MONTH_FIELD = 2; + public final static int DATE_FIELD = 3; + public final static int HOUR_OF_DAY1_FIELD = 4; + public final static int HOUR_OF_DAY0_FIELD = 5; + public final static int MINUTE_FIELD = 6; + public final static int SECOND_FIELD = 7; + public final static int MILLISECOND_FIELD = 8; + public final static int DAY_OF_WEEK_FIELD = 9; + public final static int DAY_OF_YEAR_FIELD = 10; + public final static int DAY_OF_WEEK_IN_MONTH_FIELD = 11; + public final static int WEEK_OF_YEAR_FIELD = 12; + public final static int WEEK_OF_MONTH_FIELD = 13; + public final static int AM_PM_FIELD = 14; + public final static int HOUR1_FIELD = 15; + public final static int HOUR0_FIELD = 16; + public final static int TIMEZONE_FIELD = 17; + + protected DateFormat () + { + } + + public boolean equals (Object obj) + { + if (! (obj instanceof DateFormat)) + return false; + DateFormat d = (DateFormat) obj; + return calendar.equals(d.calendar) && numberFormat.equals(d.numberFormat); + } + + public final StringBuffer format (Object obj, + StringBuffer buf, FieldPosition pos) + { + if (obj instanceof Number) + return format (new Date(((Number) obj).longValue()), buf, pos); + return format ((Date) obj, buf, pos); + } + + public final String format (Date date) + { + StringBuffer sb = new StringBuffer (); + format (date, sb, new FieldPosition (MONTH_FIELD)); + return sb.toString(); + } + + public abstract StringBuffer format (Date date, + StringBuffer buf, FieldPosition pos); + + public static Locale[] getAvailableLocales () + { + return null; // FIXME + } + + public Calendar getCalendar () + { + return calendar; + } + + private static final DateFormat computeInstance (int style, Locale loc, + boolean use_date, + boolean use_time) + { + ResourceBundle res; + try + { + res = ResourceBundle.getBundle("gnu.gcj.text.LocaleData", loc); + } + catch (MissingResourceException x) + { + res = null; + } + + String pattern = null; + if (use_date) + { + String name, def; + switch (style) + { + case FULL: + name = "fullDateFormat"; + def = "EEEE MMMM d, yyyy G"; + break; + case LONG: + name = "longDateFormat"; + def = "MMMM d, yyyy"; + break; + case MEDIUM: + name = "mediumDateFormat"; + def = "d-MMM-yy"; + break; + case SHORT: + name = "shortDateFormat"; + def = "M/d/yy"; + break; + default: + throw new IllegalArgumentException (); + } + try + { + pattern = res == null ? def : res.getString(name); + } + catch (MissingResourceException x) + { + pattern = def; + } + } + + if (use_time) + { + if (pattern == null) + pattern = ""; + else + pattern += " "; + + String name, def; + switch (style) + { + case FULL: + name = "fullTimeFormat"; + def = "h:mm:ss;S 'o''clock' a z"; + break; + case LONG: + name = "longTimeFormat"; + def = "h:mm:ss a z"; + break; + case MEDIUM: + name = "mediumTimeFormat"; + def = "h:mm:ss a"; + break; + case SHORT: + name = "shortTimeFormat"; + def = "h:mm a"; + break; + default: + throw new IllegalArgumentException (); + } + + String s; + try + { + s = res == null ? def : res.getString(name); + } + catch (MissingResourceException x) + { + s = def; + } + pattern += s; + } + + return new SimpleDateFormat (pattern, loc); + } + + public static final DateFormat getDateInstance () + { + return getDateInstance (DEFAULT, Locale.getDefault()); + } + + public static final DateFormat getDateInstance (int style) + { + return getDateInstance (style, Locale.getDefault()); + } + + public static final DateFormat getDateInstance (int style, Locale loc) + { + return computeInstance (style, loc, true, false); + } + + public static final DateFormat getDateTimeInstance () + { + return getDateTimeInstance (DEFAULT, Locale.getDefault()); + } + + public static final DateFormat getDateTimeInstance (int style) + { + return getDateTimeInstance (style, Locale.getDefault()); + } + + public static final DateFormat getDateTimeInstance (int style, Locale loc) + { + return computeInstance (style, loc, true, true); + } + + public static final DateFormat getInstance () + { + // JCL book says SHORT. + return getDateTimeInstance (SHORT, Locale.getDefault()); + } + + public NumberFormat getNumberFormat () + { + return numberFormat; + } + + public static final DateFormat getTimeInstance () + { + return getTimeInstance (DEFAULT, Locale.getDefault()); + } + + public static final DateFormat getTimeInstance (int style) + { + return getTimeInstance (style, Locale.getDefault()); + } + + public static final DateFormat getTimeInstance (int style, Locale loc) + { + return computeInstance (style, loc, false, true); + } + + public TimeZone getTimeZone () + { + return calendar.getTimeZone(); + } + + public int hashCode () + { + int hash = calendar.hashCode(); + if (numberFormat != null) + hash ^= numberFormat.hashCode(); + return hash; + } + + public boolean isLenient () + { + return calendar.isLenient(); + } + + public Date parse (String source) throws ParseException + { + ParsePosition pos = new ParsePosition(0); + Date result = parse (source, pos); + if (result == null) + { + int index = pos.getErrorIndex(); + if (index < 0) + index = pos.getIndex(); + throw new ParseException("invalid Date syntax", index); + } + return result; + } + + public abstract Date parse (String source, ParsePosition pos); + + public Object parseObject (String source, ParsePosition pos) + { + return parse(source, pos); + } + + public void setCalendar (Calendar calendar) + { + this.calendar = calendar; + } + + public void setLenient (boolean lenient) + { + calendar.setLenient(lenient); + } + + public void setNumberFormat (NumberFormat numberFormat) + { + this.numberFormat = numberFormat; + } + + public void setTimeZone (TimeZone timeZone) + { + calendar.setTimeZone(timeZone); + } +} diff --git a/libjava/java/text/DateFormatSymbols.java b/libjava/java/text/DateFormatSymbols.java new file mode 100644 index 0000000..d21cdea --- /dev/null +++ b/libjava/java/text/DateFormatSymbols.java @@ -0,0 +1,265 @@ +/* Copyright (C) 1998, 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * @author Per Bothner <bothner@cygnus.com> + * @date October 24, 1998. + */ + +/* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3. + * Status: Believed complete and correct. + */ + +public class DateFormatSymbols extends Object + implements java.io.Serializable, Cloneable +{ + private String[] ampms; + private String[] eras; + private String localPatternChars; + private String[] months; + private String[] shortMonths; + private String[] shortWeekdays; + private String[] weekdays; + private String[][] zoneStrings; + + private static final String[] ampmsDefault = {"AM", "PM" }; + private static final String[] erasDefault = {"BC", "AD" }; + // localPatternCharsDefault is used by SimpleDateFormat. + protected static final String localPatternCharsDefault + = "GyMdkHmsSEDFwWahKz"; + private static final String[] monthsDefault = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", "" + }; + private static final String[] shortMonthsDefault = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" + }; + private static final String[] shortWeekdaysDefault = { + "", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + private static final String[] weekdaysDefault = { + "", "Sunday", "Monday", "Tuesday", + "Wednesday", "Thursday", "Friday", "Saturday" + }; + + private static String[][] zoneStringsDefault = { + { "PST", "Pacific Standard Time", "PST", + /**/ "Pacific Daylight Time", "PDT", "San Francisco" }, + { "MST", "Mountain Standard Time", "MST", + /**/ "Mountain Daylight Time", "MDT", "Denver" }, + { "PNT", "Mountain Standard Time", "MST", + /**/ "Mountain Standard Time", "MST", "Phoenix" }, + { "CST", "Central Standard Time", "CST", + /**/ "Central Daylight Time", "CDT", "Chicago" }, + { "EST", "Eastern Standard Time", "EST", + /**/ "Eastern Daylight Time", "EDT", "Boston" }, + { "IET", "Eastern Standard Time", "EST", + /**/ "Eastern Standard Time", "EST", "Indianapolis" }, + { "PRT", "Atlantic Standard Time", "AST", + /**/ "Atlantic Daylight Time", "ADT", "Halifax" }, + { "HST", "Hawaii Standard Time", "HST", + /**/ "Hawaii Daylight Time", "HDT", "Honolulu" }, + { "AST", "Alaska Standard Time", "AST", + /**/ "Alaska Daylight Time", "ADT", "Anchorage" } + }; + + private final Object safeGetResource (ResourceBundle res, + String key, Object def) + { + if (res != null) + { + try + { + return res.getObject(key); + } + catch (MissingResourceException x) + { + } + } + return def; + } + + public DateFormatSymbols (Locale locale) + { + ResourceBundle res; + try + { + res = ResourceBundle.getBundle("gnu.gcj.text.LocaleData", locale); + } + catch (MissingResourceException x) + { + res = null; + } + ampms = (String[]) safeGetResource (res, "ampm", ampmsDefault); + eras = (String[]) safeGetResource (res, "eras", erasDefault); + localPatternChars = (String) safeGetResource (res, "datePatternChars", + localPatternCharsDefault); + months = (String[]) safeGetResource (res, "months", monthsDefault); + shortMonths = (String[]) safeGetResource (res, "shortMonths", + shortMonthsDefault); + shortWeekdays = (String[]) safeGetResource (res, "shortWeekdays", + shortWeekdaysDefault); + weekdays = (String[]) safeGetResource (res, "weekdays", weekdaysDefault); + zoneStrings = (String[][]) safeGetResource (res, "zoneStrings", + zoneStringsDefault); + } + + public DateFormatSymbols () + { + this (Locale.getDefault()); + } + + public String[] getAmPmStrings() + { + return ampms; + } + + public String[] getEras() + { + return eras; + } + + + public String getLocalPatternChars() + { + return localPatternChars; + } + + public String[] getMonths () + { + return months; + } + + public String[] getShortMonths () + { + return shortMonths; + } + + public String[] getShortWeekdays () + { + return shortWeekdays; + } + + public String[] getWeekdays () + { + return weekdays; + } + + public String[] [] getZoneStrings () + { + return zoneStrings; + } + + public void setAmPmStrings (String[] value) + { + ampms = value; + } + + public void setEras (String[] value) + { + eras = value; + } + + public void setLocalPatternChars (String value) + { + localPatternChars = value; + } + + public void setMonths (String[] value) + { + months = value; + } + + public void setShortMonths (String[] value) + { + shortMonths = value; + } + + public void setShortWeekdays (String[] value) + { + shortWeekdays = value; + } + + public void setWeekdays (String[] value) + { + weekdays = value; + } + + public void setZoneStrings (String[][] value) + { + zoneStrings = value; + } + + /* Does a "deep" equality test - recurses into arrays. */ + protected static boolean equals (Object x, Object y) + { + if (x == y) + return true; + if (x == null || y == null) + return false; + if (! (x instanceof Object[]) || ! (y instanceof Object[])) + return x.equals(y); + Object[] xa = (Object[]) x; + Object[] ya = (Object[]) y; + if (xa.length != ya.length) + return false; + for (int i = xa.length; --i >= 0; ) + { + if (! equals(xa[i], ya[i])) + return false; + } + return true; + } + + private static int hashCode (Object x) + { + if (x == null) + return 0; + if (! (x instanceof Object[])) + return x.hashCode(); + Object[] xa = (Object[]) x; + int hash = 0; + for (int i = 0; i < xa.length; i++) + hash = 37 * hashCode(xa[i]); + return hash; + } + + public boolean equals (Object obj) + { + if (obj == null || ! (obj instanceof DateFormatSymbols)) + return false; + DateFormatSymbols other = (DateFormatSymbols) obj; + return (equals(ampms, other.ampms) + && equals(eras, other.eras) + && equals(localPatternChars, other.localPatternChars) + && equals(months, other.months) + && equals(shortMonths, other.shortMonths) + && equals(shortWeekdays, other.shortWeekdays) + && equals(weekdays, other.weekdays) + && equals(zoneStrings, other.zoneStrings)); + } + + public int hashCode () + { + return (hashCode(ampms) + ^ hashCode(eras) + ^ hashCode(localPatternChars) + ^ hashCode(months) + ^ hashCode(shortMonths) + ^ hashCode(shortWeekdays) + ^ hashCode(weekdays) + ^ hashCode(zoneStrings)); + } +} diff --git a/libjava/java/text/DecimalFormat.java b/libjava/java/text/DecimalFormat.java new file mode 100644 index 0000000..9ea9d92 --- /dev/null +++ b/libjava/java/text/DecimalFormat.java @@ -0,0 +1,983 @@ +// DecimalFormat.java - Localized number formatting. + +/* Copyright (C) 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * @author Tom Tromey <tromey@cygnus.com> + * @date March 4, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 from http://www.javasoft.com. + * Status: Believed complete and correct to 1.2, except serialization. + * Note however that the docs are very unclear about how format parsing + * should work. No doubt there are problems here. + */ + +public class DecimalFormat extends NumberFormat +{ + // This is a helper for applyPatternWithSymbols. It reads a prefix + // or a suffix. It can cause some side-effects. + private final int scanFix (String pattern, int index, StringBuffer buf, + String patChars, DecimalFormatSymbols syms, + boolean is_suffix) + throws ParseException + { + int len = pattern.length(); + buf.setLength(0); + boolean multiplierSet = false; + while (index < len) + { + char c = pattern.charAt(index); + if (c == '\'' && index + 1 < len + && pattern.charAt(index + 1) == '\'') + { + buf.append(c); + ++index; + } + else if (c == '\'' && index + 2 < len + && pattern.charAt(index + 2) == '\'') + { + buf.append(pattern.charAt(index + 1)); + index += 2; + } + else if (c == '\u00a4') + { + if (index + 1 < len && pattern.charAt(index + 1) == '\u00a4') + { + buf.append(syms.getInternationalCurrencySymbol()); + ++index; + } + else + buf.append(syms.getCurrencySymbol()); + } + else if (is_suffix && c == syms.getPercent()) + { + if (multiplierSet) + throw new ParseException ("multiplier already set", index); + multiplierSet = true; + multiplier = 100; + buf.append(c); + } + else if (is_suffix && c == syms.getPerMill()) + { + if (multiplierSet) + throw new ParseException ("multiplier already set", index); + multiplierSet = true; + multiplier = 1000; + buf.append(c); + } + else if (patChars.indexOf(c) != -1) + { + // This is a pattern character. + break; + } + else + buf.append(c); + ++index; + } + + return index; + } + + // A helper which reads a number format. + private final int scanFormat (String pattern, int index, + String patChars, DecimalFormatSymbols syms, + boolean is_positive) + throws ParseException + { + int max = pattern.length(); + + int countSinceGroup = 0; + int zeroCount = 0; + boolean saw_group = false; + + // + // Scan integer part. + // + while (index < max) + { + char c = pattern.charAt(index); + + if (c == syms.getDigit()) + { + if (zeroCount > 0) + throw new ParseException ("digit mark following zero", index); + ++countSinceGroup; + } + else if (c == syms.getZeroDigit()) + { + ++zeroCount; + ++countSinceGroup; + } + else if (c == syms.getGroupingSeparator()) + { + countSinceGroup = 0; + saw_group = true; + } + else + break; + + ++index; + } + + // We can only side-effect when parsing the positive format. + if (is_positive) + { + groupingUsed = saw_group; + groupingSize = (byte) countSinceGroup; + minimumIntegerDigits = zeroCount; + } + + // Early termination. + if (index == max || pattern.charAt(index) == syms.getGroupingSeparator()) + { + if (is_positive) + decimalSeparatorAlwaysShown = false; + return index; + } + + if (pattern.charAt(index) == syms.getDecimalSeparator()) + { + ++index; + + // + // Scan fractional part. + // + int hashCount = 0; + zeroCount = 0; + while (index < max) + { + char c = pattern.charAt(index); + if (c == syms.getZeroDigit()) + { + if (hashCount > 0) + throw new ParseException ("zero mark following digit", + index); + ++zeroCount; + } + else if (c == syms.getDigit()) + { + ++hashCount; + } + else if (c != syms.getExponential() + && c != syms.getPatternSeparator() + && patChars.indexOf(c) != -1) + throw new ParseException ("unexpected special character", + index); + else + break; + + ++index; + } + + if (is_positive) + { + maximumFractionDigits = hashCount + zeroCount; + minimumFractionDigits = zeroCount; + } + + if (index == max) + return index; + } + + if (pattern.charAt(index) == syms.getExponential()) + { + // + // Scan exponential format. + // + zeroCount = 0; + ++index; + while (index < max) + { + char c = pattern.charAt(index); + if (c == syms.getZeroDigit()) + ++zeroCount; + else if (c == syms.getDigit()) + { + if (zeroCount > 0) + throw new + ParseException ("digit mark following zero in exponent", + index); + } + else if (patChars.indexOf(c) != -1) + throw new ParseException ("unexpected special character", + index); + else + break; + + ++index; + } + + if (is_positive) + { + useExponentialNotation = true; + minExponentDigits = (byte) zeroCount; + } + } + + return index; + } + + // This helper function creates a string consisting of all the + // characters which can appear in a pattern and must be quoted. + private final String patternChars (DecimalFormatSymbols syms) + { + StringBuffer buf = new StringBuffer (); + buf.append(syms.getDecimalSeparator()); + buf.append(syms.getDigit()); + buf.append(syms.getExponential()); + buf.append(syms.getGroupingSeparator()); + // Adding this one causes pattern application to fail. + // Of course, omitting is causes toPattern to fail. + // ... but we already have bugs there. FIXME. + // buf.append(syms.getMinusSign()); + buf.append(syms.getPatternSeparator()); + buf.append(syms.getPercent()); + buf.append(syms.getPerMill()); + buf.append(syms.getZeroDigit()); + buf.append('\u00a4'); + return buf.toString(); + } + + private final void applyPatternWithSymbols (String pattern, + DecimalFormatSymbols syms) + throws ParseException + { + // Initialize to the state the parser expects. + negativePrefix = ""; + negativeSuffix = ""; + positivePrefix = ""; + positiveSuffix = ""; + decimalSeparatorAlwaysShown = false; + groupingSize = 0; + minExponentDigits = 0; + multiplier = 1; + useExponentialNotation = false; + groupingUsed = false; + maximumFractionDigits = 0; + maximumIntegerDigits = 309; + minimumFractionDigits = 0; + minimumIntegerDigits = 1; + + StringBuffer buf = new StringBuffer (); + String patChars = patternChars (syms); + + int max = pattern.length(); + int index = scanFix (pattern, 0, buf, patChars, syms, false); + positivePrefix = buf.toString(); + + index = scanFormat (pattern, index, patChars, syms, true); + + index = scanFix (pattern, index, buf, patChars, syms, true); + positiveSuffix = buf.toString(); + + if (index == pattern.length()) + { + // No negative info. + negativePrefix = null; + negativeSuffix = null; + } + else + { + if (pattern.charAt(index) != syms.getPatternSeparator()) + throw new ParseException ("separator character expected", index); + + index = scanFix (pattern, index + 1, buf, patChars, syms, false); + negativePrefix = buf.toString(); + + // We parse the negative format for errors but we don't let + // it side-effect this object. + index = scanFormat (pattern, index, patChars, syms, false); + + index = scanFix (pattern, index, buf, patChars, syms, true); + negativeSuffix = buf.toString(); + + if (index != pattern.length()) + throw new ParseException ("end of pattern expected", index); + } + } + + public void applyLocalizedPattern (String pattern) throws ParseException + { + applyPatternWithSymbols (pattern, symbols); + } + + public void applyPattern (String pattern) throws ParseException + { + applyPatternWithSymbols (pattern, nonLocalizedSymbols); + } + + public Object clone () + { + return new DecimalFormat (this); + } + + private DecimalFormat (DecimalFormat dup) + { + decimalSeparatorAlwaysShown = dup.decimalSeparatorAlwaysShown; + groupingSize = dup.groupingSize; + minExponentDigits = dup.minExponentDigits; + multiplier = dup.multiplier; + negativePrefix = dup.negativePrefix; + negativeSuffix = dup.negativeSuffix; + positivePrefix = dup.positivePrefix; + positiveSuffix = dup.positiveSuffix; + symbols = (DecimalFormatSymbols) dup.symbols.clone(); + useExponentialNotation = dup.useExponentialNotation; + } + + public DecimalFormat () + { + this ("#,##0.###"); + } + + public DecimalFormat (String pattern) + { + this (pattern, new DecimalFormatSymbols ()); + } + + public DecimalFormat (String pattern, DecimalFormatSymbols symbols) + { + this.symbols = symbols; + // The docs imply that the constructor turns a ParseException + // into an IllegalArgumentException. + try + { + applyPattern (pattern); + } + catch (ParseException x) + { + throw new IllegalArgumentException (x.getMessage()); + } + } + + private final boolean equals (String s1, String s2) + { + if (s1 == null || s2 == null) + return s1 == s2; + return s1.equals(s2); + } + + public boolean equals (Object obj) + { + if (! (obj instanceof DecimalFormat)) + return false; + DecimalFormat dup = (DecimalFormat) obj; + return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown + && groupingSize == dup.groupingSize + && minExponentDigits == dup.minExponentDigits + && multiplier == dup.multiplier + && equals(negativePrefix, dup.negativePrefix) + && equals(negativeSuffix, dup.negativeSuffix) + && equals(positivePrefix, dup.positivePrefix) + && equals(positiveSuffix, dup.positiveSuffix) + && symbols.equals(dup.symbols) + && useExponentialNotation == dup.useExponentialNotation); + } + + public StringBuffer format (double number, StringBuffer dest, + FieldPosition fieldPos) + { + // A very special case. + if (Double.isNaN(number)) + { + dest.append(symbols.getNaN()); + if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD) + { + int index = dest.length(); + fieldPos.setBeginIndex(index - symbols.getNaN().length()); + fieldPos.setEndIndex(index); + } + return dest; + } + + boolean is_neg = number < 0; + if (is_neg) + { + if (negativePrefix != null) + dest.append(negativePrefix); + else + { + dest.append(symbols.getMinusSign()); + dest.append(positivePrefix); + } + number = - number; + } + else + dest.append(positivePrefix); + + int integerBeginIndex = dest.length(); + int integerEndIndex = 0; + if (Double.isInfinite (number)) + { + dest.append(symbols.getInfinity()); + integerEndIndex = dest.length(); + } + else + { + number *= multiplier; + + // Compute exponent. + long exponent = 0; + double baseNumber; + if (useExponentialNotation) + { + exponent = (long) (Math.log(number) / Math.log(10)); + if (minimumIntegerDigits > 0) + exponent -= minimumIntegerDigits - 1; + baseNumber = (long) (number / Math.pow(10.0, exponent)); + } + else + baseNumber = number; + + // Round to the correct number of digits. + baseNumber += 5 * Math.pow(10.0, - maximumFractionDigits - 1); + + int index = dest.length(); + double intPart = Math.floor(baseNumber); + int count = 0; + while (count < maximumIntegerDigits + && (intPart > 0 || count < minimumIntegerDigits)) + { + long dig = (long) (intPart % 10); + intPart = Math.floor(intPart / 10); + + // Append group separator if required. + if (groupingUsed && count > 0 && count % groupingSize == 0) + dest.insert(index, symbols.getGroupingSeparator()); + + dest.insert(index, (char) (symbols.getZeroDigit() + dig)); + + ++count; + } + + integerEndIndex = dest.length(); + + int decimal_index = integerEndIndex; + int consecutive_zeros = 0; + int total_digits = 0; + + // Strip integer part from NUMBER. + double fracPart = baseNumber - Math.floor(baseNumber); + for (count = 0; + count < maximumFractionDigits + && (fracPart != 0 || count < minimumFractionDigits); + ++count) + { + ++total_digits; + fracPart *= 10; + long dig = (long) fracPart; + if (dig == 0) + ++consecutive_zeros; + else + consecutive_zeros = 0; + dest.append((char) (symbols.getZeroDigit() + dig)); + + // Strip integer part from FRACPART. + fracPart = fracPart - Math.floor (fracPart); + } + + // Strip extraneous trailing `0's. We can't always detect + // these in the loop. + int extra_zeros = Math.min (consecutive_zeros, + total_digits - minimumFractionDigits); + if (extra_zeros > 0) + { + dest.setLength(dest.length() - extra_zeros); + total_digits -= extra_zeros; + } + + // If required, add the decimal symbol. + if (decimalSeparatorAlwaysShown + || total_digits > 0) + { + dest.insert(decimal_index, symbols.getDecimalSeparator()); + if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD) + { + fieldPos.setBeginIndex(decimal_index + 1); + fieldPos.setEndIndex(dest.length()); + } + } + + // Finally, print the exponent. + if (useExponentialNotation) + { + dest.append(symbols.getExponential()); + dest.append(exponent < 0 ? '-' : '+'); + index = dest.length(); + for (count = 0; + exponent > 0 || count < minExponentDigits; + ++count) + { + long dig = exponent % 10; + exponent /= 10; + dest.insert(index, (char) (symbols.getZeroDigit() + dig)); + } + } + } + + if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD) + { + fieldPos.setBeginIndex(integerBeginIndex); + fieldPos.setEndIndex(integerEndIndex); + } + + dest.append((is_neg && negativeSuffix != null) + ? negativeSuffix + : positiveSuffix); + return dest; + } + + public StringBuffer format (long number, StringBuffer dest, + FieldPosition fieldPos) + { + // If using exponential notation, we just format as a double. + if (useExponentialNotation) + return format ((double) number, dest, fieldPos); + + boolean is_neg = number < 0; + if (is_neg) + { + if (negativePrefix != null) + dest.append(negativePrefix); + else + { + dest.append(symbols.getMinusSign()); + dest.append(positivePrefix); + } + number = - number; + } + else + dest.append(positivePrefix); + + int integerBeginIndex = dest.length(); + int index = dest.length(); + int count = 0; + while (count < maximumIntegerDigits + && (number > 0 || count < minimumIntegerDigits)) + { + long dig = number % 10; + number /= 10; + // NUMBER and DIG will be less than 0 if the original number + // was the most negative long. + if (dig < 0) + { + dig = - dig; + number = - number; + } + + // Append group separator if required. + if (groupingUsed && count > 0 && count % groupingSize == 0) + dest.insert(index, symbols.getGroupingSeparator()); + + dest.insert(index, (char) (symbols.getZeroDigit() + dig)); + + ++count; + } + + if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD) + { + fieldPos.setBeginIndex(integerBeginIndex); + fieldPos.setEndIndex(dest.length()); + } + + if (decimalSeparatorAlwaysShown || minimumFractionDigits > 0) + { + dest.append(symbols.getDecimalSeparator()); + if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD) + { + fieldPos.setBeginIndex(dest.length()); + fieldPos.setEndIndex(dest.length() + minimumFractionDigits); + } + } + + for (count = 0; count < minimumFractionDigits; ++count) + dest.append(symbols.getZeroDigit()); + + dest.append((is_neg && negativeSuffix != null) + ? negativeSuffix + : positiveSuffix); + return dest; + } + + public DecimalFormatSymbols getDecimalFormatSymbols () + { + return symbols; + } + + public int getGroupingSize () + { + return groupingSize; + } + + public int getMultiplier () + { + return multiplier; + } + + public String getNegativePrefix () + { + return negativePrefix; + } + + public String getNegativeSuffix () + { + return negativeSuffix; + } + + public String getPositivePrefix () + { + return positivePrefix; + } + + public String getPositiveSuffix () + { + return positiveSuffix; + } + + public int hashCode () + { + int hash = (negativeSuffix.hashCode() ^ negativePrefix.hashCode() + ^positivePrefix.hashCode() ^ positiveSuffix.hashCode()); + // FIXME. + return hash; + } + + public boolean isDecimalSeparatorAlwaysShown () + { + return decimalSeparatorAlwaysShown; + } + + public Number parse (String str, ParsePosition pos) + { + // Our strategy is simple: copy the text into a buffer, + // translating or omitting locale-specific information. Then + // let Double or Long convert the number for us. + + boolean is_neg = false; + int index = pos.getIndex(); + StringBuffer buf = new StringBuffer (); + + // We have to check both prefixes, because one might be empty. + // We want to pick the longest prefix that matches. + boolean got_pos = str.startsWith(positivePrefix, index); + String np = (negativePrefix != null + ? negativePrefix + : positivePrefix + symbols.getMinusSign()); + boolean got_neg = str.startsWith(np, index); + + if (got_pos && got_neg) + { + // By checking this way, we preserve ambiguity in the case + // where the negative format differs only in suffix. We + // check this again later. + if (np.length() > positivePrefix.length()) + { + is_neg = true; + index += np.length(); + } + else + index += positivePrefix.length(); + } + else if (got_neg) + { + is_neg = true; + index += np.length(); + } + else if (got_pos) + index += positivePrefix.length(); + else + { + pos.setErrorIndex (index); + return null; + } + + // FIXME: handle Inf and NaN. + + // FIXME: do we have to respect minimum/maxmimum digit stuff? + // What about leading zeros? What about multiplier? + + int start_index = index; + int max = str.length(); + char zero = symbols.getZeroDigit(); + int last_group = -1; + boolean int_part = true; + boolean exp_part = false; + for (; index < max; ++index) + { + char c = str.charAt(index); + + // FIXME: what about grouping size? + if (groupingUsed && c == symbols.getGroupingSeparator()) + { + if (last_group != -1 + && (index - last_group) % groupingSize != 0) + { + pos.setErrorIndex(index); + return null; + } + last_group = index; + } + else if (c >= zero && c <= zero + 9) + { + buf.append((char) (c - zero + '0')); + exp_part = false; + } + else if (parseIntegerOnly) + break; + else if (c == symbols.getDecimalSeparator()) + { + if (last_group != -1 + && (index - last_group) % groupingSize != 0) + { + pos.setErrorIndex(index); + return null; + } + buf.append('.'); + int_part = false; + } + else if (c == symbols.getExponential()) + { + buf.append('E'); + int_part = false; + exp_part = true; + } + else if (exp_part + && (c == '+' || c == '-' || c == symbols.getMinusSign())) + { + // For exponential notation. + buf.append(c); + } + else + break; + } + + if (index == start_index) + { + // Didn't see any digits. + pos.setErrorIndex(index); + return null; + } + + // Check the suffix. We must do this before converting the + // buffer to a number to handle the case of a number which is + // the most negative Long. + boolean got_pos_suf = str.startsWith(positiveSuffix, index); + String ns = (negativePrefix == null ? positiveSuffix : negativeSuffix); + boolean got_neg_suf = str.startsWith(ns, index); + if (is_neg) + { + if (! got_neg_suf) + { + pos.setErrorIndex(index); + return null; + } + } + else if (got_pos && got_neg && got_neg_suf) + { + is_neg = true; + } + else if (got_pos != got_pos_suf && got_neg != got_neg_suf) + { + pos.setErrorIndex(index); + return null; + } + + String suffix = is_neg ? ns : positiveSuffix; + if (is_neg) + buf.insert(0, '-'); + + String t = buf.toString(); + Number result = null; + try + { + result = new Long (t); + } + catch (NumberFormatException x1) + { + try + { + result = new Double (t); + } + catch (NumberFormatException x2) + { + } + } + if (result == null) + { + pos.setErrorIndex(index); + return null; + } + + pos.setIndex(index + suffix.length()); + + return result; + } + + public void setDecimalFormatSymbols (DecimalFormatSymbols newSymbols) + { + symbols = newSymbols; + } + + public void setDecimalSeparatorAlwaysShown (boolean newValue) + { + decimalSeparatorAlwaysShown = newValue; + } + + public void setGroupingSize (int groupSize) + { + groupingSize = (byte) groupSize; + } + + public void setMaximumFractionDigits (int newValue) + { + maximumFractionDigits = Math.min(newValue, 340); + } + + public void setMaximumIntegerDigits (int newValue) + { + maximumIntegerDigits = Math.min(newValue, 309); + } + + public void setMinimumFractionDigits (int newValue) + { + minimumFractionDigits = Math.min(newValue, 340); + } + + public void setMinimumIntegerDigits (int newValue) + { + minimumIntegerDigits = Math.min(newValue, 309); + } + + public void setMultiplier (int newValue) + { + multiplier = newValue; + } + + public void setNegativePrefix (String newValue) + { + negativePrefix = newValue; + } + + public void setNegativeSuffix (String newValue) + { + negativeSuffix = newValue; + } + + public void setPositivePrefix (String newValue) + { + positivePrefix = newValue; + } + + public void setPositiveSuffix (String newValue) + { + positiveSuffix = newValue; + } + + private final void quoteFix (StringBuffer buf, String text, String patChars) + { + int len = text.length(); + for (int index = 0; index < len; ++index) + { + char c = text.charAt(index); + if (patChars.indexOf(c) != -1) + { + buf.append('\''); + buf.append(c); + buf.append('\''); + } + else + buf.append(c); + } + } + + private final String computePattern (DecimalFormatSymbols syms) + { + StringBuffer mainPattern = new StringBuffer (); + // We have to at least emit a zero for the minimum number of + // digits. Past that we need hash marks up to the grouping + // separator (and one beyond). + int total_digits = Math.max(minimumIntegerDigits, + groupingUsed ? groupingSize + 1: 0); + for (int i = 0; i < total_digits - minimumIntegerDigits; ++i) + mainPattern.append(syms.getDigit()); + for (int i = total_digits - minimumIntegerDigits; i < total_digits; ++i) + mainPattern.append(syms.getZeroDigit()); + // Inserting the gropuing operator afterwards is easier. + if (groupingUsed) + mainPattern.insert(mainPattern.length() - groupingSize, + syms.getGroupingSeparator()); + // See if we need decimal info. + if (minimumFractionDigits > 0 || maximumFractionDigits > 0 + || decimalSeparatorAlwaysShown) + mainPattern.append(syms.getDecimalSeparator()); + for (int i = 0; i < minimumFractionDigits; ++i) + mainPattern.append(syms.getZeroDigit()); + for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i) + mainPattern.append(syms.getDigit()); + if (useExponentialNotation) + { + mainPattern.append(syms.getExponential()); + for (int i = 0; i < minExponentDigits; ++i) + mainPattern.append(syms.getZeroDigit()); + if (minExponentDigits == 0) + mainPattern.append(syms.getDigit()); + } + + String main = mainPattern.toString(); + String patChars = patternChars (syms); + mainPattern.setLength(0); + + quoteFix (mainPattern, positivePrefix, patChars); + mainPattern.append(main); + quoteFix (mainPattern, positiveSuffix, patChars); + + if (negativePrefix != null) + { + quoteFix (mainPattern, negativePrefix, patChars); + mainPattern.append(main); + quoteFix (mainPattern, negativeSuffix, patChars); + } + + return mainPattern.toString(); + } + + public String toLocalizedPattern () + { + return computePattern (symbols); + } + + public String toPattern () + { + return computePattern (nonLocalizedSymbols); + } + + // These names are fixed by the serialization spec. + private boolean decimalSeparatorAlwaysShown; + private byte groupingSize; + private byte minExponentDigits; + private int multiplier; + private String negativePrefix; + private String negativeSuffix; + private String positivePrefix; + private String positiveSuffix; + private DecimalFormatSymbols symbols; + private boolean useExponentialNotation; + + // The locale-independent pattern symbols happen to be the same as + // the US symbols. + private static final DecimalFormatSymbols nonLocalizedSymbols + = new DecimalFormatSymbols (Locale.US); +} diff --git a/libjava/java/text/DecimalFormatSymbols.java b/libjava/java/text/DecimalFormatSymbols.java new file mode 100644 index 0000000..783cb6f --- /dev/null +++ b/libjava/java/text/DecimalFormatSymbols.java @@ -0,0 +1,293 @@ +// DecimalFormatSymbols.java - Symbols used to format numbers. + +/* Copyright (C) 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +import java.io.Serializable; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * @author Tom Tromey <tromey@cygnus.com> + * @date February 24, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 from http://www.javasoft.com. + * Status: Believed complete and correct to 1.2, except serialization. + */ + +public final class DecimalFormatSymbols implements Cloneable, Serializable +{ + public Object clone () + { + return new DecimalFormatSymbols (this); + } + + private DecimalFormatSymbols (DecimalFormatSymbols orig) + { + this.currencySymbol = orig.currencySymbol; + this.decimalSeparator = orig.decimalSeparator; + this.digit = orig.digit; + this.exponential = orig.exponential; + this.groupingSeparator = orig.groupingSeparator; + this.infinity = orig.infinity; + this.intlCurrencySymbol = orig.intlCurrencySymbol; + this.minusSign = orig.minusSign; + this.NaN = orig.NaN; + this.patternSeparator = orig.patternSeparator; + this.percent = orig.percent; + this.perMill = orig.perMill; + this.zeroDigit = orig.zeroDigit; + } + + public DecimalFormatSymbols () + { + this (Locale.getDefault()); + } + + private final String safeGetString (ResourceBundle bundle, + String name, String def) + { + if (bundle != null) + { + try + { + return bundle.getString(name); + } + catch (MissingResourceException x) + { + } + } + return def; + } + + public final char safeGetChar (ResourceBundle bundle, + String name, char def) + { + String r = null; + if (bundle != null) + { + try + { + r = bundle.getString(name); + } + catch (MissingResourceException x) + { + } + } + if (r == null || r.length() < 1) + return def; + return r.charAt(0); + } + + public DecimalFormatSymbols (Locale loc) + { + ResourceBundle res; + try + { + res = ResourceBundle.getBundle("gnu.gcj.text.LocaleData", loc); + } + catch (MissingResourceException x) + { + res = null; + } + currencySymbol = safeGetString (res, "currencySymbol", "$"); + decimalSeparator = safeGetChar (res, "decimalSeparator", '.'); + digit = safeGetChar (res, "digit", '#'); + exponential = safeGetChar (res, "exponential", 'E'); + groupingSeparator = safeGetChar (res, "groupingSeparator", ','); + infinity = safeGetString (res, "infinity", "\u221e"); + // FIXME: default? + intlCurrencySymbol = safeGetString (res, "intlCurrencySymbol", "$"); + minusSign = safeGetChar (res, "minusSign", '-'); + NaN = safeGetString (res, "NaN", "\ufffd"); + patternSeparator = safeGetChar (res, "patternSeparator", ';'); + percent = safeGetChar (res, "percent", '%'); + perMill = safeGetChar (res, "perMill", '\u2030'); + zeroDigit = safeGetChar (res, "zeroDigit", '0'); + } + + public boolean equals (Object obj) + { + if (! (obj instanceof DecimalFormatSymbols)) + return false; + DecimalFormatSymbols dfs = (DecimalFormatSymbols) obj; + return (currencySymbol.equals(dfs.currencySymbol) + && decimalSeparator == dfs.decimalSeparator + && digit == dfs.digit + && exponential == dfs.exponential + && groupingSeparator == dfs.groupingSeparator + && infinity.equals(dfs.infinity) + && intlCurrencySymbol.equals(dfs.intlCurrencySymbol) + && minusSign == dfs.minusSign + && NaN.equals(dfs.NaN) + && patternSeparator == dfs.patternSeparator + && percent == dfs.percent + && perMill == dfs.perMill + && zeroDigit == dfs.zeroDigit); + } + + public String getCurrencySymbol () + { + return currencySymbol; + } + + public char getDecimalSeparator () + { + return decimalSeparator; + } + + public char getDigit () + { + return digit; + } + + // This is our own extension. + char getExponential () + { + return exponential; + } + + public char getGroupingSeparator () + { + return groupingSeparator; + } + + public String getInfinity () + { + return infinity; + } + + public String getInternationalCurrencySymbol () + { + return intlCurrencySymbol; + } + + public char getMinusSign () + { + return minusSign; + } + + public String getNaN () + { + return NaN; + } + + public char getPatternSeparator () + { + return patternSeparator; + } + + public char getPercent () + { + return percent; + } + + public char getPerMill () + { + return perMill; + } + + public char getZeroDigit () + { + return zeroDigit; + } + + public int hashCode () + { + // Compute based on zero digit, grouping separator, and decimal + // separator -- JCL book. This probably isn't a very good hash + // code. + return zeroDigit << 16 + groupingSeparator << 8 + decimalSeparator; + } + + public void setCurrenySymbol (String currency) + { + currencySymbol = currency; + } + + public void setDecimalSeparator (char decimalSep) + { + decimalSeparator = decimalSep; + } + + public void setDigit (char digit) + { + this.digit = digit; + } + + // This is our own extension. + void setExponential (char exp) + { + exponential = exp; + } + + public void setGroupingSeparator (char groupSep) + { + groupingSeparator = groupSep; + } + + public void setInfinity (String infinity) + { + this.infinity = infinity; + } + + public void setInternationalCurrencySymbol (String currency) + { + intlCurrencySymbol = currency; + } + + public void setMinusSign (char minusSign) + { + this.minusSign = minusSign; + } + + public void setNaN (String nan) + { + NaN = nan; + } + + public void setPatternSeparator (char patternSep) + { + patternSeparator = patternSep; + } + + public void setPercent (char percent) + { + this.percent = percent; + } + + public void setPerMill (char perMill) + { + this.perMill = perMill; + } + + public void setZeroDigit (char zeroDigit) + { + this.zeroDigit = zeroDigit; + } + + // The names of the instance variables are fixed by the + // serialization spec. + private String currencySymbol; + private char decimalSeparator; + private char digit; + private char exponential; + private char groupingSeparator; + private String infinity; + private String intlCurrencySymbol; + private char minusSign; + private String NaN; + private char patternSeparator; + private char percent; + private char perMill; + private char zeroDigit; +} diff --git a/libjava/java/text/FieldPosition.java b/libjava/java/text/FieldPosition.java new file mode 100644 index 0000000..2f8c093 --- /dev/null +++ b/libjava/java/text/FieldPosition.java @@ -0,0 +1,65 @@ +/* Copyright (C) 1998, 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +/** + * @author Per Bothner <bothner@cygnus.com> + * @date October 25, 1998. + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: Believed complete and correct. + * Includes JDK 1.2 methods. + */ + +public class FieldPosition +{ + int field; + int beginIndex; + int endIndex; + + public FieldPosition (int field) + { + this.field = field; + } + + public int getField () + { + return field; + } + + public int getBeginIndex () + { + return beginIndex; + } + + public int getEndIndex () + { + return endIndex; + } + + public void setBeginIndex (int index) + { + beginIndex = index; + } + + public void setEndIndex (int index) + { + endIndex = index; + } + + public boolean equals (Object obj) + { + if (! (obj instanceof FieldPosition)) + return false; + FieldPosition other = (FieldPosition) obj; + return (field == other.field + && beginIndex == other.beginIndex && endIndex == other.endIndex); + } +} diff --git a/libjava/java/text/Format.java b/libjava/java/text/Format.java new file mode 100644 index 0000000..d2c918b --- /dev/null +++ b/libjava/java/text/Format.java @@ -0,0 +1,51 @@ +/* Copyright (C) 1998, 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +/** + * @author Per Bothner <bothner@cygnus.com> + * @date October 25, 1998. + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: Believed complete and correct. + */ + +public abstract class Format implements java.io.Serializable, Cloneable +{ + public Format () + { + } + + public abstract StringBuffer format (Object obj, + StringBuffer sbuf, FieldPosition pos); + + public final String format (Object obj) + { + StringBuffer sbuf = new StringBuffer(); + format(obj, sbuf, new FieldPosition(0)); + return sbuf.toString(); + } + + public abstract Object parseObject (String source, ParsePosition pos); + + public Object parseObject (String source) throws ParseException + { + ParsePosition pos = new ParsePosition(0); + Object result = parseObject (source, pos); + if (result == null) + { + int index = pos.getErrorIndex(); + if (index < 0) + index = pos.getIndex(); + throw new ParseException("parseObject failed", index); + } + return result; + } +} diff --git a/libjava/java/text/MessageFormat.java b/libjava/java/text/MessageFormat.java new file mode 100644 index 0000000..8b42235 --- /dev/null +++ b/libjava/java/text/MessageFormat.java @@ -0,0 +1,543 @@ +// MessageFormat.java - Localized message formatting. + +/* Copyright (C) 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +import java.util.Date; +import java.util.Locale; +import java.util.Vector; + +/** + * @author Tom Tromey <tromey@cygnus.com> + * @date March 3, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 from http://www.javasoft.com. + * Status: Believed complete and correct to 1.2, except serialization. + * and parsing. + */ + +final class MessageFormatElement +{ + // Argument number. + int argNumber; + // Formatter to be used. This is the format set by setFormat. + Format setFormat; + // Formatter to be used based on the type. + Format format; + + // Argument will be checked to make sure it is an instance of this + // class. + Class formatClass; + + // Formatter type. + String type; + // Formatter style. + String style; + + // Text to follow this element. + String trailer; + + // FIXME: shouldn't need this. + Class forName (String name) + { + try + { + return Class.forName (name); + } + catch (ClassNotFoundException x) + { + } + return null; + } + + // Recompute the locale-based formatter. + void setLocale (Locale loc) + { + if (type == null) + ; + else if (type.equals("number")) + { + // FIXME: named class literal. + // formatClass = Number.class; + formatClass = forName ("java.lang.Number"); + + if (style == null) + format = NumberFormat.getInstance(loc); + else if (style.equals("currency")) + format = NumberFormat.getCurrencyInstance(loc); + else if (style.equals("percent")) + format = NumberFormat.getPercentInstance(loc); + else if (style.equals("integer")) + { + NumberFormat nf = NumberFormat.getNumberInstance(loc); + nf.setMaximumFractionDigits(0); + nf.setGroupingUsed(false); + format = nf; + } + else + { + format = NumberFormat.getNumberInstance(loc); + DecimalFormat df = (DecimalFormat) format; + try + { + df.applyPattern(style); + } + catch (ParseException x) + { + throw new IllegalArgumentException (x.getMessage()); + } + } + } + else if (type.equals("time") || type.equals("date")) + { + // FIXME: named class literal. + // formatClass = Date.class; + formatClass = forName ("java.util.Date"); + + int val = DateFormat.DEFAULT; + if (style == null) + ; + if (style.equals("short")) + val = DateFormat.SHORT; + else if (style.equals("medium")) + val = DateFormat.MEDIUM; + else if (style.equals("long")) + val = DateFormat.LONG; + else if (style.equals("full")) + val = DateFormat.FULL; + + if (type.equals("time")) + format = DateFormat.getTimeInstance(val, loc); + else + format = DateFormat.getDateInstance(val, loc); + + if (style != null && val == DateFormat.DEFAULT) + { + SimpleDateFormat sdf = (SimpleDateFormat) format; + sdf.applyPattern(style); + } + } + else if (type.equals("choice")) + { + // FIXME: named class literal. + // formatClass = Number.class; + formatClass = forName ("java.lang.Number"); + + if (style == null) + throw new + IllegalArgumentException ("style required for choice format"); + format = new ChoiceFormat (style); + } + } +} + +public class MessageFormat extends Format +{ + // Helper that returns the text up to the next format opener. The + // text is put into BUFFER. Returns index of character after end of + // string. Throws IllegalArgumentException on error. + private static final int scanString (String pat, int index, + StringBuffer buffer) + { + int max = pat.length(); + buffer.setLength(0); + for (; index < max; ++index) + { + char c = pat.charAt(index); + if (c == '\'' && index + 2 < max && pat.charAt(index + 2) == '\'') + { + buffer.append(pat.charAt(index + 1)); + index += 2; + } + else if (c == '\'' && index + 1 < max + && pat.charAt(index + 1) == '\'') + { + buffer.append(c); + ++index; + } + else if (c == '{') + break; + else if (c == '}') + throw new IllegalArgumentException (); + else + buffer.append(c); + } + return index; + } + + // This helper retrieves a single part of a format element. Returns + // the index of the terminating character. + private static final int scanFormatElement (String pat, int index, + StringBuffer buffer, + char term) + { + int max = pat.length(); + buffer.setLength(0); + int brace_depth = 1; + + for (; index < max; ++index) + { + char c = pat.charAt(index); + if (c == '\'' && index + 2 < max && pat.charAt(index + 2) == '\'') + { + buffer.append(c); + buffer.append(pat.charAt(index + 1)); + buffer.append(c); + index += 2; + } + else if (c == '\'' && index + 1 < max + && pat.charAt(index + 1) == '\'') + { + buffer.append(c); + ++index; + } + else if (c == '{') + { + buffer.append(c); + ++brace_depth; + } + else if (c == '}') + { + if (--brace_depth == 0) + break; + buffer.append(c); + } + // Check for TERM after braces, because TERM might be `}'. + else if (c == term) + break; + else + buffer.append(c); + } + return index; + } + + // This is used to parse a format element and whatever non-format + // text might trail it. + private static final int scanFormat (String pat, int index, + StringBuffer buffer, Vector elts, + Locale locale) + { + MessageFormatElement mfe = new MessageFormatElement (); + elts.addElement(mfe); + + int max = pat.length(); + + // Skip the opening `{'. + ++index; + + // Fetch the argument number. + index = scanFormatElement (pat, index, buffer, ','); + try + { + mfe.argNumber = Integer.parseInt(buffer.toString()); + } + catch (NumberFormatException nfx) + { + throw new IllegalArgumentException (); + } + + // Extract the element format. + if (index < max && pat.charAt(index) == ',') + { + index = scanFormatElement (pat, index + 1, buffer, ','); + mfe.type = buffer.toString(); + + // Extract the style. + if (index < max && pat.charAt(index) == ',') + { + index = scanFormatElement (pat, index + 1, buffer, '}'); + mfe.style = buffer.toString (); + } + } + + // Advance past the last terminator. + if (index >= max || pat.charAt(index) != '}') + throw new IllegalArgumentException (); + ++index; + + // Now fetch trailing string. + index = scanString (pat, index, buffer); + mfe.trailer = buffer.toString (); + + mfe.setLocale(locale); + + return index; + } + + public void applyPattern (String newPattern) + { + pattern = newPattern; + + StringBuffer tempBuffer = new StringBuffer (); + + int index = scanString (newPattern, 0, tempBuffer); + leader = tempBuffer.toString(); + + Vector elts = new Vector (); + while (index < newPattern.length()) + index = scanFormat (newPattern, index, tempBuffer, elts, locale); + + elements = new MessageFormatElement[elts.size()]; + elts.copyInto(elements); + } + + public Object clone () + { + MessageFormat c = new MessageFormat (); + c.setLocale(locale); + c.applyPattern(pattern); + return (Object) c; + } + + public boolean equals (Object obj) + { + if (! (obj instanceof MessageFormat)) + return false; + MessageFormat mf = (MessageFormat) obj; + return (pattern.equals(mf.pattern) + && locale.equals(mf.locale)); + } + + public static String format (String pattern, Object arguments[]) + { + MessageFormat mf = new MessageFormat (pattern); + StringBuffer sb = new StringBuffer (); + FieldPosition fp = new FieldPosition (NumberFormat.INTEGER_FIELD); + return mf.format(arguments, sb, fp).toString(); + } + + public final StringBuffer format (Object arguments[], StringBuffer appendBuf, + FieldPosition ignore) + { + appendBuf.append(leader); + + for (int i = 0; i < elements.length; ++i) + { + if (elements[i].argNumber >= arguments.length) + throw new IllegalArgumentException (); + Object thisArg = arguments[elements[i].argNumber]; + + Format formatter = null; + if (elements[i].setFormat != null) + formatter = elements[i].setFormat; + else if (elements[i].format != null) + { + if (elements[i].formatClass != null + && ! elements[i].formatClass.isInstance(thisArg)) + throw new IllegalArgumentException (); + formatter = elements[i].format; + } + else if (thisArg instanceof Number) + formatter = NumberFormat.getInstance(locale); + else if (thisArg instanceof Date) + formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); + else + appendBuf.append(thisArg); + + if (formatter != null) + { + // Special-case ChoiceFormat. + if (formatter instanceof ChoiceFormat) + { + StringBuffer buf = new StringBuffer (); + // FIXME: don't actually know what is correct here. + // Can a sub-format refer to any argument, or just + // the single argument passed to it? Must test + // against JDK. + formatter.format(thisArg, buf, ignore); + MessageFormat mf = new MessageFormat (); + mf.setLocale(locale); + mf.applyPattern(buf.toString()); + formatter = mf; + } + formatter.format(thisArg, appendBuf, ignore); + } + + appendBuf.append(elements[i].trailer); + } + + return appendBuf; + } + + public final StringBuffer format (Object singleArg, StringBuffer appendBuf, + FieldPosition ignore) + { + Object[] args = new Object[1]; + args[0] = singleArg; + return format (args, appendBuf, ignore); + } + + public Format[] getFormats () + { + Format[] f = new Format[elements.length]; + for (int i = elements.length - 1; i >= 0; --i) + f[i] = elements[i].setFormat; + return f; + } + + public Locale getLocale () + { + return locale; + } + + public int hashCode () + { + // FIXME: not a very good hash. + return pattern.hashCode() + locale.hashCode(); + } + + private MessageFormat () + { + } + + public MessageFormat (String pattern) + { + applyPattern (pattern); + } + + public Object[] parse (String sourceStr, ParsePosition pos) + { + // Check initial text. + int index = pos.getIndex(); + if (! sourceStr.startsWith(leader, index)) + { + pos.setErrorIndex(index); + return null; + } + index += leader.length(); + + Vector results = new Vector (elements.length, 1); + // Now check each format. + for (int i = 0; i < elements.length; ++i) + { + Format formatter = null; + if (elements[i].setFormat != null) + formatter = elements[i].setFormat; + else if (elements[i].format != null) + formatter = elements[i].format; + + Object value = null; + if (formatter instanceof ChoiceFormat) + { + // We must special-case a ChoiceFormat because it might + // have recursive formatting. + ChoiceFormat cf = (ChoiceFormat) formatter; + String[] formats = (String[]) cf.getFormats(); + double[] limits = (double[]) cf.getLimits(); + MessageFormat subfmt = new MessageFormat (); + subfmt.setLocale(locale); + ParsePosition subpos = new ParsePosition (index); + + int j; + for (j = 0; value == null && j < limits.length; ++j) + { + subfmt.applyPattern(formats[j]); + subpos.setIndex(index); + value = subfmt.parse(sourceStr, subpos); + } + if (value != null) + { + index = subpos.getIndex(); + value = new Double (limits[j]); + } + } + else if (formatter != null) + { + pos.setIndex(index); + value = formatter.parseObject(sourceStr, pos); + if (value != null) + index = pos.getIndex(); + } + else + { + // We have a String format. This can lose in a number + // of ways, but we give it a shot. + int next_index = sourceStr.indexOf(elements[i].trailer, index); + if (next_index == -1) + { + pos.setErrorIndex(index); + return null; + } + value = sourceStr.substring(index, next_index); + index = next_index; + } + + if (value == null + || ! sourceStr.startsWith(elements[i].trailer, index)) + { + pos.setErrorIndex(index); + return null; + } + + if (elements[i].argNumber >= results.size()) + results.setSize(elements[i].argNumber + 1); + results.setElementAt(value, elements[i].argNumber); + + index += elements[i].trailer.length(); + } + + Object[] r = new Object[results.size()]; + results.copyInto(r); + return r; + } + + public Object[] parse (String sourceStr) throws ParseException + { + ParsePosition pp = new ParsePosition (0); + Object[] r = parse (sourceStr, pp); + if (r == null) + throw new ParseException ("couldn't parse string", pp.getErrorIndex()); + return r; + } + + public Object parseObject (String sourceStr, ParsePosition pos) + { + return parse (sourceStr, pos); + } + + public void setFormat (int variableNum, Format newFormat) + { + elements[variableNum].setFormat = newFormat; + } + + public void setFormats (Format[] newFormats) + { + if (newFormats.length < elements.length) + throw new IllegalArgumentException (); + int len = Math.min(newFormats.length, elements.length); + for (int i = 0; i < len; ++i) + elements[i].setFormat = newFormats[i]; + } + + public void setLocale (Locale loc) + { + locale = loc; + if (elements != null) + { + for (int i = 0; i < elements.length; ++i) + elements[i].setLocale(loc); + } + } + + public String toPattern () + { + return pattern; + } + + // The pattern string. + private String pattern; + // The locale. + private Locale locale; + // Variables. + private MessageFormatElement[] elements; + // Leader text. + private String leader; +} diff --git a/libjava/java/text/NumberFormat.java b/libjava/java/text/NumberFormat.java new file mode 100644 index 0000000..6ee79b3 --- /dev/null +++ b/libjava/java/text/NumberFormat.java @@ -0,0 +1,236 @@ +/* Copyright (C) 1998, 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.MissingResourceException; + +/** + * @author Tom Tromey <tromey@cygnus.com> + * @date March 4, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 from http://www.javasoft.com. + * Status: Believed complete and correct to 1.2, except serialization + * and getAvailableLocales. + */ + +public abstract class NumberFormat extends Format implements Cloneable +{ + public static final int INTEGER_FIELD = 0; + public static final int FRACTION_FIELD = 1; + + public final String format (long number) + { + StringBuffer sbuf = new StringBuffer(50); + format (number, sbuf, null); + return sbuf.toString(); + } + + public final StringBuffer format (Object obj, StringBuffer sbuf, + FieldPosition pos) + { + return format(((Number) obj).doubleValue(), sbuf, pos); + } + + public abstract StringBuffer format (double number, + StringBuffer sbuf, FieldPosition pos); + + public abstract StringBuffer format (long number, + StringBuffer sbuf, FieldPosition pos); + + public static Locale[] getAvailableLocales () + { + // FIXME. + return null; + } + + private static final NumberFormat computeInstance (Locale loc, + String resource, + String def) + { + ResourceBundle res; + try + { + res = ResourceBundle.getBundle("gnu.gcj.text.LocaleData", loc); + } + catch (MissingResourceException x) + { + res = null; + } + String fmt; + try + { + fmt = res == null ? def : res.getString(resource); + } + catch (MissingResourceException x) + { + fmt = def; + } + DecimalFormatSymbols dfs = new DecimalFormatSymbols (loc); + return new DecimalFormat (fmt, dfs); + } + + public static final NumberFormat getCurrencyInstance () + { + return getCurrencyInstance (Locale.getDefault()); + } + + public static NumberFormat getCurrencyInstance (Locale loc) + { + return computeInstance (loc, "currencyFormat", "$#,##0.00;($#,##0.00)"); + } + + public static final NumberFormat getInstance () + { + return getInstance (Locale.getDefault()); + } + + public static NumberFormat getInstance (Locale loc) + { + // For now always return a number instance. + return getNumberInstance (loc); + } + + public int getMaximumFractionDigits () + { + return maximumFractionDigits; + } + + public int getMaximumIntegerDigits () + { + return maximumIntegerDigits; + } + + public int getMinimumFractionDigits () + { + return minimumFractionDigits; + } + + public int getMinimumIntegerDigits () + { + return minimumIntegerDigits; + } + + public static final NumberFormat getNumberInstance () + { + return getNumberInstance (Locale.getDefault()); + } + + public static NumberFormat getNumberInstance (Locale loc) + { + return computeInstance (loc, "numberFormat", "#,##0.###"); + } + + public static final NumberFormat getPercentInstance () + { + return getPercentInstance (Locale.getDefault()); + } + + public static NumberFormat getPercentInstance (Locale loc) + { + return computeInstance (loc, "percentFormat", "#,##0%"); + } + + public int hashCode () + { + int hash = super.hashCode(); + hash ^= (maximumFractionDigits + maximumIntegerDigits + + minimumFractionDigits + minimumIntegerDigits); + if (groupingUsed) + hash ^= 0xf0f0; + if (parseIntegerOnly) + hash ^= 0x0f0f; + return hash; + } + + public boolean isGroupingUsed () + { + return groupingUsed; + } + + public boolean isParseIntegerOnly () + { + return parseIntegerOnly; + } + + public NumberFormat () + { + } + + public abstract Number parse (String sourceStr, ParsePosition pos); + + public Number parse (String sourceStr) throws ParseException + { + ParsePosition pp = new ParsePosition (0); + Number r = parse (sourceStr, pp); + if (r == null) + { + int index = pp.getErrorIndex(); + if (index < 0) + index = pp.getIndex(); + throw new ParseException ("couldn't parse number", index); + } + return r; + } + + public final Object parseObject (String sourceStr, ParsePosition pos) + { + return parse (sourceStr, pos); + } + + public void setGroupingUsed (boolean newValue) + { + groupingUsed = newValue; + } + + public void setMaximumFractionDigits (int newValue) + { + maximumFractionDigits = newValue; + } + + public void setMaximumIntegerDigits (int newValue) + { + maximumIntegerDigits = newValue; + } + + public void setMinimumFractionDigits (int newValue) + { + minimumFractionDigits = newValue; + } + + public void setMinimumIntegerDigits (int newValue) + { + minimumIntegerDigits = newValue; + } + + public void setParseIntegerOnly (boolean value) + { + parseIntegerOnly = value; + } + + public final String format (double number) + { + StringBuffer sbuf = new StringBuffer(50); + format (number, sbuf, null); + return sbuf.toString(); + } + + // These field names are fixed by the serialization spec. + // FIXME: serialization spec also mentions `byte' versions of the + // min/max fields. We have no use for those, so for now they are + // omitted. + protected boolean groupingUsed; + protected int maximumFractionDigits; + protected int maximumIntegerDigits; + protected int minimumFractionDigits; + protected int minimumIntegerDigits; + protected boolean parseIntegerOnly; +} diff --git a/libjava/java/text/ParseException.java b/libjava/java/text/ParseException.java new file mode 100644 index 0000000..6bc9353 --- /dev/null +++ b/libjava/java/text/ParseException.java @@ -0,0 +1,34 @@ +/* Copyright (C) 1998, 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +/** + * @author Per Bothner <bothner@cygnus.com> + * @date October 25, 1998. + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: Believed complete and correct. + */ + +public class ParseException extends Exception +{ + private int errorOffset; + + public ParseException (String msg, int errorOffset) + { + super(msg); + this.errorOffset = errorOffset; + } + + public int getErrorOffset () + { + return errorOffset; + } +} diff --git a/libjava/java/text/ParsePosition.java b/libjava/java/text/ParsePosition.java new file mode 100644 index 0000000..4603f79 --- /dev/null +++ b/libjava/java/text/ParsePosition.java @@ -0,0 +1,59 @@ +/* Copyright (C) 1998, 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +/** + * @author Per Bothner <bothner@cygnus.com> + * @date October 25, 1998. + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: Believed complete and correct. + * Includes JDK 1.2 methods. + */ + +public class ParsePosition +{ + int index; + int errorIndex; + + public ParsePosition (int index) + { + this.index = index; + errorIndex = -1; + } + + public int getIndex () + { + return index; + } + + public void setIndex (int index) + { + this.index = index; + } + + public int getErrorIndex () + { + return errorIndex; + } + + public void setErrorIndex (int ei) + { + errorIndex = ei; + } + + public boolean equals (Object obj) + { + if (obj != null || ! (obj instanceof ParsePosition)) + return false; + ParsePosition other = (ParsePosition) obj; + return index == other.index && errorIndex == other.errorIndex; + } +} diff --git a/libjava/java/text/SimpleDateFormat.java b/libjava/java/text/SimpleDateFormat.java new file mode 100644 index 0000000..b401247 --- /dev/null +++ b/libjava/java/text/SimpleDateFormat.java @@ -0,0 +1,522 @@ +/* Copyright (C) 1998, 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +import java.util.*; + +/** + * @author Per Bothner <bothner@cygnus.com> + * @date October 25, 1998. + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: parse is not implemented. + */ + +public class SimpleDateFormat extends DateFormat +{ + private Date defaultCenturyStart; + private DateFormatSymbols formatData; + private String pattern; + + public SimpleDateFormat () + { + this("dd/MM/yy HH:mm", Locale.getDefault()); + } + + public SimpleDateFormat (String pattern) + { + this(pattern, Locale.getDefault()); + } + + public SimpleDateFormat (String pattern, Locale locale) + { + this.pattern = pattern; + this.calendar = Calendar.getInstance(locale); + this.numberFormat = NumberFormat.getInstance(locale); + numberFormat.setGroupingUsed(false); + this.formatData = new DateFormatSymbols (locale); + } + + public SimpleDateFormat (String pattern, DateFormatSymbols formatData) + { + this.pattern = pattern; + this.formatData = formatData; + this.calendar = Calendar.getInstance(); + this.numberFormat = NumberFormat.getInstance(); + numberFormat.setGroupingUsed(false); + } + + public Date get2DigitYearStart() + { + return defaultCenturyStart; + } + + public void set2DigitYearStart(Date startDate) + { + defaultCenturyStart = startDate; + } + + public DateFormatSymbols getDateFormatSymbols () + { + return formatData; + } + + public void setDateFormatSymbols (DateFormatSymbols value) + { + formatData = value; + } + + public String toPattern () + { + return pattern; + } + + public void applyPattern (String pattern) + { + this.pattern = pattern; + } + + private String applyLocalizedPattern (String pattern, + String oldChars, String newChars) + { + int len = pattern.length(); + StringBuffer buf = new StringBuffer(len); + boolean quoted = false; + for (int i = 0; i < len; i++) + { + char ch = pattern.charAt(i); + if (ch == '\'') + quoted = ! quoted; + if (! quoted) + { + int j = oldChars.indexOf(ch); + if (j >= 0) + ch = newChars.charAt(j); + } + buf.append(ch); + } + return buf.toString(); + } + + public void applyLocalizedPattern (String pattern) + { + String localChars = formatData.getLocalPatternChars(); + String standardChars = DateFormatSymbols.localPatternCharsDefault; + pattern = applyLocalizedPattern (pattern, localChars, standardChars); + applyPattern(pattern); + } + + public String toLocalizedPattern () + { + String localChars = formatData.getLocalPatternChars(); + String standardChars = DateFormatSymbols.localPatternCharsDefault; + return applyLocalizedPattern (pattern, standardChars, localChars); + } + + private final void append (StringBuffer buf, int value, int numDigits) + { + numberFormat.setMinimumIntegerDigits(numDigits); + numberFormat.format(value, buf, null); + } + + public StringBuffer format (Date date, StringBuffer buf, FieldPosition pos) + { + Calendar calendar = (Calendar) this.calendar.clone(); + calendar.setTime(date); + int len = pattern.length(); + int quoteStart = -1; + for (int i = 0; i < len; i++) + { + char ch = pattern.charAt(i); + if (ch == '\'') + { + // We must do a little lookahead to see if we have two + // single quotes embedded in quoted text. + if (i < len - 1 && pattern.charAt(i + 1) == '\'') + { + ++i; + buf.append(ch); + } + else + quoteStart = quoteStart < 0 ? i : -1; + } + // From JCL: any characters in the pattern that are not in + // the ranges of [a..z] and [A..Z] are treated as quoted + // text. + else if (quoteStart != -1 + || ((ch < 'a' || ch > 'z') + && (ch < 'A' || ch > 'Z'))) + buf.append(ch); + else + { + int first = i; + int value; + while (++i < len && pattern.charAt(i) == ch) ; + int count = i - first; // Number of repetions of ch in pattern. + int beginIndex = buf.length(); + int field; + i--; // Skip all but last instance of ch in pattern. + switch (ch) + { + case 'd': + append(buf, calendar.get(Calendar.DATE), count); + field = DateFormat.DATE_FIELD; + break; + case 'D': + append(buf, calendar.get(Calendar.DAY_OF_YEAR), count); + field = DateFormat.DAY_OF_YEAR_FIELD; + break; + case 'F': + append(buf, calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH),count); + field = DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD; + break; + case 'E': + value = calendar.get(calendar.DAY_OF_WEEK); + buf.append(count <= 3 ? formatData.getShortWeekdays()[value] + : formatData.getWeekdays()[value]); + field = DateFormat.DAY_OF_WEEK_FIELD; + break; + case 'w': + append(buf, calendar.get(Calendar.WEEK_OF_YEAR), count); + field = DateFormat.WEEK_OF_YEAR_FIELD; + break; + case 'W': + append(buf, calendar.get(Calendar.WEEK_OF_MONTH), count); + field = DateFormat.WEEK_OF_MONTH_FIELD; + break; + case 'M': + value = calendar.get(Calendar.MONTH); + if (count <= 2) + append(buf, value + 1, count); + else + buf.append(count <= 3 ? formatData.getShortMonths()[value] + : formatData.getMonths()[value]); + field = DateFormat.MONTH_FIELD; + break; + case 'y': + value = calendar.get(Calendar.YEAR); + append(buf, count <= 2 ? value % 100 : value, count); + field = DateFormat.YEAR_FIELD; + break; + case 'K': + append(buf, calendar.get(Calendar.HOUR), count); + field = DateFormat.HOUR0_FIELD; + break; + case 'h': + value = ((calendar.get(Calendar.HOUR) + 11) % 12) + 1; + append(buf, value, count); + field = DateFormat.HOUR1_FIELD; + break; + case 'H': + append(buf, calendar.get(Calendar.HOUR_OF_DAY), count); + field = DateFormat.HOUR_OF_DAY0_FIELD; + break; + case 'k': + value = ((calendar.get(Calendar.HOUR_OF_DAY) + 23) % 24) + 1; + append(buf, value, count); + field = DateFormat.HOUR_OF_DAY1_FIELD; + break; + case 'm': + append(buf, calendar.get(Calendar.MINUTE), count); + field = DateFormat.MINUTE_FIELD; + break; + case 's': + append(buf, calendar.get(Calendar.SECOND), count); + field = DateFormat.SECOND_FIELD; + break; + case 'S': + append(buf, calendar.get(Calendar.MILLISECOND), count); + field = DateFormat.MILLISECOND_FIELD; + break; + case 'a': + value = calendar.get(calendar.AM_PM); + buf.append(formatData.getAmPmStrings()[value]); + field = DateFormat.AM_PM_FIELD; + break; + case 'z': + String zoneID = calendar.getTimeZone().getID(); + String[][] zoneStrings = formatData.getZoneStrings(); + int zoneCount = zoneStrings.length; + for (int j = 0; j < zoneCount; j++) + { + String[] strings = zoneStrings[j]; + if (zoneID.equals(strings[0])) + { + j = count > 3 ? 2 : 1; + if (calendar.get(Calendar.DST_OFFSET) != 0) + j+=2; + zoneID = strings[j]; + break; + } + } + buf.append(zoneID); + field = DateFormat.TIMEZONE_FIELD; + break; + default: + // Note that the JCL is actually somewhat + // contradictory here. It defines the pattern letters + // to be a particular list, but also says that a + // pattern containing an invalid pattern letter must + // throw an exception. It doesn't describe what an + // invalid pattern letter might be, so we just assume + // it is any letter in [a-zA-Z] not explicitly covered + // above. + throw new RuntimeException("bad format string"); + } + if (pos != null && field == pos.getField()) + { + pos.setBeginIndex(beginIndex); + pos.setEndIndex(buf.length()); + } + } + } + return buf; + } + + private final boolean expect (String source, ParsePosition pos, + char ch) + { + int x = pos.getIndex(); + boolean r = x < source.length() && source.charAt(x) == ch; + if (r) + pos.setIndex(x + 1); + else + pos.setErrorIndex(x); + return r; + } + + public Date parse (String source, ParsePosition pos) + { + int fmt_index = 0; + int fmt_max = pattern.length(); + + calendar.clear(); + int quote_start = -1; + for (; fmt_index < fmt_max; ++fmt_index) + { + char ch = pattern.charAt(fmt_index); + if (ch == '\'') + { + int index = pos.getIndex(); + if (fmt_index < fmt_max - 1 + && pattern.charAt(fmt_index + 1) == '\'') + { + if (! expect (source, pos, ch)) + return null; + ++fmt_index; + } + else + quote_start = quote_start < 0 ? fmt_index : -1; + continue; + } + + if (quote_start != -1 + || ((ch < 'a' || ch > 'z') + && (ch < 'A' || ch > 'Z'))) + { + if (! expect (source, pos, ch)) + return null; + continue; + } + + // We've arrived at a potential pattern character in the + // pattern. + int first = fmt_index; + while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch) + ; + int count = fmt_index - first; + --fmt_index; + + // We can handle most fields automatically: most either are + // numeric or are looked up in a string vector. In some cases + // we need an offset. When numeric, `offset' is added to the + // resulting value. When doing a string lookup, offset is the + // initial index into the string array. + int calendar_field; + boolean is_numeric = true; + String[] match = null; + int offset = 0; + int zone_number = 0; + switch (ch) + { + case 'd': + calendar_field = Calendar.DATE; + break; + case 'D': + calendar_field = Calendar.DAY_OF_YEAR; + break; + case 'F': + calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH; + break; + case 'E': + is_numeric = false; + offset = 1; + calendar_field = Calendar.DAY_OF_WEEK; + match = (count <= 3 + ? formatData.getShortWeekdays() + : formatData.getWeekdays()); + break; + case 'w': + calendar_field = Calendar.WEEK_OF_YEAR; + break; + case 'W': + calendar_field = Calendar.WEEK_OF_MONTH; + break; + case 'M': + calendar_field = Calendar.MONTH; + if (count <= 2) + ; + else + { + is_numeric = false; + match = (count <= 3 + ? formatData.getShortMonths() + : formatData.getMonths()); + } + break; + case 'y': + calendar_field = Calendar.YEAR; + if (count <= 2) + offset = 1900; + break; + case 'K': + calendar_field = Calendar.HOUR; + break; + case 'h': + calendar_field = Calendar.HOUR; + offset = -1; + break; + case 'H': + calendar_field = Calendar.HOUR_OF_DAY; + break; + case 'k': + calendar_field = Calendar.HOUR_OF_DAY; + offset = -1; + break; + case 'm': + calendar_field = Calendar.MINUTE; + break; + case 's': + calendar_field = Calendar.SECOND; + break; + case 'S': + calendar_field = Calendar.MILLISECOND; + break; + case 'a': + is_numeric = false; + calendar_field = Calendar.AM_PM; + match = formatData.getAmPmStrings(); + break; + case 'z': + // We need a special case for the timezone, because it + // uses a different data structure than the other cases. + is_numeric = false; + calendar_field = Calendar.DST_OFFSET; + String[][] zoneStrings = formatData.getZoneStrings(); + int zoneCount = zoneStrings.length; + int index = pos.getIndex(); + boolean found_zone = false; + for (int j = 0; j < zoneCount; j++) + { + String[] strings = zoneStrings[j]; + int k; + for (k = 1; k < strings.length; ++k) + { + if (source.startsWith(strings[k], index)) + break; + } + if (k != strings.length) + { + if (k > 2) + ; // FIXME: dst. + zone_number = 0; // FIXME: dst. + // FIXME: raw offset to SimpleTimeZone const. + calendar.setTimeZone(new SimpleTimeZone (1, strings[0])); + pos.setIndex(index + strings[k].length()); + break; + } + } + if (! found_zone) + { + pos.setErrorIndex(pos.getIndex()); + return null; + } + break; + default: + pos.setErrorIndex(pos.getIndex()); + return null; + } + + // Compute the value we should assign to the field. + int value; + if (is_numeric) + { + numberFormat.setMinimumIntegerDigits(count); + Number n = numberFormat.parse(source, pos); + if (pos == null || ! (n instanceof Long)) + return null; + value = n.intValue() + offset; + } + else if (match != null) + { + int index = pos.getIndex(); + int i; + for (i = offset; i < match.length; ++i) + { + if (source.startsWith(match[i], index)) + break; + } + if (i == match.length) + { + pos.setErrorIndex(index); + return null; + } + pos.setIndex(index + match[i].length()); + value = i; + } + else + value = zone_number; + + // Assign the value and move on. + try + { + calendar.set(calendar_field, value); + } + // FIXME: what exception is thrown on an invalid + // non-lenient set? + catch (IllegalArgumentException x) + { + pos.setErrorIndex(pos.getIndex()); + return null; + } + } + + return calendar.getTime(); + } + + public boolean equals (Object obj) + { + if (! (obj instanceof SimpleDateFormat) || ! super.equals(obj) ) + return false; + SimpleDateFormat other = (SimpleDateFormat) obj; + return (DateFormatSymbols.equals(pattern, other.pattern) + && DateFormatSymbols.equals(formatData, other.formatData) + && DateFormatSymbols.equals(defaultCenturyStart, + other.defaultCenturyStart)); + } + + public int hashCode () + { + int hash = super.hashCode(); + if (pattern != null) + hash ^= pattern.hashCode(); + return hash; + } +} diff --git a/libjava/java/text/StringCharacterIterator.java b/libjava/java/text/StringCharacterIterator.java new file mode 100644 index 0000000..6eaa8e7 --- /dev/null +++ b/libjava/java/text/StringCharacterIterator.java @@ -0,0 +1,142 @@ +// StringCharacterIterator.java - Iterate over string of Unicode characters. + +/* Copyright (C) 1999 Cygnus Solutions + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +package java.text; + +/** + * @author Tom Tromey <tromey@cygnus.com> + * @date February 22, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: Believed complete and correct to 1.1. + */ + +public final class StringCharacterIterator implements CharacterIterator +{ + public Object clone () + { + return (Object) new StringCharacterIterator (text, begin, end, pos); + } + + public char current () + { + // This follows JDK 1.2 semantics and not 1.1 semantics. + // In 1.1 we would throw an exception if begin==end. + return (pos < end) ? text.charAt(pos) : CharacterIterator.DONE; + } + + public boolean equals (Object obj) + { + if (! (obj instanceof StringCharacterIterator)) + return false; + StringCharacterIterator sci = (StringCharacterIterator) obj; + // The spec says "the same text". We take this to mean equals, + // not ==. + return (pos == sci.pos + && begin == sci.begin + && end == sci.end + && text.equals(sci.text)); + } + + public char first () + { + pos = begin; + return current (); + } + + public int getBeginIndex () + { + return begin; + } + + public int getEndIndex () + { + return end; + } + + public int getIndex () + { + return pos; + } + + public int hashCode () + { + // FIXME: this is a terrible hash code. Find a better one. + return text.hashCode() + pos + begin + end; + } + + public char last () + { + pos = end; + return current (); + } + + public char next () + { + if (pos == end) + return CharacterIterator.DONE; + ++pos; + return current (); + } + + public char previous () + { + if (pos == begin) + return CharacterIterator.DONE; + --pos; + return current (); + } + + public char setIndex (int idx) + { + // In 1.1 we would throw an error if `idx == end'. + if (idx < begin || idx > end) + throw new IllegalArgumentException (); + pos = idx; + return current (); + } + + public StringCharacterIterator (String text) + { + // FIXME: remove check for null once we have compiler/runtime + // support for NullPointerException. + this (text, 0, text == null ? 0 : text.length(), 0); + } + public StringCharacterIterator (String text, int pos) + { + // FIXME: remove check for null once we have compiler/runtime + // support for NullPointerException. + this (text, 0, text == null ? 0 : text.length(), pos); + } + public StringCharacterIterator (String text, int begin, int end, int pos) + { + if (text == null) + throw new NullPointerException (); + if (begin < 0 || begin > end || end > text.length() + // In 1.1 we would also throw if `pos == end'. + || pos < begin || pos > end) + throw new IllegalArgumentException (); + + this.text = text; + this.begin = begin; + this.end = end; + this.pos = pos; + } + + // String to iterate over. + private String text; + // Current position. + private int pos; + // Start position in string. + private int begin; + // End position in string. + private int end; +} |