From c8875fb97fc03779a5bba09872227b1d08e5d52a Mon Sep 17 00:00:00 2001 From: tromey Date: Sat, 16 Jul 2005 00:30:23 +0000 Subject: Initial revision git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@102074 138bc75d-0d04-0410-961f-82ee72b054a4 --- libjava/classpath/java/text/Annotation.java | 113 ++ .../java/text/AttributedCharacterIterator.java | 268 ++++ libjava/classpath/java/text/AttributedString.java | 425 ++++++ .../java/text/AttributedStringIterator.java | 348 +++++ libjava/classpath/java/text/BreakIterator.java | 374 +++++ libjava/classpath/java/text/CharacterIterator.java | 144 ++ libjava/classpath/java/text/ChoiceFormat.java | 503 +++++++ .../java/text/CollationElementIterator.java | 467 +++++++ libjava/classpath/java/text/CollationKey.java | 199 +++ libjava/classpath/java/text/Collator.java | 400 ++++++ libjava/classpath/java/text/DateFormat.java | 892 ++++++++++++ libjava/classpath/java/text/DateFormatSymbols.java | 525 +++++++ libjava/classpath/java/text/DecimalFormat.java | 1435 ++++++++++++++++++++ .../classpath/java/text/DecimalFormatSymbols.java | 688 ++++++++++ libjava/classpath/java/text/FieldPosition.java | 232 ++++ libjava/classpath/java/text/Format.java | 181 +++ libjava/classpath/java/text/MessageFormat.java | 832 ++++++++++++ libjava/classpath/java/text/NumberFormat.java | 808 +++++++++++ libjava/classpath/java/text/ParseException.java | 86 ++ libjava/classpath/java/text/ParsePosition.java | 151 ++ libjava/classpath/java/text/RuleBasedCollator.java | 1017 ++++++++++++++ libjava/classpath/java/text/SimpleDateFormat.java | 1257 +++++++++++++++++ .../java/text/StringCharacterIterator.java | 356 +++++ .../classpath/java/text/class-dependencies.conf | 220 +++ libjava/classpath/java/text/package.html | 47 + 25 files changed, 11968 insertions(+) create mode 100644 libjava/classpath/java/text/Annotation.java create mode 100644 libjava/classpath/java/text/AttributedCharacterIterator.java create mode 100644 libjava/classpath/java/text/AttributedString.java create mode 100644 libjava/classpath/java/text/AttributedStringIterator.java create mode 100644 libjava/classpath/java/text/BreakIterator.java create mode 100644 libjava/classpath/java/text/CharacterIterator.java create mode 100644 libjava/classpath/java/text/ChoiceFormat.java create mode 100644 libjava/classpath/java/text/CollationElementIterator.java create mode 100644 libjava/classpath/java/text/CollationKey.java create mode 100644 libjava/classpath/java/text/Collator.java create mode 100644 libjava/classpath/java/text/DateFormat.java create mode 100644 libjava/classpath/java/text/DateFormatSymbols.java create mode 100644 libjava/classpath/java/text/DecimalFormat.java create mode 100644 libjava/classpath/java/text/DecimalFormatSymbols.java create mode 100644 libjava/classpath/java/text/FieldPosition.java create mode 100644 libjava/classpath/java/text/Format.java create mode 100644 libjava/classpath/java/text/MessageFormat.java create mode 100644 libjava/classpath/java/text/NumberFormat.java create mode 100644 libjava/classpath/java/text/ParseException.java create mode 100644 libjava/classpath/java/text/ParsePosition.java create mode 100644 libjava/classpath/java/text/RuleBasedCollator.java create mode 100644 libjava/classpath/java/text/SimpleDateFormat.java create mode 100644 libjava/classpath/java/text/StringCharacterIterator.java create mode 100644 libjava/classpath/java/text/class-dependencies.conf create mode 100644 libjava/classpath/java/text/package.html (limited to 'libjava/classpath/java/text') diff --git a/libjava/classpath/java/text/Annotation.java b/libjava/classpath/java/text/Annotation.java new file mode 100644 index 00000000000..cecb44aaf0e --- /dev/null +++ b/libjava/classpath/java/text/Annotation.java @@ -0,0 +1,113 @@ +/* Annotation.java -- Wrapper for a text attribute object + Copyright (C) 1998, 1999 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +/** + * This class is used as a wrapper for a text attribute object. Annotation + * objects are associated with a specific range of text. Changing either + * the text range or the underlying text invalidates the object. + * + * @version 0.0 + * + * @author Aaron M. Renn (arenn@urbanophile.com) + */ +public class Annotation +{ + +/* + * Instance Variables + */ + +/** + * This is the attribute object being wrappered + */ +private Object attrib; + +/*************************************************************************/ + +/** + * Constructors + */ + +/** + * This method initializes a new instance of Annotation to + * wrapper the specified text attribute object. + * + * @param attrib The text attribute Object to wrapper. + */ +public +Annotation(Object attrib) +{ + this.attrib = attrib; +} + +/*************************************************************************/ + +/* + * Instance Variables + */ + +/** + * This method returns the text attribute object this Annotation + * instance is wrappering. + * + * @return The text attribute object for this Annotation. + */ +public Object +getValue() +{ + return(attrib); +} + +/*************************************************************************/ + +/** + * This method returns a String representation of this + * object. + * + * @return This object as a String. + */ +public String +toString() +{ + return(getClass().getName() + "[value=" + attrib.toString() + "]"); +} + +} // class Annotation + diff --git a/libjava/classpath/java/text/AttributedCharacterIterator.java b/libjava/classpath/java/text/AttributedCharacterIterator.java new file mode 100644 index 00000000000..e5686ba346d --- /dev/null +++ b/libjava/classpath/java/text/AttributedCharacterIterator.java @@ -0,0 +1,268 @@ +/* AttributedCharacterIterator.java -- Iterate over attributes + Copyright (C) 1998, 1999, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.io.InvalidObjectException; +import java.io.Serializable; +import java.util.Map; +import java.util.Set; + +/** + * This interface extends the CharacterIterator interface + * in order to support iteration over character attributes as well as + * over the characters themselves. + *

+ * In addition to attributes of specific characters, this interface + * supports the concept of the "attribute run", which is an attribute + * that is defined for a particular value across an entire range of + * characters or which is undefined over a range of characters. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + */ +public interface AttributedCharacterIterator extends CharacterIterator +{ + /** + * This class defines attribute keys that are used as text attributes. + */ + public static class Attribute implements Serializable + { + private static final long serialVersionUID = -9142742483513960612L; + + /** + * This is the attribute for the language of the text. The value of + * attributes of this key type are instances of Locale. + */ + public static final Attribute LANGUAGE = new Attribute ("LANGUAGE"); + + /** + * This is the attribute for the reading form of text. This is used + * for storing pronunciation along with the written text for languages + * which need it. The value of attributes of this key type are + * instances of Annotation which wrappers a String. + */ + public static final Attribute READING = new Attribute ("READING"); + + /** + * This is the attribute for input method segments. The value of attributes + * of this key type are instances of Annotation which wrapper + * a String. + */ + public static final Attribute INPUT_METHOD_SEGMENT = + new Attribute ("INPUT_METHOD_SEGMENT"); + + /** + * This is the name of the attribute key + * @serial + */ + private String name; + + /** + * This method initializes a new instance of this class with the specified + * name. + * + * @param name The name of this attribute key. + */ + protected Attribute (String name) + { + this.name = name; + } + + /** + * This method returns the name of this attribute. + * + * @return The attribute name + */ + protected String getName() + { + return name; + } + + /** + * This method resolves an instance of AttributedCharacterIterator.Attribute + * that is being deserialized to one of the three pre-defined attribute + * constants. It does this by comparing the names of the attributes. The + * constant that the deserialized object resolves to is returned. + * + * @return The resolved contant value + * + * @exception InvalidObjectException If the object being deserialized cannot be resolved. + */ + protected Object readResolve() throws InvalidObjectException + { + if (this.equals (READING)) + return READING; + + if (this.equals (LANGUAGE)) + return LANGUAGE; + + if (this.equals (INPUT_METHOD_SEGMENT)) + return INPUT_METHOD_SEGMENT; + + throw new InvalidObjectException ("Can't resolve Attribute: " + getName()); + } + + /** + * This method tests this object for equality against the specified object. + * The two objects will be considered equal if and only if: + *

+ * + * @param The Object to test for equality against this object. + * + * @return true if the specified object is equal to this one, false otherwise. + */ + public final boolean equals (Object obj) + { + if (obj == this) + return true; + else + return false; + } + + /** + * This method returns a hash value for this object. + * + * @return A hash value for this object. + */ + public final int hashCode() + { + return super.hashCode(); + } + + /** + * This method returns a String representation of this object. + * + * @return A String representation of this object. + */ + public String toString() + { + return getClass().getName() + "(" + getName() + ")"; + } + + } // Inner class Attribute + + /** + * This method returns a list of all keys that are defined for the + * text range. This can be an empty list if no attributes are defined. + * + * @return A list of keys + */ + Set getAllAttributeKeys(); + + /** + * This method returns a Map of the attributed defined for + * the current character. + * + * @return A Map of the attributes for the current character. + */ + Map getAttributes(); + + /** + * This method returns the value of the specified attribute for the + * current character. If the attribute is not defined for the current + * character, null is returned. + * + * @param attrib The attribute to retrieve the value of. + * + * @return The value of the specified attribute + */ + Object getAttribute (AttributedCharacterIterator.Attribute attrib); + + /** + * This method returns the index of the first character in the run that + * contains all attributes defined for the current character. + * + * @return The start index of the run + */ + int getRunStart(); + + /** + * This method returns the index of the first character in the run that + * contains all attributes in the specified Set defined for + * the current character. + * + * @param attribs The Set of attributes. + * + * @return The start index of the run. + */ + int getRunStart (Set attribs); + + /** + * This method returns the index of the first character in the run that + * contains the specified attribute defined for the current character. + * + * @param attrib The attribute. + * + * @return The start index of the run. + */ + int getRunStart (AttributedCharacterIterator.Attribute attrib); + + /** + * This method returns the index of the character after the end of the run + * that contains all attributed defined for the current character. + * + * @return The end index of the run. + */ + int getRunLimit(); + + /** + * This method returns the index of the character after the end of the run + * that contains all attributes in the specified Set defined + * for the current character. + * + * @param attribs The Set of attributes. + * + * @return The end index of the run. + */ + int getRunLimit (Set attribs); + + /** + * This methods returns the index of the character after the end of the run + * that contains the specified attribute defined for the current character. + * + * @param attrib The attribute. + * + * @return The end index of the run. + */ + int getRunLimit (AttributedCharacterIterator.Attribute attrib); + +} // interface AttributedCharacterIterator diff --git a/libjava/classpath/java/text/AttributedString.java b/libjava/classpath/java/text/AttributedString.java new file mode 100644 index 00000000000..b9ced8f5ac2 --- /dev/null +++ b/libjava/classpath/java/text/AttributedString.java @@ -0,0 +1,425 @@ +/* AttributedString.java -- Models text with attributes + Copyright (C) 1998, 1999, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * This class models a String with attributes over various + * subranges of the string. It allows applications to access this + * information via the AttributedCharcterIterator interface. + * + * @version 0.0 + * + * @author Aaron M. Renn (arenn@urbanophile.com) + */ +public class AttributedString +{ + +/*************************************************************************/ + +/* + * Inner Classes + */ + +/** + * This class contains the attributes and ranges of text over which + * that attributes apply. + */ +final class AttributeRange +{ + +/* + * Instance Variables + */ + +/** + * A Map of the attributes + */ +Map attribs; + +/** + * The beginning index of the attributes + */ +int begin_index; + +/** + * The ending index of the attributes + */ +int end_index; + +/*************************************************************************/ + +/* + * Constructors + */ + +AttributeRange(Map attribs, int begin_index, int end_index) +{ + this.attribs = attribs; + this.begin_index = begin_index; + this.end_index = end_index; +} + +} // Inner class AttributeRange + +/*************************************************************************/ + +/* + * Instance Variables + */ + +/** + * This object holds the string we are representing. + */ +private StringCharacterIterator sci; + +/** + * This is the attribute information + */ +private AttributeRange[] attribs; + +/*************************************************************************/ + +/* + * Constructors + */ + +/** + * This method initializes a new instance of AttributedString + * that represents the specified String with no attributes. + * + * @param str The String to be attributed. + */ +public +AttributedString(String str) +{ + sci = new StringCharacterIterator(str); + attribs = new AttributeRange[0]; +} + +/*************************************************************************/ + +/** + * This method initializes a new instance of AttributedString + * that represents that specified String with the specified + * attributes over the entire length of the String. + * + * @param str The String to be attributed. + * @param attributes The attribute list. + */ +public +AttributedString(String str, Map attributes) +{ + this(str); + + attribs = new AttributeRange[1]; + attribs[0] = new AttributeRange(attributes, 0, str.length()); +} + +/*************************************************************************/ + +/** + * This method initializes a new instance of AttributedString + * that will use the text and attribute information from the specified + * AttributedCharacterIterator. + * + * @param aci The AttributedCharacterIterator containing the text and attribute information. + */ +public +AttributedString(AttributedCharacterIterator aci) +{ + this(aci, aci.getBeginIndex(), aci.getEndIndex(), null); +} + +/*************************************************************************/ + +/** + * This method initializes a new instance of AttributedString + * that will use the text and attribute information from the specified + * subrange of the specified AttributedCharacterIterator. + * + * @param aci The AttributedCharacterIterator containing the text and attribute information. + * @param begin_index The beginning index of the text subrange. + * @param end_index The ending index of the text subrange. + */ +public +AttributedString(AttributedCharacterIterator aci, int begin_index, + int end_index) +{ + this(aci, begin_index, end_index, null); +} + +/*************************************************************************/ + +/** + * This method initializes a new instance of AttributedString + * that will use the text and attribute information from the specified + * subrange of the specified AttributedCharacterIterator. + * Only attributes from the source iterator that are present in the + * specified array of attributes will be included in the attribute list + * for this object. + * + * @param aci The AttributedCharacterIterator containing the text and attribute information. + * @param begin_index The beginning index of the text subrange. + * @param end_index The ending index of the text subrange. + * @param attributes A list of attributes to include from the iterator, or null to include all attributes. + */ +public +AttributedString(AttributedCharacterIterator aci, int begin_index, + int end_index, AttributedCharacterIterator.Attribute[] attributes) +{ + // Validate some arguments + if ((begin_index < 0) || (end_index < begin_index)) + throw new IllegalArgumentException("Bad index values"); + + StringBuffer sb = new StringBuffer(""); + + // Get the valid attribute list + Set all_attribs = aci.getAllAttributeKeys(); + if (attributes != null) + all_attribs.retainAll(Arrays.asList(attributes)); + + // Loop through and extract the attributes + char c = aci.setIndex(begin_index); + + ArrayList accum = new ArrayList(); + do + { + sb.append(c); + + Iterator iter = all_attribs.iterator(); + while(iter.hasNext()) + { + Object obj = iter.next(); + + // What should we do if this is not true? + if (!(obj instanceof AttributedCharacterIterator.Attribute)) + continue; + + AttributedCharacterIterator.Attribute attrib = + (AttributedCharacterIterator.Attribute)obj; + + // Make sure the attribute is defined. + int rl = aci.getRunLimit(attrib); + if (rl == -1) + continue; + if (rl > end_index) + rl = end_index; + rl -= begin_index; + + // Check to see if we already processed this one + int rs = aci.getRunStart(attrib); + if ((rs < aci.getIndex()) && (aci.getIndex() != begin_index)) + continue; + + // If the attribute run starts before the beginning index, we + // need to junk it if it is an Annotation. + Object attrib_obj = aci.getAttribute(attrib); + if (rs < begin_index) + { + if (attrib_obj instanceof Annotation) + continue; + + rs = begin_index; + } + else + { + rs -= begin_index; + } + + // Create a map object. Yes this will only contain one attribute + Map new_map = new Hashtable(); + new_map.put(attrib, attrib_obj); + + // Add it to the attribute list. + accum.add(new AttributeRange(new_map, rs, rl)); + } + + c = aci.next(); + } + while(c != CharacterIterator.DONE); + + attribs = new AttributeRange[accum.size()]; + attribs = (AttributeRange[]) accum.toArray(attribs); + + sci = new StringCharacterIterator(sb.toString()); +} + +/*************************************************************************/ + +/* + * Instance Methods + */ + +/** + * This method adds a new attribute that will cover the entire string. + * + * @param attrib The attribute to add. + * @param value The value of the attribute. + */ +public void +addAttribute(AttributedCharacterIterator.Attribute attrib, Object value) +{ + addAttribute(attrib, value, 0, sci.getEndIndex()); +} + +/*************************************************************************/ + +/** + * This method adds a new attribute that will cover the specified subrange + * of the string. + * + * @param attrib The attribute to add. + * @param value The value of the attribute, which may be null. + * @param begin_index The beginning index of the subrange. + * @param end_index The ending index of the subrange. + * + * @exception IllegalArgumentException If attribute is null or the subrange is not valid. + */ +public void +addAttribute(AttributedCharacterIterator.Attribute attrib, Object value, + int begin_index, int end_index) +{ + if (attrib == null) + throw new IllegalArgumentException("null attribute"); + + HashMap hm = new HashMap(); + hm.put(attrib, value); + + addAttributes(hm, begin_index, end_index); +} + +/*************************************************************************/ + +/** + * This method adds all of the attributes in the specified list to the + * specified subrange of the string. + * + * @param attributes The list of attributes. + * @param begin_index The beginning index. + * @param end_index The ending index + * + * @param IllegalArgumentException If the list is null or the subrange is not valid. + */ +public void +addAttributes(Map attributes, int begin_index, int end_index) +{ + if (attributes == null) + throw new IllegalArgumentException("null attribute"); + + if ((begin_index < 0) || (end_index > sci.getEndIndex()) || + (end_index < begin_index)) + throw new IllegalArgumentException("bad range"); + + AttributeRange[] new_list = new AttributeRange[attribs.length + 1]; + System.arraycopy(attribs, 0, new_list, 0, attribs.length); + attribs = new_list; + attribs[attribs.length - 1] = new AttributeRange(attributes, begin_index, + end_index); +} + +/*************************************************************************/ + +/** + * This method returns an AttributedCharacterIterator that + * will iterate over the entire string. + * + * @return An AttributedCharacterIterator for the entire string. + */ +public AttributedCharacterIterator +getIterator() +{ + return(new AttributedStringIterator(sci, attribs, 0, sci.getEndIndex(), null)); +} + +/*************************************************************************/ + +/** + * This method returns an AttributedCharacterIterator that + * will iterate over the entire string. This iterator will return information + * about the list of attributes in the specified array. Attributes not in + * the array may or may not be returned by the iterator. If the specified + * array is null, all attributes will be returned. + * + * @param attributes A list of attributes to include in the returned iterator. + * + * @return An AttributedCharacterIterator for this string. + */ +public AttributedCharacterIterator +getIterator(AttributedCharacterIterator.Attribute[] attributes) +{ + return(getIterator(attributes, 0, sci.getEndIndex())); +} + +/*************************************************************************/ + +/** + * This method returns an AttributedCharacterIterator that + * will iterate over the specified subrange. This iterator will return information + * about the list of attributes in the specified array. Attributes not in + * the array may or may not be returned by the iterator. If the specified + * array is null, all attributes will be returned. + * + * @param attributes A list of attributes to include in the returned iterator. + * @param begin_index The beginning index of the subrange. + * @param end_index The ending index of the subrange. + * + * @return An AttributedCharacterIterator for this string. + */ +public AttributedCharacterIterator +getIterator(AttributedCharacterIterator.Attribute[] attributes, + int begin_index, int end_index) +{ + if ((begin_index < 0) || (end_index > sci.getEndIndex()) || + (end_index < begin_index)) + throw new IllegalArgumentException("bad range"); + + return(new AttributedStringIterator(sci, attribs, begin_index, end_index, + attributes)); +} + +} // class AttributedString + diff --git a/libjava/classpath/java/text/AttributedStringIterator.java b/libjava/classpath/java/text/AttributedStringIterator.java new file mode 100644 index 00000000000..7d7bf270cef --- /dev/null +++ b/libjava/classpath/java/text/AttributedStringIterator.java @@ -0,0 +1,348 @@ +/* AttributedStringIterator.java -- Class to iterate over AttributedString + Copyright (C) 1998, 1999, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * This class implements the AttributedCharacterIterator interface. It + * is used by AttributedString.getIterator(). + * + * @version 0.0 + * + * @author Aaron M. Renn (arenn@urbanophile.com) + */ +class AttributedStringIterator implements AttributedCharacterIterator +{ + +/*************************************************************************/ + +/** + * Instance Variables + */ + +/** + * The character iterator containing the text + */ +private CharacterIterator ci; + +/** + * The list of attributes and ranges + */ +private AttributedString.AttributeRange[] attribs; + +/** + * The list of attributes that the user is interested in. We may, + * at our option, not return any other attributes. + */ +private AttributedCharacterIterator.Attribute[] restricts; + +/*************************************************************************/ + +/* + * Constructors + */ + +AttributedStringIterator(StringCharacterIterator sci, + AttributedString.AttributeRange[] attribs, + int begin_index, int end_index, + AttributedCharacterIterator.Attribute[] restricts) +{ + this.ci = new StringCharacterIterator(sci, begin_index, end_index); + this.attribs = attribs; + this.restricts = restricts; +} + +/*************************************************************************/ + +/* + * Instance Methods + */ + +// First we have a bunch of stupid redirects. If StringCharacterIterator +// weren't final, I just would have extended that for this class. Alas, no. + +public Object +clone() +{ + return(ci.clone()); +} + +public char +current() +{ + return(ci.current()); +} + +public char +next() +{ + return(ci.next()); +} + +public char +previous() +{ + return(ci.previous()); +} + +public char +first() +{ + return(ci.first()); +} + +public char +last() +{ + return(ci.last()); +} + +public int +getIndex() +{ + return(ci.getIndex()); +} + +public char +setIndex(int index) +{ + return(ci.setIndex(index)); +} + +public int +getBeginIndex() +{ + return(ci.getBeginIndex()); +} + +public int +getEndIndex() +{ + return(ci.getEndIndex()); +} + +/* + * Here is where the AttributedCharacterIterator methods start. + */ + +/*************************************************************************/ + +/** + * Returns a list of all the attribute keys that are defined anywhere + * on this string. + */ +public Set +getAllAttributeKeys() +{ + HashSet s = new HashSet(); + if (attribs == null) + return(s); + + for (int i = 0; i < attribs.length; i++) + { + if (attribs[i].begin_index > getEndIndex() + || attribs[i].end_index <= getBeginIndex()) + continue; + + Set key_set = attribs[i].attribs.keySet(); + Iterator iter = key_set.iterator(); + while (iter.hasNext()) + { + s.add(iter.next()); + } + } + + return(s); +} + +/*************************************************************************/ + +/** + * Various methods that determine how far the run extends for various + * attribute combinations. + */ + +public int +getRunLimit() +{ + return(getRunLimit(getAttributes().keySet())); +} + +public int +getRunLimit(AttributedCharacterIterator.Attribute attrib) +{ + HashSet s = new HashSet(); + s.add(attrib); + + return(getRunLimit(s)); +} + +public synchronized int +getRunLimit(Set attribute_set) +{ + boolean hit = false; + int runLimit = ci.getEndIndex (); + int pos = ci.getIndex (); + + for (int i = 0; i < attribs.length; ++i) + { + if (pos >= attribs[i].begin_index && + pos < attribs[i].end_index) + { + Iterator iter = attribute_set.iterator(); + while(iter.hasNext()) + if (attribs[i].attribs.containsKey(iter.next())) + { + hit = true; + runLimit = Math.min(runLimit, attribs[i].end_index); + } + } + } + if (hit) + return runLimit; + else + return ci.getEndIndex(); +} + +/*************************************************************************/ + +/** + * Various methods that determine where the run begins for various + * attribute combinations. + */ + +public int +getRunStart() +{ + return(getRunStart(getAttributes().keySet())); +} + +public int +getRunStart(AttributedCharacterIterator.Attribute attrib) +{ + HashSet s = new HashSet(); + s.add(attrib); + + return(getRunStart(s)); +} + +public int +getRunStart(Set attribute_set) +{ + boolean hit = false; + int runBegin = 0; + int pos = ci.getIndex (); + + for (int i = 0; i < attribs.length; ++i) + { + if (pos >= attribs[i].begin_index && + pos <= attribs[i].end_index) + { + Iterator iter = attribute_set.iterator(); + while(iter.hasNext()) + if (attribs[i].attribs.containsKey(iter.next())) + { + hit = true; + runBegin = Math.max(runBegin, attribs[i].begin_index); + } + } + } + if (hit) + return runBegin; + else + return -1; +} + +/*************************************************************************/ + +public Object +getAttribute(AttributedCharacterIterator.Attribute attrib) +{ + if (attribs == null) + return(null); + + for (int i = 0; i < attribs.length; i++) + { + Set key_set = attribs[i].attribs.keySet(); + Iterator iter = key_set.iterator(); + while (iter.hasNext()) + { + Object obj = iter.next(); + + // Check for attribute match and range match + if (obj.equals(attrib)) + if ((ci.getIndex() >= attribs[i].begin_index) && + (ci.getIndex() < attribs[i].end_index)) + return(attribs[i].attribs.get(obj)); + } + } + + return(null); +} + +/*************************************************************************/ + +/** + * Return a list of all the attributes and values defined for this + * character + */ +public Map +getAttributes() +{ + HashMap m = new HashMap(); + if (attribs == null) + return(m); + + for (int i = 0; i < attribs.length; i++) + { + if ((ci.getIndex() >= attribs[i].begin_index) && + (ci.getIndex() < attribs[i].end_index)) + m.putAll(attribs[i].attribs); + } + + return(m); +} + +} // class AttributedStringIterator + diff --git a/libjava/classpath/java/text/BreakIterator.java b/libjava/classpath/java/text/BreakIterator.java new file mode 100644 index 00000000000..d021dceba73 --- /dev/null +++ b/libjava/classpath/java/text/BreakIterator.java @@ -0,0 +1,374 @@ +/* BreakIterator.java -- Breaks text into elements + Copyright (C) 1998, 1999, 2001, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * This class iterates over text elements such as words, lines, sentences, + * and characters. It can only iterate over one of these text elements at + * a time. An instance of this class configured for the desired iteration + * type is created by calling one of the static factory methods, not + * by directly calling a constructor. + * + * The standard iterators created by the factory methods in this + * class will be valid upon creation. That is, their methods will + * not cause exceptions if called before you call setText(). + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Aaron M. Renn (arenn@urbanophile.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 +{ + /** + * This value is returned by the next() and + * previous in order to indicate that the end of the + * text has been reached. + */ + // The value was discovered by writing a test program. + public static final int DONE = -1; + + /** + * This method initializes a new instance of BreakIterator. + * This protected constructor is available to subclasses as a default + * no-arg superclass constructor. + */ + protected BreakIterator () + { + } + + /** + * Create a clone of this object. + */ + public Object clone () + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + return null; + } + } + + /** + * This method returns the index of the current text element boundary. + * + * @return The current text boundary. + */ + public abstract int current (); + + /** + * This method returns the first text element boundary in the text being + * iterated over. + * + * @return The first text boundary. + */ + public abstract int first (); + + /** + * This methdod returns the offset of the text element boundary following + * the specified offset. + * + * @param offset The text index from which to find the next text boundary. + * + * @param The next text boundary following the specified index. + */ + public abstract int following (int pos); + + /** + * This method returns a list of locales for which instances of + * BreakIterator are available. + * + * @return A list of available locales + */ + public static synchronized Locale[] getAvailableLocales () + { + Locale[] l = new Locale[1]; + l[0] = Locale.US; + return l; + } + + private static BreakIterator getInstance (String type, Locale loc) + { + String className; + try + { + ResourceBundle res + = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + loc, ClassLoader.getSystemClassLoader()); + 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; + } + } + + /** + * This method returns an instance of BreakIterator that will + * iterate over characters as defined in the default locale. + * + * @return A BreakIterator instance for the default locale. + */ + public static BreakIterator getCharacterInstance () + { + return getCharacterInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of BreakIterator that will + * iterate over characters as defined in the specified locale. If the + * desired locale is not available, the default locale is used. + * + * @param locale The desired locale. + * + * @return A BreakIterator instance for the default locale. + */ + public static BreakIterator getCharacterInstance (Locale loc) + { + BreakIterator r = getInstance ("CharacterIterator", loc); + if (r == null) + r = new gnu.java.text.CharacterBreakIterator (); + return r; + } + + /** + * This method returns an instance of BreakIterator that will + * iterate over line breaks as defined in the default locale. + * + * @return A BreakIterator instance for the default locale. + */ + public static BreakIterator getLineInstance () + { + return getLineInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of BreakIterator that will + * iterate over line breaks as defined in the specified locale. If the + * desired locale is not available, the default locale is used. + * + * @param locale The desired locale. + * + * @return A BreakIterator instance for the default locale. + */ + public static BreakIterator getLineInstance (Locale loc) + { + BreakIterator r = getInstance ("LineIterator", loc); + if (r == null) + r = new gnu.java.text.LineBreakIterator (); + return r; + } + + /** + * This method returns an instance of BreakIterator that will + * iterate over sentences as defined in the default locale. + * + * @return A BreakIterator instance for the default locale. + */ + public static BreakIterator getSentenceInstance () + { + return getSentenceInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of BreakIterator that will + * iterate over sentences as defined in the specified locale. If the + * desired locale is not available, the default locale is used. + * + * @param locale The desired locale. + * + * @return A BreakIterator instance for the default locale. + */ + public static BreakIterator getSentenceInstance (Locale loc) + { + BreakIterator r = getInstance ("SentenceIterator", loc); + if (r == null) + r = new gnu.java.text.SentenceBreakIterator (); + return r; + } + + /** + * This method returns the text this object is iterating over as a + * CharacterIterator. + * + * @param The text being iterated over. + */ + public abstract CharacterIterator getText (); + + /** + * This method returns an instance of BreakIterator that will + * iterate over words as defined in the default locale. + * + * @return A BreakIterator instance for the default locale. + */ + public static BreakIterator getWordInstance () + { + return getWordInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of BreakIterator that will + * iterate over words as defined in the specified locale. If the + * desired locale is not available, the default locale is used. + * + * @param locale The desired locale. + * + * @return A BreakIterator instance for the default locale. + */ + public static BreakIterator getWordInstance (Locale loc) + { + BreakIterator r = getInstance ("WordIterator", loc); + if (r == null) + r = new gnu.java.text.WordBreakIterator (); + return r; + } + + /** + * This method tests whether or not the specified position is a text + * element boundary. + * + * @param offset The text position to test. + * + * @return true if the position is a boundary, + * false otherwise. + */ + public boolean isBoundary (int pos) + { + if (pos == 0) + return true; + return following (pos - 1) == pos; + } + + /** + * This method returns the last text element boundary in the text being + * iterated over. + * + * @return The last text boundary. + */ + public abstract int last (); + + /** + * This method returns the text element boundary following the current + * text position. + * + * @return The next text boundary. + */ + public abstract int next (); + + /** + * This method returns the n'th text element boundary following the current + * text position. + * + * @param n The number of text element boundaries to skip. + * + * @return The next text boundary. + */ + public abstract int next (int n); + + /** + * This methdod returns the offset of the text element boundary preceding + * the specified offset. + * + * @param offset The text index from which to find the preceding + * text boundary. + * + * @returns The next text boundary preceding the specified index. + */ + public int preceding (int pos) + { + if (following (pos) == DONE) + last (); + while (previous () >= pos) + ; + return current (); + } + + /** + * This method returns the text element boundary preceding the current + * text position. + * + * @return The previous text boundary. + */ + public abstract int previous (); + + /** + * This method sets the text string to iterate over. + * + * @param str The String to iterate over. + */ + public void setText (String newText) + { + setText (new StringCharacterIterator (newText)); + } + + /** + * This method sets the text to iterate over from the specified + * CharacterIterator. + * + * @param ci The desired CharacterIterator. + */ + public abstract void setText (CharacterIterator newText); +} diff --git a/libjava/classpath/java/text/CharacterIterator.java b/libjava/classpath/java/text/CharacterIterator.java new file mode 100644 index 00000000000..6b3f951d1fb --- /dev/null +++ b/libjava/classpath/java/text/CharacterIterator.java @@ -0,0 +1,144 @@ +/* CharacterIterator.java -- Iterate over a character range + Copyright (C) 1998, 2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +/** + * This interface defines a mechanism for iterating over a range of + * characters. For a given range of text, a beginning and ending index, + * as well as a current index are defined. These values can be queried + * by the methods in this interface. Additionally, various methods allow + * the index to be set. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + */ +public interface CharacterIterator extends Cloneable +{ + /** + * This is a special constant value that is returned when the beginning or + * end of the character range has been reached. + */ + char DONE = '\uFFFF'; + + /** + * This method returns the character at the current index position + * + * @return The character at the current index position. + */ + char current(); + + /** + * This method increments the current index and then returns the character + * at the new index value. If the index is already at getEndIndex() - 1, + * it will not be incremented. + * + * @return The character at the position of the incremented index value, + * or DONE if the index has reached getEndIndex() - 1 + */ + char next(); + + /** + * This method decrements the current index and then returns the character + * at the new index value. If the index value is already at the beginning + * index, it will not be decremented. + * + * @return The character at the position of the decremented index value, + * or DONE if index was already equal to the beginning index value. + */ + char previous(); + + /** + * This method sets the index value to the beginning of the range and returns + * the character there. + * + * @return The character at the beginning of the range, or DONE if the range is empty. + */ + char first(); + + /** + * This method sets the index value to getEndIndex() - 1 and + * returns the character there. If the range is empty, then the index value + * will be set equal to the beginning index. + * + * @return The character at the end of the range, or DONE if the range is empty. + */ + char last(); + + /** + * This method returns the current value of the index. + * + * @return The current index value + */ + int getIndex(); + + /** + * This method sets the value of the index to the specified value, then + * returns the character at that position. + * + * @param index The new index value. + * + * @return The character at the new index value or DONE if the index value is equal to getEndIndex. + */ + char setIndex (int index) throws IllegalArgumentException; + + /** + * This method returns the character position of the first character in the + * range. + * + * @return The index of the first character in the range. + */ + int getBeginIndex(); + + /** + * This method returns the character position of the end of the text range. + * This will actually be the index of the first character following the + * end of the range. In the event the text range is empty, this will be + * equal to the first character in the range. + * + * @return The index of the end of the range. + */ + int getEndIndex(); + + /** + * This method creates a copy of this CharacterIterator. + * + * @return A copy of this CharacterIterator. + */ + Object clone(); + +} // interface CharacterIterator diff --git a/libjava/classpath/java/text/ChoiceFormat.java b/libjava/classpath/java/text/ChoiceFormat.java new file mode 100644 index 00000000000..23c8a8c3af0 --- /dev/null +++ b/libjava/classpath/java/text/ChoiceFormat.java @@ -0,0 +1,503 @@ +/* ChoiceFormat.java -- Format over a range of numbers + Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005 + Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.util.Vector; + +/** + * This class allows a format to be specified based on a range of numbers. + * To use this class, first specify two lists of formats and range terminators. + * These lists must be arrays of equal length. The format of index + * i will be selected for value X if + * terminator[i] <= X < limit[i + 1]. If the value X is not + * included in any range, then either the first or last format will be + * used depending on whether the value X falls outside the range. + *

+ * This sounds complicated, but that is because I did a poor job of + * explaining it. Consider the following example: + *

+ * +

terminators = { 1, ChoiceFormat.nextDouble(1) }
+formats = { "file", "files" }
+ * + *

+ * In this case if the actual number tested is one or less, then the word + * "file" is used as the format value. If the number tested is greater than + * one, then "files" is used. This allows plurals to be handled + * gracefully. Note the use of the method nextDouble. This + * method selects the next highest double number than its argument. This + * effectively makes any double greater than 1.0 cause the "files" string + * to be selected. (Note that all terminator values are specified as + * doubles. + *

+ * Note that in order for this class to work properly, the range terminator + * array must be sorted in ascending order and the format string array + * must be the same length as the terminator array. + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Aaron M. Renn (arenn@urbanophile.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 +{ + /** + * This method sets new range terminators and format strings for this + * object based on the specified pattern. This pattern is of the form + * "term#string|term#string...". For example "1#Sunday|2#Monday|#Tuesday". + * + * @param pattern The pattern of terminators and format strings. + * + * @exception IllegalArgumentException If the pattern is not valid + */ + public void applyPattern (String newPattern) + { + // 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. + 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; + } + + choiceFormats = new String[stringVec.size()]; + stringVec.copyInto(choiceFormats); + + choiceLimits = new double[limitVec.size()]; + for (int i = 0; i < choiceLimits.length; ++i) + { + Double d = (Double) limitVec.elementAt(i); + choiceLimits[i] = d.doubleValue(); + } + } + + /** + * This method initializes a new instance of ChoiceFormat that + * generates its range terminator and format string arrays from the + * specified pattern. This pattern is of the form + * "term#string|term#string...". For example "1#Sunday|2#Monday|#Tuesday". + * This is the same pattern type used by the applyPattern + * method. + * + * @param pattern The pattern of terminators and format strings. + * + * @exception IllegalArgumentException If the pattern is not valid + */ + public ChoiceFormat (String newPattern) + { + super (); + applyPattern (newPattern); + } + + /** + * This method initializes a new instance of ChoiceFormat that + * will use the specified range terminators and format strings. + * + * @param choiceLimits The array of range terminators + * @param choiceFormats The array of format strings + */ + public ChoiceFormat (double[] choiceLimits, String[] choiceFormats) + { + super (); + setChoices (choiceLimits, choiceFormats); + } + + /** + * This method tests this object for equality with the specified + * object. This will be true if and only if: + *

+ * + * @param obj The object to test for equality against. + * + * @return true if the specified object is equal to + * this one, false otherwise. + */ + public boolean equals (Object obj) + { + if (! (obj instanceof ChoiceFormat)) + return false; + ChoiceFormat cf = (ChoiceFormat) obj; + if (choiceLimits.length != cf.choiceLimits.length) + return false; + for (int i = choiceLimits.length - 1; i >= 0; --i) + { + if (choiceLimits[i] != cf.choiceLimits[i] + || !choiceFormats[i].equals(cf.choiceFormats[i])) + return false; + } + return true; + } + + /** + * This method appends the appropriate format string to the specified + * StringBuffer based on the supplied long + * argument. + * + * @param number The number used for determine (based on the range + * terminators) which format string to append. + * @param sb The StringBuffer to append the format string to. + * @param status Unused. + * + * @return The StringBuffer with the format string appended. + */ + public StringBuffer format (long num, StringBuffer appendBuf, + FieldPosition pos) + { + return format ((double) num, appendBuf, pos); + } + + /** + * This method appends the appropriate format string to the specified + * StringBuffer based on the supplied double + * argument. + * + * @param number The number used for determine (based on the range + * terminators) which format string to append. + * @param sb The StringBuffer to append the format string to. + * @param status Unused. + * + * @return The StringBuffer with the format string appended. + */ + public StringBuffer format (double num, StringBuffer appendBuf, + FieldPosition pos) + { + if (choiceLimits.length == 0) + return appendBuf; + + int index = 0; + if (! Double.isNaN(num) && num >= choiceLimits[0]) + { + for (; index < choiceLimits.length - 1; ++index) + { + if (choiceLimits[index] <= num && num < choiceLimits[index + 1]) + break; + } + } + + return appendBuf.append(choiceFormats[index]); + } + + /** + * This method returns the list of format strings in use. + * + * @return The list of format objects. + */ + public Object[] getFormats () + { + return (Object[]) choiceFormats.clone(); + } + + /** + * This method returns the list of range terminators in use. + * + * @return The list of range terminators. + */ + public double[] getLimits () + { + return (double[]) choiceLimits.clone(); + } + + /** + * This method returns a hash value for this object + * + * @return A hash value for this object. + */ + public int hashCode () + { + int hash = 0; + for (int i = 0; i < choiceLimits.length; ++i) + { + long v = Double.doubleToLongBits(choiceLimits[i]); + hash ^= (v ^ (v >>> 32)); + hash ^= choiceFormats[i].hashCode(); + } + return hash; + } + + /** + * This method returns the lowest possible double greater than the + * specified double. If the specified double value is equal to + * Double.NaN then that is the value returned. + * + * @param d The specified double + * + * @return The lowest double value greater than the specified double. + */ + public static final double nextDouble (double d) + { + return nextDouble (d, true); + } + + /** + * This method returns a double that is either the next highest double + * or next lowest double compared to the specified double depending on the + * value of the passed boolean parameter. If the boolean parameter is + * true, then the lowest possible double greater than the + * specified double will be returned. Otherwise the highest possible + * double less than the specified double will be returned. + * + * @param d The specified double + * @param positive true to return the next highest + * double, false otherwise. + * + * @return The next highest or lowest double value. + */ + public static 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); + } + + /** + * I'm not sure what this method is really supposed to do, as it is + * not documented. + */ + public Number parse (String sourceStr, ParsePosition pos) + { + int index = pos.getIndex(); + for (int i = 0; i < choiceLimits.length; ++i) + { + if (sourceStr.startsWith(choiceFormats[i], index)) + { + pos.setIndex(index + choiceFormats[i].length()); + return new Double (choiceLimits[i]); + } + } + pos.setErrorIndex(index); + return new Double (Double.NaN); + } + + /** + * This method returns the highest possible double less than the + * specified double. If the specified double value is equal to + * Double.NaN then that is the value returned. + * + * @param d The specified double + * + * @return The highest double value less than the specified double. + */ + public static final double previousDouble (double d) + { + return nextDouble (d, false); + } + + /** + * This method sets new range terminators and format strings for this + * object. + * + * @param choiceLimits The new range terminators + * @param choiceFormats The new choice formats + */ + public void setChoices (double[] choiceLimits, String[] choiceFormats) + { + if (choiceLimits == null || choiceFormats == null) + throw new NullPointerException (); + if (choiceLimits.length != choiceFormats.length) + throw new IllegalArgumentException (); + this.choiceFormats = (String[]) choiceFormats.clone(); + this.choiceLimits = (double[]) choiceLimits.clone(); + } + + private 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); + } + } + + /** + * This method returns the range terminator list and format string list + * as a String suitable for using with the + * applyPattern method. + * + * @return A pattern string for this object + */ + public String toPattern () + { + StringBuffer result = new StringBuffer (); + for (int i = 0; i < choiceLimits.length; ++i) + { + result.append(choiceLimits[i]); + result.append('#'); + quoteString (result, choiceFormats[i]); + } + return result.toString(); + } + + /** + * This is the list of format strings. Note that this variable is + * specified by the serialization spec of this class. + */ + private String[] choiceFormats; + + /** + * This is the list of range terminator values. Note that this variable is + * specified by the serialization spec of this class. + */ + private double[] choiceLimits; + + // 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; + + private static final long serialVersionUID = 1795184449645032964L; +} diff --git a/libjava/classpath/java/text/CollationElementIterator.java b/libjava/classpath/java/text/CollationElementIterator.java new file mode 100644 index 00000000000..60b148ef7c1 --- /dev/null +++ b/libjava/classpath/java/text/CollationElementIterator.java @@ -0,0 +1,467 @@ +/* CollationElementIterator.java -- Walks through collation elements + Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.util.ArrayList; + +/* 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 JDK 1.1. + */ + +/** + * This class walks through the character collation elements of a + * String as defined by the collation rules in an instance of + * RuleBasedCollator. There is no public constructor for + * this class. An instance is created by calling the + * getCollationElementIterator method on + * RuleBasedCollator. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Tom Tromey (tromey@cygnus.com) + * @author Guilhem Lavaux (guilhem.lavaux@free.fr) + */ +public final class CollationElementIterator +{ + /** + * This is a constant value that is returned to indicate that the end of + * the string was encountered. + */ + public static final int NULLORDER = -1; + + /** + * This is the RuleBasedCollator this object was created from. + */ + RuleBasedCollator collator; + + /** + * This is the String that is being iterated over. + */ + String text; + + /** + * This is the index into the collation decomposition where we are currently scanning. + */ + int index; + + /** + * This is the index into the String where we are currently scanning. + */ + int textIndex; + + /** + * Array containing the collation decomposition of the + * text given to the constructor. + */ + private RuleBasedCollator.CollationElement[] text_decomposition; + + /** + * Array containing the index of the specified block. + */ + private int[] text_indexes; + + /** + * This method initializes a new instance of CollationElementIterator + * to iterate over the specified String using the rules in the + * specified RuleBasedCollator. + * + * @param collator The RuleBasedCollation used for calculating collation values + * @param text The String to iterate over. + */ + CollationElementIterator(RuleBasedCollator collator, String text) + { + this.collator = collator; + + setText (text); + } + + RuleBasedCollator.CollationElement nextBlock() + { + if (index >= text_decomposition.length) + return null; + + RuleBasedCollator.CollationElement e = text_decomposition[index]; + + textIndex = text_indexes[index+1]; + + index++; + + return e; + } + + RuleBasedCollator.CollationElement previousBlock() + { + if (index == 0) + return null; + + index--; + RuleBasedCollator.CollationElement e = text_decomposition[index]; + + textIndex = text_indexes[index+1]; + + return e; + } + + /** + * This method returns the collation ordering value of the next character sequence + * in the string (it may be an extended character following collation rules). + * This method will return NULLORDER if the + * end of the string was reached. + * + * @return The collation ordering value. + */ + public int next() + { + RuleBasedCollator.CollationElement e = nextBlock(); + + if (e == null) + return NULLORDER; + + return e.getValue(); + } + + /** + * This method returns the collation ordering value of the previous character + * in the string. This method will return NULLORDER if the + * beginning of the string was reached. + * + * @return The collation ordering value. + */ + public int previous() + { + RuleBasedCollator.CollationElement e = previousBlock(); + + if (e == null) + return NULLORDER; + + return e.getValue(); + } + + /** + * This method returns the primary order value for the given collation + * value. + * + * @param value The collation value returned from next() or previous(). + * + * @return The primary order value of the specified collation value. This is the high 16 bits. + */ + public static int primaryOrder(int order) + { + // From the JDK 1.2 spec. + return order >>> 16; + } + + /** + * This method resets the internal position pointer to read from the + * beginning of the String again. + */ + public void reset() + { + index = 0; + textIndex = 0; + } + + /** + * This method returns the secondary order value for the given collation + * value. + * + * @param value The collation value returned from next() or previous(). + * + * @return The secondary order value of the specified collation value. This is the bits 8-15. + */ + public static short secondaryOrder(int order) + { + // From the JDK 1.2 spec. + return (short) ((order >>> 8) & 255); + } + + /** + * This method returns the tertiary order value for the given collation + * value. + * + * @param value The collation value returned from next() or previous(). + * + * @return The tertiary order value of the specified collation value. This is the low eight bits. + */ + public static short tertiaryOrder(int order) + { + // From the JDK 1.2 spec. + return (short) (order & 255); + } + + /** + * This method sets the String that it is iterating over + * to the specified String. + * + * @param text The new String to iterate over. + * + * @since 1.2 + */ + public void setText(String text) + { + int idx = 0; + int idx_idx = 0; + int alreadyExpanded = 0; + int idxToMove = 0; + + this.text = text; + this.index = 0; + + String work_text = text.intern(); + + ArrayList a_element = new ArrayList(); + ArrayList a_idx = new ArrayList(); + + // Build element collection ordered as they come in "text". + while (idx < work_text.length()) + { + String key, key_old; + + Object object = null; + int p = 1; + + // IMPROVE: use a TreeMap with a prefix-ordering rule. + key_old = key = null; + do + { + if (object != null) + key_old = key; + key = work_text.substring (idx, idx+p); + object = collator.prefix_tree.get (key); + if (object != null && idx < alreadyExpanded) + { + RuleBasedCollator.CollationElement prefix = (RuleBasedCollator.CollationElement)object; + if (prefix.expansion != null && + prefix.expansion.startsWith(work_text.substring(0, idx))) + { + object = null; + key = key_old; + } + } + p++; + } + while (idx+p <= work_text.length()); + + if (object == null) + key = key_old; + + RuleBasedCollator.CollationElement prefix = + (RuleBasedCollator.CollationElement) collator.prefix_tree.get (key); + + /* + * First case: There is no such sequence in the database. + * We will have to build one from the context. + */ + if (prefix == null) + { + /* + * We are dealing with sequences in an expansion. They + * are treated as accented characters (tertiary order). + */ + if (alreadyExpanded > 0) + { + RuleBasedCollator.CollationElement e = + collator.getDefaultAccentedElement (work_text.charAt (idx)); + + a_element.add (e); + a_idx.add (new Integer(idx_idx)); + idx++; + alreadyExpanded--; + if (alreadyExpanded == 0) + { + /* There is not any characters left in the expansion set. + * We can increase the pointer in the source string. + */ + idx_idx += idxToMove; + idxToMove = 0; + } + else + idx_idx++; + } + else + { + /* This is a normal character. */ + RuleBasedCollator.CollationElement e = + collator.getDefaultElement (work_text.charAt (idx)); + Integer i_ref = new Integer(idx_idx); + + /* Don't forget to mark it as a special sequence so the + * string can be ordered. + */ + a_element.add (RuleBasedCollator.SPECIAL_UNKNOWN_SEQ); + a_idx.add (i_ref); + a_element.add (e); + a_idx.add (i_ref); + idx_idx++; + idx++; + } + continue; + } + + /* + * Second case: Here we have found a matching sequence. + * Here we have an expansion string prepend it to the "work text" and + * add the corresponding sorting element. We must also mark + */ + if (prefix.expansion != null) + { + work_text = prefix.expansion + + work_text.substring (idx+prefix.key.length()); + idx = 0; + a_element.add (prefix); + a_idx.add (new Integer(idx_idx)); + if (alreadyExpanded == 0) + idxToMove = prefix.key.length(); + alreadyExpanded += prefix.expansion.length()-prefix.key.length(); + } + else + { + /* Third case: the simplest. We have got the prefix and it + * has not to be expanded. + */ + a_element.add (prefix); + a_idx.add (new Integer(idx_idx)); + idx += prefix.key.length(); + /* If the sequence is in an expansion, we must decrease the + * counter. + */ + if (alreadyExpanded > 0) + { + alreadyExpanded -= prefix.key.length(); + if (alreadyExpanded == 0) + { + idx_idx += idxToMove; + idxToMove = 0; + } + } + else + idx_idx += prefix.key.length(); + } + } + + text_decomposition = (RuleBasedCollator.CollationElement[]) + a_element.toArray(new RuleBasedCollator.CollationElement[a_element.size()]); + text_indexes = new int[a_idx.size()+1]; + for (int i = 0; i < a_idx.size(); i++) + { + text_indexes[i] = ((Integer)a_idx.get(i)).intValue(); + } + text_indexes[a_idx.size()] = text.length(); + } + + /** + * This method sets the String that it is iterating over + * to the String represented by the specified + * CharacterIterator. + * + * @param source The CharacterIterator containing the new + * String to iterate over. + */ + public void setText(CharacterIterator source) + { + StringBuffer expand = new StringBuffer(); + + // For now assume we read from the beginning of the string. + for (char c = source.first(); + c != CharacterIterator.DONE; + c = source.next()) + expand.append(c); + + setText(expand.toString()); + } + + /** + * This method returns the current offset into the String + * that is being iterated over. + * + * @return The iteration index position. + * + * @since 1.2 + */ + public int getOffset() + { + return textIndex; + } + + /** + * This method sets the iteration index position into the current + * String to the specified value. This value must not + * be negative and must not be greater than the last index position + * in the String. + * + * @param offset The new iteration index position. + * + * @exception IllegalArgumentException If the new offset is not valid. + */ + public void setOffset(int offset) + { + if (offset < 0) + throw new IllegalArgumentException("Negative offset: " + offset); + + if (offset > (text.length() - 1)) + throw new IllegalArgumentException("Offset too large: " + offset); + + for (index = 0; index < text_decomposition.length; index++) + { + if (offset <= text_indexes[index]) + break; + } + /* + * As text_indexes[0] == 0, we should not have to take care whether index is + * greater than 0. It is always. + */ + if (text_indexes[index] == offset) + textIndex = offset; + else + textIndex = text_indexes[index-1]; + } + + /** + * This method returns the maximum length of any expansion sequence that + * ends with the specified collation order value. (Whatever that means). + * + * @param value The collation order value + * + * @param The maximum length of an expansion sequence. + */ + public int getMaxExpansion(int value) + { + return 1; + } +} diff --git a/libjava/classpath/java/text/CollationKey.java b/libjava/classpath/java/text/CollationKey.java new file mode 100644 index 00000000000..f7e3a2476de --- /dev/null +++ b/libjava/classpath/java/text/CollationKey.java @@ -0,0 +1,199 @@ +/* CollationKey.java -- Precomputed collation value + Copyright (C) 1998, 1999, 2000, 2003, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +/* 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. + */ + +/** + * This class represents a pre-computed series of bits representing a + * String for under a particular Collator. This + * value may be compared bitwise against another CollationKey + * representing a different String under the same + * Collator in a manner than is usually more efficient than + * using the raw Collator compare methods. There is overhead + * associated with calculating this value, so it is generally not + * advisable to compute CollationKey's unless multiple + * comparisons against a String will be done. (For example, + * in a sort routine). + *

+ * This class cannot be instantiated directly. Instead, a + * CollationKey is created by calling the + * getCollationKey method on an instance of Collator. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Tom Tromey (tromey@cygnus.com) + * @date March 25, 1999 + */ +public final class CollationKey implements Comparable +{ + /** + * This is the Collator this object was created from. + */ + private Collator collator; + + /** + * This is the String this object represents. + */ + private String originalText; + + /** + * This is the bit value for this key. + */ + private byte[] key; + + CollationKey (Collator collator, String originalText, byte[] key) + { + this.collator = collator; + this.originalText = originalText; + this.key = key; + } + + /** + * This method compares the specified object to this one. An integer is + * returned which indicates whether the specified object is less than, + * greater than, or equal to this object. + * + * @param ck The CollationKey to compare against this one. + * + * @return A negative integer if this object is less than the specified object, 0 if it is equal or a positive integer if it is greater than the specified object. + */ + public int compareTo (CollationKey ck) + { + int max = Math.min (key.length, ck.key.length); + + for (int i = 0; i < max; ++i) + { + if (key[i] != ck.key[i]) + return key[i] - ck.key[i]; + } + + return key.length - ck.key.length; + } + + /** + * This method compares the specified object to this one. The specified + * object must be an instance of CollationKey or an exception + * will be thrown. An integer is returned which indicates whether the + * specified object is less than, greater than, or equal to this object. + * + * @param obj The Object to compare against this one. + * + * @return A negative integer if this object is less than the specified object, 0 if it is equal or a positive integer if it is greater than the specified object. + */ + public int compareTo (Object obj) + { + return compareTo ((CollationKey) obj); + } + + /** + * This method tests the specified Object for equality with + * this object. This will be true if and only if: + *

+ *

+ * + * @param obj The Object to test for equality. + * + * @return true if the specified object is equal to this one, false otherwise. + */ + public boolean equals (Object obj) + { + if (! (obj instanceof CollationKey)) + return false; + + CollationKey ck = (CollationKey) obj; + + if (ck.collator != collator) + return false; + + if (!ck.getSourceString ().equals (getSourceString ())) + return false; + + if (!ck.toByteArray ().equals (toByteArray ())) + return false; + + return true; + } + + /** + * This method returns the String that this object was created + * from. + * + * @return The source String for this object. + */ + public String getSourceString() + { + return originalText; + } + + /** + * This method returns a hash value for this object. The hash value + * returned will be the hash code of the bit key so that identical bit + * keys will return the same value. + * + * @return A hash value for this object. + */ + public int hashCode() + { + // We just follow BitSet instead of thinking up something new. + long h = originalText.hashCode(); + for (int i = key.length - 1; i >= 0; --i) + h ^= key[i] * (i + 1); + return (int) ((h >> 32) ^ h); + } + + /** + * This method returns the collation bit sequence as a byte array. + * + * @param A byte array containing the collation bit sequence. + */ + public byte[] toByteArray() + { + return key; + } +} diff --git a/libjava/classpath/java/text/Collator.java b/libjava/classpath/java/text/Collator.java new file mode 100644 index 00000000000..633bc672527 --- /dev/null +++ b/libjava/classpath/java/text/Collator.java @@ -0,0 +1,400 @@ +/* Collator.java -- Perform locale dependent String comparisons. + Copyright (C) 1998, 1999, 2000, 2001, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.util.Comparator; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * This class is the abstract superclass of classes which perform + * locale dependent String comparisons. A caller requests + * an instance of Collator for a particular locale using + * the getInstance() static method in this class. That method + * will return a locale specific subclass of Collator which + * can be used to perform String comparisons for that locale. + * If a subclass of Collator cannot be located for a particular + * locale, a default instance for the current locale will be returned. + * + * In addition to setting the correct locale, there are two additional + * settings that can be adjusted to affect String comparisons: + * strength and decomposition. The strength value determines the level + * of signficance of character differences required for them to sort + * differently. (For example, whether or not capital letters are considered + * different from lower case letters). The decomposition value affects how + * variants of the same character are treated for sorting purposes. (For + * example, whether or not an accent is signficant or not). These settings + * are described in detail in the documentation for the methods and values + * that are related to them. + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Aaron M. Renn (arenn@urbanophile.com) + * @date March 18, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 from http://www.javasoft.com. + * Status: Mostly complete, but parts stubbed out. Look for FIXME. + */ +public abstract class Collator implements Comparator, Cloneable +{ + /** + * This constant is a strength value which indicates that only primary + * differences between characters will be considered signficant. As an + * example, two completely different English letters such as 'a' and 'b' + * are considered to have a primary difference. + */ + public static final int PRIMARY = 0; + + /** + * This constant is a strength value which indicates that only secondary + * or primary differences between characters will be considered + * significant. An example of a secondary difference between characters + * are instances of the same letter with different accented forms. + */ + public static final int SECONDARY = 1; + + /** + * This constant is a strength value which indicates that tertiary, + * secondary, and primary differences will be considered during sorting. + * An example of a tertiary difference is capitalization of a given letter. + * This is the default value for the strength setting. + */ + public static final int TERTIARY = 2; + + /** + * This constant is a strength value which indicates that any difference + * at all between character values are considered significant. + */ + public static final int IDENTICAL = 3; + + /** + * This constant indicates that accented characters won't be decomposed + * when performing comparisons. This will yield the fastest results, but + * will only work correctly in call cases for languages which do not + * use accents such as English. + */ + public static final int NO_DECOMPOSITION = 0; + + /** + * This constant indicates that only characters which are canonical variants + * in Unicode 2.0 will be decomposed prior to performing comparisons. This + * will cause accented languages to be sorted correctly. This is the + * default decomposition value. + */ + public static final int CANONICAL_DECOMPOSITION = 1; + + /** + * This constant indicates that both canonical variants and compatibility + * variants in Unicode 2.0 will be decomposed prior to performing + * comparisons. This is the slowest mode, but is required to get the + * correct sorting for certain languages with certain special formats. + */ + public static final int FULL_DECOMPOSITION = 2; + + /** + * This method initializes a new instance of Collator to have + * the default strength (TERTIARY) and decomposition + * (CANONICAL_DECOMPOSITION) settings. This constructor is protected and + * is for use by subclasses only. Non-subclass callers should use the + * static getInstance() methods of this class to instantiate + * Collation objects for the desired locale. + */ + protected Collator () + { + strength = TERTIARY; + decmp = CANONICAL_DECOMPOSITION; + } + + /** + * This method compares the two String's and returns an + * integer indicating whether or not the first argument is less than, + * equal to, or greater than the second argument. The comparison is + * performed according to the rules of the locale for this + * Collator and the strength and decomposition rules in + * effect. + * + * @param str1 The first object to compare + * @param str2 The second object to compare + * + * @return A negative integer if str1 < str2, 0 if str1 == str2, or + * a positive integer if str1 > str2. + */ + public abstract int compare (String source, String target); + + /** + * This method compares the two Object's and returns an + * integer indicating whether or not the first argument is less than, + * equal to, or greater than the second argument. These two objects + * must be String's or an exception will be thrown. + * + * @param obj1 The first object to compare + * @param obj2 The second object to compare + * + * @return A negative integer if obj1 < obj2, 0 if obj1 == obj2, or + * a positive integer if obj1 > obj2. + * + * @exception ClassCastException If the arguments are not instances + * of String. + */ + public int compare (Object o1, Object o2) + { + return compare ((String) o1, (String) o2); + } + + /** + * This method tests the specified object for equality against this + * object. This will be true if and only if the following conditions are + * met: + * + * + * @param obj The Object to test for equality against + * this object. + * + * @return true if the specified object is equal to + * this one, false otherwise. + */ + public boolean equals (Object obj) + { + if (! (obj instanceof Collator)) + return false; + Collator c = (Collator) obj; + return decmp == c.decmp && strength == c.strength; + } + + /** + * This method tests whether the specified String's are equal + * according to the collation rules for the locale of this object and + * the current strength and decomposition settings. + * + * @param str1 The first String to compare + * @param str2 The second String to compare + * + * @return true if the two strings are equal, + * false otherwise. + */ + public boolean equals (String source, String target) + { + return compare (source, target) == 0; + } + + /** + * This method returns a copy of this Collator object. + * + * @return A duplicate of this object. + */ + public Object clone () + { + try + { + return super.clone (); + } + catch (CloneNotSupportedException _) + { + return null; + } + } + + /** + * This method returns an array of Locale objects which is + * the list of locales for which Collator objects exist. + * + * @return The list of locales for which Collator's exist. + */ + public static synchronized Locale[] getAvailableLocales () + { + // FIXME + Locale[] l = new Locale[1]; + l[0] = Locale.US; + return l; + } + + /** + * This method transforms the specified String into a + * CollationKey for faster comparisons. This is useful when + * comparisons against a string might be performed multiple times, such + * as during a sort operation. + * + * @param str The String to convert. + * + * @return A CollationKey for the specified String. + */ + public abstract CollationKey getCollationKey (String source); + + /** + * This method returns the current decomposition setting for this + * object. This * will be one of NO_DECOMPOSITION, + * CANONICAL_DECOMPOSITION, or * FULL_DECOMPOSITION. See the + * documentation for those constants for an * explanation of this + * setting. + * + * @return The current decomposition setting. + */ + public synchronized int getDecomposition () + { + return decmp; + } + + /** + * This method returns an instance of Collator for the + * default locale. + * + * @return A Collator for the default locale. + */ + public static Collator getInstance () + { + return getInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of Collator for the + * specified locale. If no Collator exists for the desired + * locale, a Collator for the default locale will be returned. + * + * @param locale The desired localed to load a Collator for. + * + * @return A Collator for the requested locale + */ + public static Collator getInstance (Locale loc) + { + ResourceBundle res; + String pattern; + try + { + res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + loc, ClassLoader.getSystemClassLoader()); + pattern = res.getString("collation_rules"); + } + catch (MissingResourceException x) + { + pattern = "<0<1<2<3<4<5<6<7<8<9= allFields.length || calendarField < 0) + throw new IllegalArgumentException("no such calendar field (" + + calendarField + ")"); + + return allFields[calendarField]; + } + + protected Object readResolve() throws InvalidObjectException + { + String s = getName(); + + for (int i=0;iDateFormat. + */ + protected DateFormat () + { + } + + /** + * This method tests this object for equality against the specified object. + * The two objects will be considered equal if an only if the specified + * object: + *

+ *

    + *
  • Is not null.
  • + *
  • Is an instance of DateFormat.
  • + *
  • Has the same numberFormat field value as this object.
  • + *
+ * + * @param obj The object to test for equality against. + * + * @return true if the specified object is equal to this object, + * false otherwise. + */ + public boolean equals (Object obj) + { + if (!(obj instanceof DateFormat)) + return false; + + DateFormat d = (DateFormat) obj; + + return numberFormat.equals(d.numberFormat); + } + + /** + * This method returns a copy of this object. + * + * @return A copy of this object. + */ + public Object clone () + { + // We know the superclass just call's Object's generic cloner. + return super.clone (); + } + + /** + * This method formats the specified Object into a date string + * and appends it to the specified StringBuffer. + * The specified object must be an instance of Number or + * Date or an IllegalArgumentException will be + * thrown. + * + * @param obj The Object to format. + * @param toAppendTo The StringBuffer to append the resultant + * String to. + * @param fieldPosition Is updated to the start and end index of the + * specified field. + * + * @return The StringBuffer supplied on input, with the + * formatted date/time appended. + */ + public final StringBuffer format (Object obj, + StringBuffer buf, FieldPosition pos) + { + if (obj instanceof Number) + obj = new Date(((Number) obj).longValue()); + else if (! (obj instanceof Date)) + throw new IllegalArgumentException + ("Cannot format given Object as a Date"); + + return format ((Date) obj, buf, pos); + } + + /** + * Formats the date argument according to the pattern specified. + * + * @param date The formatted date. + */ + public final String format (Date date) + { + StringBuffer sb = new StringBuffer (); + format (date, sb, new FieldPosition (MONTH_FIELD)); + return sb.toString(); + } + + /** + * This method formats a Date into a string and appends it + * to the specified StringBuffer. + * + * @param date The Date value to format. + * @param toAppendTo The StringBuffer to append the resultant + * String to. + * @param fieldPosition Is updated to the start and end index of the + * specified field. + * + * @return The StringBuffer supplied on input, with the + * formatted date/time appended. + */ + public abstract StringBuffer format (Date date, + StringBuffer buf, FieldPosition pos); + + /** + * This method returns a list of available locales supported by this + * class. + */ + public static Locale[] getAvailableLocales() + { + return Locale.getAvailableLocales(); + } + + /** + * This method returns the Calendar object being used by + * this object to parse/format datetimes. + * + * @return The Calendar being used by this object. + * + * @see java.util.Calendar + */ + public Calendar getCalendar () + { + return calendar; + } + + private static DateFormat computeInstance (int style, Locale loc, + boolean use_date, boolean use_time) + { + return computeInstance (style, style, loc, use_date, use_time); + } + + private static DateFormat computeInstance (int dateStyle, int timeStyle, + Locale loc, boolean use_date, + boolean use_time) + { + ResourceBundle res; + try + { + res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + loc, ClassLoader.getSystemClassLoader()); + } + catch (MissingResourceException x) + { + res = null; + } + + String pattern = null; + if (use_date) + { + String name, def; + switch (dateStyle) + { + 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 (timeStyle) + { + 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); + } + + /** + * This method returns an instance of DateFormat that will + * format using the default formatting style for dates. + * + * @return A new DateFormat instance. + */ + public static final DateFormat getDateInstance () + { + return getDateInstance (DEFAULT, Locale.getDefault()); + } + + /** + * This method returns an instance of DateFormat that will + * format using the specified formatting style for dates. + * + * @param style The type of formatting to perform. + * + * @return A new DateFormat instance. + */ + public static final DateFormat getDateInstance (int style) + { + return getDateInstance (style, Locale.getDefault()); + } + + /** + * This method returns an instance of DateFormat that will + * format using the specified formatting style for dates. The specified + * localed will be used in place of the default. + * + * @param style The type of formatting to perform. + * @param aLocale The desired locale. + * + * @return A new DateFormat instance. + */ + public static final DateFormat getDateInstance (int style, Locale loc) + { + return computeInstance (style, loc, true, false); + } + + /** + * This method returns a new instance of DateFormat that + * formats both dates and times using the SHORT style. + * + * @return A new DateFormatinstance. + */ + public static final DateFormat getDateTimeInstance () + { + return getDateTimeInstance (DEFAULT, DEFAULT, Locale.getDefault()); + } + + /** + * This method returns a new instance of DateFormat that + * formats both dates and times using the DEFAULT style. + * + * @return A new DateFormatinstance. + */ + public static final DateFormat getDateTimeInstance (int dateStyle, + int timeStyle) + { + return getDateTimeInstance (dateStyle, timeStyle, Locale.getDefault()); + } + + /** + * This method returns a new instance of DateFormat that + * formats both dates and times using the specified styles. + * + * @param dateStyle The desired style for date formatting. + * @param timeStyle The desired style for time formatting + * + * @return A new DateFormatinstance. + */ + public static final DateFormat getDateTimeInstance (int dateStyle, + int timeStyle, + Locale loc) + { + return computeInstance (dateStyle, timeStyle, loc, true, true); + } + + /** + * This method returns a new instance of DateFormat that + * formats both dates and times using the SHORT style. + * + * @return A new DateFormatinstance. + */ + public static final DateFormat getInstance () + { + // JCL book says SHORT. + return getDateTimeInstance (SHORT, SHORT, Locale.getDefault()); + } + + /** + * This method returns the NumberFormat object being used + * by this object to parse/format time values. + * + * @return The NumberFormat in use by this object. + */ + public NumberFormat getNumberFormat () + { + return numberFormat; + } + + /** + * This method returns an instance of DateFormat that will + * format using the default formatting style for times. + * + * @return A new DateFormat instance. + */ + public static final DateFormat getTimeInstance () + { + return getTimeInstance (DEFAULT, Locale.getDefault()); + } + + /** + * This method returns an instance of DateFormat that will + * format using the specified formatting style for times. + * + * @param style The type of formatting to perform. + * + * @return A new DateFormat instance. + */ + public static final DateFormat getTimeInstance (int style) + { + return getTimeInstance (style, Locale.getDefault()); + } + + /** + * This method returns an instance of DateFormat that will + * format using the specified formatting style for times. The specified + * localed will be used in place of the default. + * + * @param style The type of formatting to perform. + * @param aLocale The desired locale. + * + * @return A new DateFormat instance. + */ + public static final DateFormat getTimeInstance (int style, Locale loc) + { + return computeInstance (style, loc, false, true); + } + + /** + * This method returns the TimeZone object being used by + * this instance. + * + * @return The time zone in use. + */ + public TimeZone getTimeZone () + { + return calendar.getTimeZone(); + } + + /** + * This method returns a hash value for this object. + * + * @return A hash value for this object. + */ + public int hashCode () + { + if (numberFormat != null) + return numberFormat.hashCode(); + else + return 0; + } + + /** + * This method indicates whether or not the parsing of date and time + * values should be done in a lenient value. + * + * @return true if date/time parsing is lenient, + * false otherwise. + */ + public boolean isLenient () + { + return calendar.isLenient(); + } + + /** + * This method parses the specified date/time string. + * + * @param source The string to parse. + * @return The resultant date. + * + * @exception ParseException If the specified string cannot be parsed. + */ + 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 in \"" + + source + '\"', index); + } + return result; + } + + /** + * This method parses the specified String into a + * Date. The pos argument contains the + * starting parse position on method entry and the ending parse + * position on method exit. + * + * @param text The string to parse. + * @param pos The starting parse position in entry, the ending parse + * position on exit. + * + * @return The parsed date, or null if the string cannot + * be parsed. + */ + public abstract Date parse (String source, ParsePosition pos); + + /** + * This method is identical to parse(String, ParsePosition), + * but returns its result as an Object instead of a + * Date. + * + * @param source The string to parse. + * @param pos The starting parse position in entry, the ending parse + * position on exit. + * + * @return The parsed date, or null if the string cannot + * be parsed. + */ + public Object parseObject (String source, ParsePosition pos) + { + return parse(source, pos); + } + + /** + * This method specified the Calendar that should be used + * by this object to parse/format datetimes. + * + * @param The new Calendar for this object. + * + * @see java.util.Calendar + */ + public void setCalendar (Calendar calendar) + { + this.calendar = calendar; + } + + /** + * This method specifies whether or not this object should be lenient in + * the syntax it accepts while parsing date/time values. + * + * @param lenient true if parsing should be lenient, + * false otherwise. + */ + public void setLenient (boolean lenient) + { + calendar.setLenient(lenient); + } + + /** + * This method specifies the NumberFormat object that should + * be used by this object to parse/format times. + * + * @param The NumberFormat in use by this object. + */ + public void setNumberFormat (NumberFormat numberFormat) + { + this.numberFormat = numberFormat; + } + + /** + * This method sets the time zone that should be used by this object. + * + * @param The new time zone. + */ + public void setTimeZone (TimeZone timeZone) + { + calendar.setTimeZone(timeZone); + } +} diff --git a/libjava/classpath/java/text/DateFormatSymbols.java b/libjava/classpath/java/text/DateFormatSymbols.java new file mode 100644 index 00000000000..543a5c13a78 --- /dev/null +++ b/libjava/classpath/java/text/DateFormatSymbols.java @@ -0,0 +1,525 @@ +/* DateFormatSymbols.java -- Format over a range of numbers + Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.StringTokenizer; + +/** + * This class acts as container for locale specific date/time formatting + * information such as the days of the week and the months of the year. + * @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 implements java.io.Serializable, Cloneable +{ + String[] ampms; + String[] eras; + private String localPatternChars; + String[] months; + String[] shortMonths; + String[] shortWeekdays; + String[] weekdays; + private String[][] zoneStrings; + + private static final long serialVersionUID = -5987973545549424702L; + + // The order of these prefixes must be the same as in DateFormat + private static final String[] formatPrefixes = + { + "full", "long", "medium", "short" + }; + + // These are each arrays with a value for SHORT, MEDIUM, LONG, FULL, + // and DEFAULT (constants defined in java.text.DateFormat). While + // not part of the official spec, we need a way to get at locale-specific + // default formatting patterns. They are declared package scope so + // as to be easily accessible where needed (DateFormat, SimpleDateFormat). + transient String[] dateFormats; + transient String[] timeFormats; + + private static String[] getStringArray(ResourceBundle res, String name) + { + return res.getString(name).split("\u00ae"); + } + + private String[][] getZoneStrings(ResourceBundle res) + { + try + { + int index = 0; + String data = res.getString("zoneStrings"); + String[] zones = data.split("\u00a9"); + String[][] array = new String[zones.length][]; + for (int a = 0; a < zones.length; ++a) + array[a] = zones[a].split("\u00ae"); + return array; + } + catch (MissingResourceException e) + { + return new String[0][]; + } + } + + private String[] formatsForKey(ResourceBundle res, String key) + { + String[] values = new String[formatPrefixes.length]; + + for (int i = 0; i < formatPrefixes.length; i++) + values[i] = res.getString(formatPrefixes[i] + key); + + return values; + } + + /** + * This method initializes a new instance of DateFormatSymbols + * by loading the date format information for the specified locale. + * + * @param locale The locale for which date formatting symbols should + * be loaded. + */ + public DateFormatSymbols (Locale locale) throws MissingResourceException + { + ResourceBundle res + = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", locale, + ClassLoader.getSystemClassLoader()); + + ampms = getStringArray(res, "ampms"); + eras = getStringArray(res, "eras"); + localPatternChars = res.getString("localPatternChars"); + months = getStringArray(res, "months"); + shortMonths = getStringArray(res, "shortMonths"); + shortWeekdays = getStringArray(res, "shortWeekdays"); + weekdays = getStringArray(res, "weekdays"); + zoneStrings = getZoneStrings(res); + dateFormats = formatsForKey(res, "DateFormat"); + timeFormats = formatsForKey(res, "TimeFormat"); + } + + /** + * This method loads the format symbol information for the default + * locale. + */ + public DateFormatSymbols () throws MissingResourceException + { + this (Locale.getDefault()); + } + + /** + * This method returns the list of strings used for displaying AM or PM. + * This is a two element String array indexed by + * Calendar.AM and Calendar.PM + * + * @return The list of AM/PM display strings. + */ + public String[] getAmPmStrings() + { + return ampms; + } + + /** + * This method returns the list of strings used for displaying eras + * (e.g., "BC" and "AD"). This is a two element String + * array indexed by Calendar.BC and Calendar.AD. + * + * @return The list of era disply strings. + */ + public String[] getEras() + { + return eras; + } + + /** + * This method returns the pattern character information for this + * object. This is an 18 character string that contains the characters + * that are used in creating the date formatting strings in + * SimpleDateFormat. The following are the character + * positions in the string and which format character they correspond + * to (the character in parentheses is the default value in the US English + * locale): + *

+ *

    + *
  • 0 - era (G)
  • + *
  • 1 - year (y)
  • + *
  • 2 - month (M)
  • + *
  • 3 - day of month (d)
  • + *
  • 4 - hour out of 12, from 1-12 (h)
  • + *
  • 5 - hour out of 24, from 0-23 (H)
  • + *
  • 6 - minute (m)
  • + *
  • 7 - second (s)
  • + *
  • 8 - millisecond (S)
  • + *
  • 9 - date of week (E)
  • + *
  • 10 - date of year (D)
  • + *
  • 11 - day of week in month, eg. "4th Thur in Nov" (F)
  • + *
  • 12 - week in year (w)
  • + *
  • 13 - week in month (W)
  • + *
  • 14 - am/pm (a)
  • + *
  • 15 - hour out of 24, from 1-24 (k)
  • + *
  • 16 - hour out of 12, from 0-11 (K)
  • + *
  • 17 - time zone (z)
  • + *
+ * + * @return The format patter characters + */ + public String getLocalPatternChars() + { + return localPatternChars; + } + + /** + * This method returns the list of strings used for displaying month + * names (e.g., "January" and "February"). This is a thirteen element + * string array indexed by Calendar.JANUARY through + * Calendar.UNDECEMBER. Note that there are thirteen + * elements because some calendars have thriteen months. + * + * @return The list of month display strings. + */ + public String[] getMonths () + { + return months; + } + + /** + * This method returns the list of strings used for displaying abbreviated + * month names (e.g., "Jan" and "Feb"). This is a thirteen element + * String array indexed by Calendar.JANUARY + * through Calendar.UNDECEMBER. Note that there are thirteen + * elements because some calendars have thirteen months. + * + * @return The list of abbreviated month display strings. + */ + public String[] getShortMonths () + { + return shortMonths; + } + + /** + * This method returns the list of strings used for displaying abbreviated + * weekday names (e.g., "Sun" and "Mon"). This is an eight element + * String array indexed by Calendar.SUNDAY + * through Calendar.SATURDAY. Note that the first element + * of this array is ignored. + * + * @return This list of abbreviated weekday display strings. + */ + public String[] getShortWeekdays () + { + return shortWeekdays; + } + + /** + * This method returns the list of strings used for displaying weekday + * names (e.g., "Sunday" and "Monday"). This is an eight element + * String array indexed by Calendar.SUNDAY + * through Calendar.SATURDAY. Note that the first element + * of this array is ignored. + * + * @return This list of weekday display strings. + */ + public String[] getWeekdays () + { + return weekdays; + } + + /** + * This method returns this list of localized timezone display strings. + * This is a two dimensional String array where each row in + * the array contains five values: + *

+ *

    + *
  • 0 - The non-localized time zone id string.
  • + *
  • 1 - The long name of the time zone (standard time).
  • + *
  • 2 - The short name of the time zone (standard time).
  • + *
  • 3 - The long name of the time zone (daylight savings time).
  • + *
  • 4 - the short name of the time zone (daylight savings time).
  • + *
+ * + * @return The list of time zone display strings. + */ + public String[] [] getZoneStrings () + { + return zoneStrings; + } + + /** + * This method sets the list of strings used to display AM/PM values to + * the specified list. + * This is a two element String array indexed by + * Calendar.AM and Calendar.PM + * + * @param ampms The new list of AM/PM display strings. + */ + public void setAmPmStrings (String[] value) + { + ampms = value; + } + + /** + * This method sets the list of strings used to display time eras to + * to the specified list. + * This is a two element String + * array indexed by Calendar.BC and Calendar.AD. + * + * @param eras The new list of era disply strings. + */ + public void setEras (String[] value) + { + eras = value; + } + + /** + * This method sets the list of characters used to specific date/time + * formatting strings. + * This is an 18 character string that contains the characters + * that are used in creating the date formatting strings in + * SimpleDateFormat. The following are the character + * positions in the string and which format character they correspond + * to (the character in parentheses is the default value in the US English + * locale): + *

+ *

    + *
  • 0 - era (G)
  • + *
  • 1 - year (y)
  • + *
  • 2 - month (M)
  • + *
  • 3 - day of month (d)
  • + *
  • 4 - hour out of 12, from 1-12 (h)
  • + *
  • 5 - hour out of 24, from 0-23 (H)
  • + *
  • 6 - minute (m)
  • + *
  • 7 - second (s)
  • + *
  • 8 - millisecond (S)
  • + *
  • 9 - date of week (E)
  • + *
  • 10 - date of year (D)
  • + *
  • 11 - day of week in month, eg. "4th Thur in Nov" (F)
  • + *
  • 12 - week in year (w)
  • + *
  • 13 - week in month (W)
  • + *
  • 14 - am/pm (a)
  • + *
  • 15 - hour out of 24, from 1-24 (k)
  • + *
  • 16 - hour out of 12, from 0-11 (K)
  • + *
  • 17 - time zone (z)
  • + *
+ * + * @param localPatternChars The new format patter characters + */ + public void setLocalPatternChars (String value) + { + localPatternChars = value; + } + + /** + * This method sets the list of strings used to display month names. + * This is a thirteen element + * string array indexed by Calendar.JANUARY through + * Calendar.UNDECEMBER. Note that there are thirteen + * elements because some calendars have thriteen months. + * + * @param months The list of month display strings. + */ + public void setMonths (String[] value) + { + months = value; + } + + /** + * This method sets the list of strings used to display abbreviated month + * names. + * This is a thirteen element + * String array indexed by Calendar.JANUARY + * through Calendar.UNDECEMBER. Note that there are thirteen + * elements because some calendars have thirteen months. + * + * @param shortMonths The new list of abbreviated month display strings. + */ + public void setShortMonths (String[] value) + { + shortMonths = value; + } + + /** + * This method sets the list of strings used to display abbreviated + * weekday names. + * This is an eight element + * String array indexed by Calendar.SUNDAY + * through Calendar.SATURDAY. Note that the first element + * of this array is ignored. + * + * @param shortWeekdays This list of abbreviated weekday display strings. + */ + public void setShortWeekdays (String[] value) + { + shortWeekdays = value; + } + + /** + * This method sets the list of strings used to display weekday names. + * This is an eight element + * String array indexed by Calendar.SUNDAY + * through Calendar.SATURDAY. Note that the first element + * of this array is ignored. + * + * @param weekdays This list of weekday display strings. + */ + public void setWeekdays (String[] value) + { + weekdays = value; + } + + /** + * This method sets the list of display strings for time zones. + * This is a two dimensional String array where each row in + * the array contains five values: + *

+ *

    + *
  • 0 - The non-localized time zone id string.
  • + *
  • 1 - The long name of the time zone (standard time).
  • + *
  • 2 - The short name of the time zone (standard time).
  • + *
  • 3 - The long name of the time zone (daylight savings time).
  • + *
  • 4 - the short name of the time zone (daylight savings time).
  • + *
+ * + * @return The list of time zone display strings. + */ + public void setZoneStrings (String[][] value) + { + zoneStrings = value; + } + + /* Does a "deep" equality test - recurses into arrays. */ + private 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; + } + + /** + * This method tests a specified object for equality against this object. + * This will be true if and only if the specified object: + *

+ *

    + *
  • Is not null.
  • + *
  • Is an instance of DateFormatSymbols.
  • + *
  • Contains identical formatting symbols to this object.
  • + *
+ * + * @param obj The Object to test for equality against. + * + * @return true if the specified object is equal to this one, + * false otherwise. + */ + public boolean equals (Object obj) + { + if (! (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)); + } + + /** + * Returns a new copy of this object. + * + * @param A copy of this object + */ + public Object clone () + { + try + { + return super.clone (); + } + catch (CloneNotSupportedException e) + { + return null; + } + } + + /** + * This method returns a hash value for this object. + * + * @return A hash value for this object. + */ + public int hashCode () + { + return (hashCode(ampms) + ^ hashCode(eras) + ^ hashCode(localPatternChars) + ^ hashCode(months) + ^ hashCode(shortMonths) + ^ hashCode(shortWeekdays) + ^ hashCode(weekdays) + ^ hashCode(zoneStrings)); + } +} diff --git a/libjava/classpath/java/text/DecimalFormat.java b/libjava/classpath/java/text/DecimalFormat.java new file mode 100644 index 00000000000..6dadb0ce333 --- /dev/null +++ b/libjava/classpath/java/text/DecimalFormat.java @@ -0,0 +1,1435 @@ +/* DecimalFormat.java -- Formats and parses numbers + Copyright (C) 1999, 2000, 2001, 2003, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.text; + +import gnu.java.text.AttributedFormatBuffer; +import gnu.java.text.FormatBuffer; +import gnu.java.text.FormatCharacterIterator; +import gnu.java.text.StringFormatBuffer; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.Currency; +import java.util.HashMap; +import java.util.Locale; + +/** + * @author Tom Tromey (tromey@cygnus.com) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @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. + * 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 int scanFix (String pattern, int index, FormatBuffer buf, + String patChars, DecimalFormatSymbols syms, + boolean is_suffix) + { + int len = pattern.length(); + boolean quoteStarted = false; + buf.clear(); + + boolean multiplierSet = false; + while (index < len) + { + char c = pattern.charAt(index); + + if (quoteStarted) + { + if (c == '\'') + quoteStarted = false; + else + buf.append(c); + index++; + continue; + } + + if (c == '\'' && index + 1 < len + && pattern.charAt(index + 1) == '\'') + { + buf.append(c); + index++; + } + else if (c == '\'') + { + quoteStarted = true; + } + else if (c == '\u00a4') + { + /* Currency interpreted later */ + buf.append(c); + } + else if (c == syms.getPercent()) + { + if (multiplierSet) + throw new IllegalArgumentException ("multiplier already set " + + "- index: " + index); + multiplierSet = true; + multiplier = 100; + buf.append(c, NumberFormat.Field.PERCENT); + } + else if (c == syms.getPerMill()) + { + if (multiplierSet) + throw new IllegalArgumentException ("multiplier already set " + + "- index: " + index); + multiplierSet = true; + multiplier = 1000; + buf.append(c, NumberFormat.Field.PERMILLE); + } + else if (patChars.indexOf(c) != -1) + { + // This is a pattern character. + break; + } + else + { + buf.append(c); + } + index++; + } + + if (quoteStarted) + throw new IllegalArgumentException ("pattern is lacking a closing quote"); + + return index; + } + + // A helper which reads a number format. + private int scanFormat (String pattern, int index, String patChars, + DecimalFormatSymbols syms, boolean is_positive) + { + 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 IllegalArgumentException ("digit mark following " + + "zero - index: " + 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 IllegalArgumentException ("zero mark " + + "following digit - index: " + index); + ++zeroCount; + } + else if (c == syms.getDigit()) + { + ++hashCount; + } + else if (c != syms.getExponential() + && c != syms.getPatternSeparator() + && c != syms.getPercent() + && c != syms.getPerMill() + && patChars.indexOf(c) != -1) + throw new IllegalArgumentException ("unexpected special " + + "character - index: " + 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 + IllegalArgumentException ("digit mark following zero " + + "in exponent - index: " + + index); + } + else if (patChars.indexOf(c) != -1) + throw new IllegalArgumentException ("unexpected special " + + "character - index: " + + index); + else + break; + + ++index; + } + + if (is_positive) + { + useExponentialNotation = true; + minExponentDigits = (byte) zeroCount; + } + + maximumIntegerDigits = groupingSize; + groupingSize = 0; + if (maximumIntegerDigits > minimumIntegerDigits && maximumIntegerDigits > 0) + { + minimumIntegerDigits = 1; + exponentRound = maximumIntegerDigits; + } + else + exponentRound = 1; + } + + return index; + } + + // This helper function creates a string consisting of all the + // characters which can appear in a pattern and must be quoted. + private 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 void applyPatternWithSymbols(String pattern, DecimalFormatSymbols syms) + { + // 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 = MAXIMUM_INTEGER_DIGITS; + minimumFractionDigits = 0; + minimumIntegerDigits = 1; + + AttributedFormatBuffer buf = new AttributedFormatBuffer (); + String patChars = patternChars (syms); + + int max = pattern.length(); + int index = scanFix (pattern, 0, buf, patChars, syms, false); + buf.sync(); + positivePrefix = buf.getBuffer().toString(); + positivePrefixRanges = buf.getRanges(); + positivePrefixAttrs = buf.getAttributes(); + + index = scanFormat (pattern, index, patChars, syms, true); + + index = scanFix (pattern, index, buf, patChars, syms, true); + buf.sync(); + positiveSuffix = buf.getBuffer().toString(); + positiveSuffixRanges = buf.getRanges(); + positiveSuffixAttrs = buf.getAttributes(); + + if (index == pattern.length()) + { + // No negative info. + negativePrefix = null; + negativeSuffix = null; + } + else + { + if (pattern.charAt(index) != syms.getPatternSeparator()) + throw new IllegalArgumentException ("separator character " + + "expected - index: " + index); + + index = scanFix (pattern, index + 1, buf, patChars, syms, false); + buf.sync(); + negativePrefix = buf.getBuffer().toString(); + negativePrefixRanges = buf.getRanges(); + negativePrefixAttrs = buf.getAttributes(); + + // 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); + buf.sync(); + negativeSuffix = buf.getBuffer().toString(); + negativeSuffixRanges = buf.getRanges(); + negativeSuffixAttrs = buf.getAttributes(); + + if (index != pattern.length()) + throw new IllegalArgumentException ("end of pattern expected " + + "- index: " + index); + } + } + + public void applyLocalizedPattern (String pattern) + { + // JCL p. 638 claims this throws a ParseException but p. 629 + // contradicts this. Empirical tests with patterns of "0,###.0" + // and "#.#.#" corroborate the p. 629 statement that an + // IllegalArgumentException is thrown. + applyPatternWithSymbols (pattern, symbols); + } + + public void applyPattern (String pattern) + { + // JCL p. 638 claims this throws a ParseException but p. 629 + // contradicts this. Empirical tests with patterns of "0,###.0" + // and "#.#.#" corroborate the p. 629 statement that an + // IllegalArgumentException is thrown. + applyPatternWithSymbols (pattern, nonLocalizedSymbols); + } + + public Object clone () + { + DecimalFormat c = (DecimalFormat) super.clone (); + c.symbols = (DecimalFormatSymbols) symbols.clone (); + return c; + } + + /** + * Constructs a DecimalFormat which uses the default + * pattern and symbols. + */ + public DecimalFormat () + { + this ("#,##0.###"); + } + + /** + * Constructs a DecimalFormat which uses the given + * pattern and the default symbols for formatting and parsing. + * + * @param pattern the non-localized pattern to use. + * @throws NullPointerException if any argument is null. + * @throws IllegalArgumentException if the pattern is invalid. + */ + public DecimalFormat (String pattern) + { + this (pattern, new DecimalFormatSymbols ()); + } + + /** + * Constructs a DecimalFormat using the given pattern + * and formatting symbols. This construction method is used to give + * complete control over the formatting process. + * + * @param pattern the non-localized pattern to use. + * @param symbols the set of symbols used for parsing and formatting. + * @throws NullPointerException if any argument is null. + * @throws IllegalArgumentException if the pattern is invalid. + */ + public DecimalFormat(String pattern, DecimalFormatSymbols symbols) + { + this.symbols = (DecimalFormatSymbols) symbols.clone(); + applyPattern(pattern); + } + + private boolean equals(String s1, String s2) + { + if (s1 == null || s2 == null) + return s1 == s2; + return s1.equals(s2); + } + + /** + * Tests this instance for equality with an arbitrary object. This method + * returns true if: + *
    + *
  • obj is not null;
  • + *
  • obj is an instance of DecimalFormat;
  • + *
  • this instance and obj have the same attributes;
  • + *
+ * + * @param obj the object (null permitted). + * + * @return A boolean. + */ + public boolean equals(Object obj) + { + if (! (obj instanceof DecimalFormat)) + return false; + DecimalFormat dup = (DecimalFormat) obj; + return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown + && groupingUsed == dup.groupingUsed + && groupingSize == dup.groupingSize + && multiplier == dup.multiplier + && useExponentialNotation == dup.useExponentialNotation + && minExponentDigits == dup.minExponentDigits + && minimumIntegerDigits == dup.minimumIntegerDigits + && maximumIntegerDigits == dup.maximumIntegerDigits + && minimumFractionDigits == dup.minimumFractionDigits + && maximumFractionDigits == dup.maximumFractionDigits + && equals(negativePrefix, dup.negativePrefix) + && equals(negativeSuffix, dup.negativeSuffix) + && equals(positivePrefix, dup.positivePrefix) + && equals(positiveSuffix, dup.positiveSuffix) + && symbols.equals(dup.symbols)); + } + + private void formatInternal (double number, FormatBuffer dest, + FieldPosition fieldPos) + { + // A very special case. + if (Double.isNaN(number)) + { + dest.append(symbols.getNaN()); + if (fieldPos != null && + (fieldPos.getField() == INTEGER_FIELD || + fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER)) + { + int index = dest.length(); + fieldPos.setBeginIndex(index - symbols.getNaN().length()); + fieldPos.setEndIndex(index); + } + return; + } + + boolean is_neg = number < 0; + if (is_neg) + { + if (negativePrefix != null) + { + dest.append(substituteCurrency(negativePrefix, number), + negativePrefixRanges, negativePrefixAttrs); + } + else + { + dest.append(symbols.getMinusSign(), NumberFormat.Field.SIGN); + dest.append(substituteCurrency(positivePrefix, number), + positivePrefixRanges, positivePrefixAttrs); + } + number = - number; + } + else + { + dest.append(substituteCurrency(positivePrefix, number), + positivePrefixRanges, positivePrefixAttrs); + } + int integerBeginIndex = dest.length(); + int integerEndIndex = 0; + int zeroStart = symbols.getZeroDigit() - '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.floor (Math.log(number) / Math.log(10)); + exponent = exponent - (exponent % exponentRound); + if (minimumIntegerDigits > 0) + exponent -= minimumIntegerDigits - 1; + baseNumber = (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); + String intPart = Long.toString((long)Math.floor(baseNumber)); + int count, groupPosition = intPart.length(); + + dest.setDefaultAttribute(NumberFormat.Field.INTEGER); + + for (count = 0; count < minimumIntegerDigits-intPart.length(); count++) + dest.append(symbols.getZeroDigit()); + + for (count = 0; + count < maximumIntegerDigits && count < intPart.length(); + count++) + { + int dig = intPart.charAt(count); + + // Append group separator if required. + if (groupingUsed && count > 0 && groupingSize != 0 && groupPosition % groupingSize == 0) + { + dest.append(symbols.getGroupingSeparator(), NumberFormat.Field.GROUPING_SEPARATOR); + dest.setDefaultAttribute(NumberFormat.Field.INTEGER); + } + dest.append((char) (zeroStart + dig)); + + groupPosition--; + } + dest.setDefaultAttribute(null); + + integerEndIndex = dest.length(); + + int decimal_index = integerEndIndex; + int consecutive_zeros = 0; + int total_digits = 0; + + int localMaximumFractionDigits = maximumFractionDigits; + + if (useExponentialNotation) + localMaximumFractionDigits += minimumIntegerDigits - count; + + // Strip integer part from NUMBER. + double fracPart = baseNumber - Math.floor(baseNumber); + + if ( ((fracPart != 0 || minimumFractionDigits > 0) && localMaximumFractionDigits > 0) + || decimalSeparatorAlwaysShown) + { + dest.append (symbols.getDecimalSeparator(), NumberFormat.Field.DECIMAL_SEPARATOR); + } + + int fraction_begin = dest.length(); + dest.setDefaultAttribute(NumberFormat.Field.FRACTION); + for (count = 0; + count < localMaximumFractionDigits + && (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.cutTail(extra_zeros); + total_digits -= extra_zeros; + if (total_digits == 0 && !decimalSeparatorAlwaysShown) + dest.cutTail(1); + } + + if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD) + { + fieldPos.setBeginIndex(fraction_begin); + fieldPos.setEndIndex(dest.length()); + } + + // Finally, print the exponent. + if (useExponentialNotation) + { + dest.append(symbols.getExponential(), NumberFormat.Field.EXPONENT_SYMBOL); + if (exponent < 0) + { + dest.append (symbols.getMinusSign (), NumberFormat.Field.EXPONENT_SIGN); + exponent = - exponent; + } + index = dest.length(); + dest.setDefaultAttribute(NumberFormat.Field.EXPONENT); + String exponentString = Long.toString ((long) exponent); + + for (count = 0; count < minExponentDigits-exponentString.length(); + count++) + dest.append((char) symbols.getZeroDigit()); + + for (count = 0; + count < exponentString.length(); + ++count) + { + int dig = exponentString.charAt(count); + dest.append((char) (zeroStart + dig)); + } + } + } + + if (fieldPos != null && + (fieldPos.getField() == INTEGER_FIELD || + fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER)) + { + fieldPos.setBeginIndex(integerBeginIndex); + fieldPos.setEndIndex(integerEndIndex); + } + + if (is_neg && negativeSuffix != null) + { + dest.append(substituteCurrency(negativeSuffix, number), + negativeSuffixRanges, negativeSuffixAttrs); + } + else + { + dest.append(substituteCurrency(positiveSuffix, number), + positiveSuffixRanges, positiveSuffixAttrs); + } + } + + public StringBuffer format (double number, StringBuffer dest, + FieldPosition fieldPos) + { + formatInternal (number, new StringFormatBuffer(dest), fieldPos); + return dest; + } + + public AttributedCharacterIterator formatToCharacterIterator (Object value) + { + AttributedFormatBuffer sbuf = new AttributedFormatBuffer(); + + if (value instanceof Number) + formatInternal(((Number) value).doubleValue(), sbuf, null); + else + throw new IllegalArgumentException + ("Cannot format given Object as a Number"); + + sbuf.sync(); + return new FormatCharacterIterator(sbuf.getBuffer().toString(), + sbuf.getRanges(), + sbuf.getAttributes()); + } + + 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(substituteCurrency(negativePrefix, number)); + else + { + dest.append(symbols.getMinusSign()); + dest.append(substituteCurrency(positivePrefix, number)); + } + number = - number; + } + else + dest.append(substituteCurrency(positivePrefix, number)); + + int integerBeginIndex = dest.length(); + int index = dest.length(); + int count = 0; + + /* Handle percentages, etc. */ + number *= multiplier; + 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 && groupingSize != 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) + ? substituteCurrency(negativeSuffix, number) + : substituteCurrency(positiveSuffix, number)); + return dest; + } + + /** + * Returns the currency corresponding to the currency symbol stored + * in the instance of DecimalFormatSymbols used by this + * DecimalFormat. + * + * @return A new instance of Currency if + * the currency code matches a known one, null otherwise. + */ + public Currency getCurrency() + { + return symbols.getCurrency(); + } + + /** + * Returns a copy of the symbols used by this instance. + * + * @return A copy of the symbols. + */ + public DecimalFormatSymbols getDecimalFormatSymbols() + { + return (DecimalFormatSymbols) symbols.clone(); + } + + 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; + } + + /** + * Returns a hash code for this object. + * + * @return A hash code. + */ + public int hashCode() + { + return toPattern().hashCode(); + } + + public boolean isDecimalSeparatorAlwaysShown () + { + return decimalSeparatorAlwaysShown; + } + + public Number parse (String str, ParsePosition pos) + { + /* + * Our strategy is simple: copy the text into separate buffers: one for the int part, + * one for the fraction part and for the exponential part. + * We translate or omit locale-specific information. + * If exponential is sufficiently big we merge the fraction and int part and + * remove the '.' and then we use Long to convert the number. In the other + * case, we use Double to convert the full number. + */ + + boolean is_neg = false; + int index = pos.getIndex(); + StringBuffer int_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 digits? + // What about multiplier? + + StringBuffer buf = int_buf; + StringBuffer frac_buf = null; + StringBuffer exp_buf = null; + int start_index = index; + int max = str.length(); + int exp_index = -1; + int last = index + maximumIntegerDigits; + + if (maximumFractionDigits > 0) + last += maximumFractionDigits + 1; + + if (useExponentialNotation) + last += minExponentDigits + 1; + + if (last > 0 && max > last) + max = last; + + 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 + && groupingSize != 0 + && (index - last_group) % groupingSize != 0) + { + pos.setErrorIndex(index); + return null; + } + last_group = index+1; + } + else if (c >= zero && c <= zero + 9) + { + buf.append((char) (c - zero + '0')); + } + else if (parseIntegerOnly) + break; + else if (c == symbols.getDecimalSeparator()) + { + if (last_group != -1 + && groupingSize != 0 + && (index - last_group) % groupingSize != 0) + { + pos.setErrorIndex(index); + return null; + } + buf = frac_buf = new StringBuffer(); + frac_buf.append('.'); + int_part = false; + } + else if (c == symbols.getExponential()) + { + buf = exp_buf = new StringBuffer(); + int_part = false; + exp_part = true; + exp_index = index+1; + } + 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; + } + else if (! got_pos_suf) + { + pos.setErrorIndex(index); + return null; + } + + String suffix = is_neg ? ns : positiveSuffix; + long parsedMultiplier = 1; + boolean use_long; + + if (is_neg) + int_buf.insert(0, '-'); + + // Now handle the exponential part if there is one. + if (exp_buf != null) + { + int exponent_value; + + try + { + exponent_value = Integer.parseInt(exp_buf.toString()); + } + catch (NumberFormatException x1) + { + pos.setErrorIndex(exp_index); + return null; + } + + if (frac_buf == null) + { + // We only have to add some zeros to the int part. + // Build a multiplier. + for (int i = 0; i < exponent_value; i++) + int_buf.append('0'); + + use_long = true; + } + else + { + boolean long_sufficient; + + if (exponent_value < frac_buf.length()-1) + { + int lastNonNull = -1; + /* We have to check the fraction buffer: it may only be full of '0' + * or be sufficiently filled with it to convert the number into Long. + */ + for (int i = 1; i < frac_buf.length(); i++) + if (frac_buf.charAt(i) != '0') + lastNonNull = i; + + long_sufficient = (lastNonNull < 0 || lastNonNull <= exponent_value); + } + else + long_sufficient = true; + + if (long_sufficient) + { + for (int i = 1; i < frac_buf.length() && i < exponent_value; i++) + int_buf.append(frac_buf.charAt(i)); + for (int i = frac_buf.length()-1; i < exponent_value; i++) + int_buf.append('0'); + use_long = true; + } + else + { + /* + * A long type is not sufficient, we build the full buffer to + * be parsed by Double. + */ + int_buf.append(frac_buf); + int_buf.append('E'); + int_buf.append(exp_buf); + use_long = false; + } + } + } + else + { + if (frac_buf != null) + { + /* Check whether the fraction buffer contains only '0' */ + int i; + for (i = 1; i < frac_buf.length(); i++) + if (frac_buf.charAt(i) != '0') + break; + + if (i != frac_buf.length()) + { + use_long = false; + int_buf.append(frac_buf); + } + else + use_long = true; + } + else + use_long = true; + } + + String t = int_buf.toString(); + Number result = null; + if (use_long) + { + try + { + result = new Long (t); + } + catch (NumberFormatException x1) + { + } + } + else + { + try + { + result = new Double (t); + } + catch (NumberFormatException x2) + { + } + } + if (result == null) + { + pos.setErrorIndex(index); + return null; + } + + pos.setIndex(index + suffix.length()); + + return result; + } + + /** + * Sets the Currency on the + * DecimalFormatSymbols used, which also sets the + * currency symbols on those symbols. + */ + public void setCurrency(Currency currency) + { + symbols.setCurrency(currency); + } + + /** + * Sets the symbols used by this instance. This method makes a copy of + * the supplied symbols. + * + * @param newSymbols the symbols (null not permitted). + */ + public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) + { + symbols = (DecimalFormatSymbols) newSymbols.clone(); + } + + public void setDecimalSeparatorAlwaysShown (boolean newValue) + { + decimalSeparatorAlwaysShown = newValue; + } + + public void setGroupingSize (int groupSize) + { + groupingSize = (byte) groupSize; + } + + public void setMaximumFractionDigits (int newValue) + { + super.setMaximumFractionDigits(Math.min(newValue, 340)); + } + + public void setMaximumIntegerDigits (int newValue) + { + super.setMaximumIntegerDigits(Math.min(newValue, 309)); + } + + public void setMinimumFractionDigits (int newValue) + { + super.setMinimumFractionDigits(Math.min(newValue, 340)); + } + + public void setMinimumIntegerDigits (int newValue) + { + super.setMinimumIntegerDigits(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 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 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: groupingSize); + 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); + } + + private static final int MAXIMUM_INTEGER_DIGITS = 309; + + // These names are fixed by the serialization spec. + private boolean decimalSeparatorAlwaysShown; + private byte groupingSize; + private byte minExponentDigits; + private int exponentRound; + private int multiplier; + private String negativePrefix; + private String negativeSuffix; + private String positivePrefix; + private String positiveSuffix; + private int[] negativePrefixRanges, positivePrefixRanges; + private HashMap[] negativePrefixAttrs, positivePrefixAttrs; + private int[] negativeSuffixRanges, positiveSuffixRanges; + private HashMap[] negativeSuffixAttrs, positiveSuffixAttrs; + private int serialVersionOnStream = 1; + private DecimalFormatSymbols symbols; + private boolean useExponentialNotation; + private static final long serialVersionUID = 864413376551465018L; + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + if (serialVersionOnStream < 1) + { + useExponentialNotation = false; + serialVersionOnStream = 1; + } + } + + // The locale-independent pattern symbols happen to be the same as + // the US symbols. + private static final DecimalFormatSymbols nonLocalizedSymbols + = new DecimalFormatSymbols (Locale.US); + + /** + *

+ * Substitutes the currency symbol into the given string, + * based on the value used. Currency symbols can either + * be a simple series of characters (e.g. '$'), which are + * simply used as is, or they can be of a more complex + * form: + *

+ *

+ * (lower bound)|(mid value)|(upper bound) + *

+ *

+ * where each bound has the syntax '(value)(# or <)(symbol)', + * to indicate the bounding value and the symbol used. + *

+ *

+ * The currency symbol replaces the currency specifier, '\u00a4', + * an unlocalised character, which thus is used as such in all formats. + * If this symbol occurs twice, the international currency code is used + * instead. + *

+ * + * @param string The string containing the currency specifier, '\u00a4'. + * @param number the number being formatted. + * @return a string formatted for the correct currency. + */ + private String substituteCurrency(String string, double number) + { + int index; + int length; + char currentChar; + StringBuffer buf; + + index = 0; + length = string.length(); + buf = new StringBuffer(); + + while (index < length) + { + currentChar = string.charAt(index); + if (string.charAt(index) == '\u00a4') + { + if ((index + 1) < length && string.charAt(index + 1) == '\u00a4') + { + buf.append(symbols.getInternationalCurrencySymbol()); + index += 2; + } + else + { + String symbol; + + symbol = symbols.getCurrencySymbol(); + if (symbol.startsWith("=")) + { + String[] bounds; + int[] boundValues; + String[] boundSymbols; + + bounds = symbol.substring(1).split("\\|"); + boundValues = new int[3]; + boundSymbols = new String[3]; + for (int a = 0; a < 3; ++a) + { + String[] bound; + + bound = bounds[a].split("[#<]"); + boundValues[a] = Integer.parseInt(bound[0]); + boundSymbols[a] = bound[1]; + } + if (number <= boundValues[0]) + { + buf.append(boundSymbols[0]); + } + else if (number >= boundValues[2]) + { + buf.append(boundSymbols[2]); + } + else + { + buf.append(boundSymbols[1]); + } + ++index; + } + else + { + buf.append(symbol); + ++index; + } + } + } + else + { + buf.append(string.charAt(index)); + ++index; + } + } + return buf.toString(); + } + +} diff --git a/libjava/classpath/java/text/DecimalFormatSymbols.java b/libjava/classpath/java/text/DecimalFormatSymbols.java new file mode 100644 index 00000000000..81ea0017822 --- /dev/null +++ b/libjava/classpath/java/text/DecimalFormatSymbols.java @@ -0,0 +1,688 @@ +/* DecimalFormatSymbols.java -- Format symbols used by DecimalFormat + Copyright (C) 1999, 2000, 2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Currency; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * This class is a container for the symbols used by + * DecimalFormat to format numbers and currency + * for a particular locale. These are + * normally handled automatically, but an application can override + * values as desired using this class. + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @date February 24, 1999 + * @see java.text.DecimalFormat + */ +/* 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. + */ +public final class DecimalFormatSymbols implements Cloneable, Serializable +{ + public Object clone () + { + try + { + return super.clone (); + } + catch(CloneNotSupportedException e) + { + return null; + } + } + + /** + * This method initializes a new instance of + * DecimalFormatSymbols for the default locale. + */ + public DecimalFormatSymbols () + { + this (Locale.getDefault()); + } + + /** + * Retrieves a valid string, either using the supplied resource + * bundle or the default value. + * + * @param bundle the resource bundle to use to find the string. + * @param name key for the string in the resource bundle. + * @param def default value for the string. + */ + private String safeGetString(ResourceBundle bundle, + String name, String def) + { + if (bundle != null) + { + try + { + return bundle.getString(name); + } + catch (MissingResourceException x) + { + } + } + return def; + } + + private 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); + } + + /** + * This method initializes a new instance of + * DecimalFormatSymbols for the specified locale. + * Note: if the locale does not have an associated + * Currency instance, the currency symbol and + * international currency symbol will be set to the strings "?" + * and "XXX" respectively. This generally happens with language + * locales (those with no specified country), such as + * Locale.ENGLISH. + * + * @param locale The local to load symbols for. + * @throws NullPointerException if the locale is null. + */ + public DecimalFormatSymbols (Locale loc) + { + ResourceBundle res; + + currency = Currency.getInstance("XXX"); + currencySymbol = "?"; + intlCurrencySymbol = "XXX"; + try + { + res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + loc, ClassLoader.getSystemClassLoader()); + } + catch (MissingResourceException x) + { + res = null; + } + try + { + Currency localeCurrency = Currency.getInstance(loc); + if (localeCurrency != null) + { + setCurrency(localeCurrency); + } + } + catch(IllegalArgumentException exception) + { + /* Locale has an invalid currency */ + } + decimalSeparator = safeGetChar (res, "decimalSeparator", '.'); + digit = safeGetChar (res, "digit", '#'); + exponential = safeGetChar (res, "exponential", 'E'); + groupingSeparator = safeGetChar (res, "groupingSeparator", ','); + infinity = safeGetString (res, "infinity", "\u221e"); + try + { + monetarySeparator = safeGetChar (res, "monetarySeparator", '.'); + } + catch (MissingResourceException x) + { + monetarySeparator = decimalSeparator; + } + 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'); + locale = loc; + } + + /** + * This method this this object for equality against the specified object. + * This will be true if and only if the following criteria are met with + * regard to the specified object: + *

+ *

    + *
  • It is not null.
  • + *
  • It is an instance of DecimalFormatSymbols.
  • + *
  • All of its symbols are identical to the symbols in this object.
  • + *
+ * + * @return true if the specified object is equal to this + * object, false otherwise. + */ + 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 + && monetarySeparator == dfs.monetarySeparator + && NaN.equals(dfs.NaN) + && patternSeparator == dfs.patternSeparator + && percent == dfs.percent + && perMill == dfs.perMill + && zeroDigit == dfs.zeroDigit); + } + + /** + * Returns the currency corresponding to the currency symbol stored + * in this instance of DecimalFormatSymbols. + * + * @return An instance of Currency which matches + * the currency used, or null if there is no corresponding + * instance. + */ + public Currency getCurrency () + { + return currency; + } + + /** + * This method returns the currency symbol in local format. For example, + * "$" for Canadian dollars. + * + * @return The currency symbol in local format. + */ + public String getCurrencySymbol () + { + return currencySymbol; + } + + /** + * This method returns the character used as the decimal point. + * + * @return The character used as the decimal point. + */ + public char getDecimalSeparator () + { + return decimalSeparator; + } + + /** + * This method returns the character used to represent a digit in a + * format pattern string. + * + * @return The character used to represent a digit in a format + * pattern string. + */ + public char getDigit () + { + return digit; + } + + /** + * This method returns the character used to represent the exponential + * format. This is a GNU Classpath extension. + * + * @return the character used to represent an exponential in a format + * pattern string. + */ + char getExponential () + { + return exponential; + } + + /** + * This method sets the character used to separate groups of digits. For + * example, the United States uses a comma (,) to separate thousands in + * a number. + * + * @return The character used to separate groups of digits. + */ + public char getGroupingSeparator () + { + return groupingSeparator; + } + + /** + * This method returns the character used to represent infinity. + * + * @return The character used to represent infinity. + */ + public String getInfinity () + { + return infinity; + } + + /** + * This method returns the ISO 4217 currency code for + * the currency used. + * + * @return the ISO 4217 currency code. + */ + public String getInternationalCurrencySymbol () + { + return intlCurrencySymbol; + } + + /** + * This method returns the character used to represent the minus sign. + * + * @return The character used to represent the minus sign. + */ + public char getMinusSign () + { + return minusSign; + } + + /** + * This method returns the character used to represent the decimal + * point for currency values. + * + * @return The decimal point character used in currency values. + */ + public char getMonetaryDecimalSeparator () + { + return monetarySeparator; + } + + /** + * This method returns the string used to represent the NaN (not a number) + * value. + * + * @return The string used to represent NaN + */ + public String getNaN () + { + return NaN; + } + + /** + * This method returns the character used to separate positive and negative + * subpatterns in a format pattern. + * + * @return The character used to separate positive and negative subpatterns + * in a format pattern. + */ + public char getPatternSeparator () + { + return patternSeparator; + } + + /** + * This method returns the character used as the percent sign. + * + * @return The character used as the percent sign. + */ + public char getPercent () + { + return percent; + } + + /** + * This method returns the character used as the per mille character. + * + * @return The per mille character. + */ + public char getPerMill () + { + return perMill; + } + + /** + * This method returns the character used to represent the digit zero. + * + * @return The character used to represent the digit zero. + */ + public char getZeroDigit () + { + return zeroDigit; + } + + /** + * This method returns a hash value for this object. + * + * @return A hash value for this object. + */ + 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; + } + + /** + * This method sets the currency symbol and ISO 4217 currency + * code to the values obtained from the supplied currency. + * + * @param currency the currency from which to obtain the values. + * @throws NullPointerException if the currency is null. + */ + public void setCurrency (Currency currency) + { + intlCurrencySymbol = currency.getCurrencyCode(); + currencySymbol = currency.getSymbol(); + this.currency = currency; + } + + /** + * This method sets the currency symbol to the specified value. + * + * @param currencySymbol The new currency symbol + */ + public void setCurrencySymbol (String currency) + { + currencySymbol = currency; + } + + /** + * This method sets the decimal point character to the specified value. + * + * @param decimalSeparator The new decimal point character + */ + public void setDecimalSeparator (char decimalSep) + { + decimalSeparator = decimalSep; + } + + /** + * This method sets the character used to represents a digit in a format + * string to the specified value. + * + * @param digit The character used to represent a digit in a format pattern. + */ + public void setDigit (char digit) + { + this.digit = digit; + } + + /** + * This method sets the exponential character used in the format string to + * the specified value. This is a GNU Classpath extension. + * + * @param exp the character used for the exponential in a format pattern. + */ + void setExponential (char exp) + { + exponential = exp; + } + + /** + * This method sets the character used to separate groups of digits. + * + * @param groupingSeparator The character used to separate groups of digits. + */ + public void setGroupingSeparator (char groupSep) + { + groupingSeparator = groupSep; + } + + /** + * This method sets the string used to represents infinity. + * + * @param infinity The string used to represent infinity. + */ + public void setInfinity (String infinity) + { + this.infinity = infinity; + } + + /** + * This method sets the international currency symbol to the + * specified value. If a valid Currency instance + * exists for the international currency code, then this is + * used for the currency attribute, and the currency symbol + * is set to the corresponding value from this instance. + * Otherwise, the currency attribute is set to null and the + * symbol is left unmodified. + * + * @param currencyCode The new international currency symbol. + */ + public void setInternationalCurrencySymbol (String currencyCode) + { + intlCurrencySymbol = currencyCode; + try + { + currency = Currency.getInstance(currencyCode); + } + catch (IllegalArgumentException exception) + { + currency = null; + } + if (currency != null) + { + setCurrencySymbol(currency.getSymbol(locale)); + } + } + + /** + * This method sets the character used to represent the minus sign. + * + * @param minusSign The character used to represent the minus sign. + */ + public void setMinusSign (char minusSign) + { + this.minusSign = minusSign; + } + + /** + * This method sets the character used for the decimal point in currency + * values. + * + * @param monetarySeparator The decimal point character used in + * currency values. + */ + public void setMonetaryDecimalSeparator (char decimalSep) + { + monetarySeparator = decimalSep; + } + + /** + * This method sets the string used to represent the NaN (not a + * number) value. + * + * @param NaN The string used to represent NaN + */ + public void setNaN (String nan) + { + NaN = nan; + } + + /** + * This method sets the character used to separate positive and negative + * subpatterns in a format pattern. + * + * @param patternSeparator The character used to separate positive and + * negative subpatterns in a format pattern. + */ + public void setPatternSeparator (char patternSep) + { + patternSeparator = patternSep; + } + + /** + * This method sets the character used as the percent sign. + * + * @param percent The character used as the percent sign. + */ + public void setPercent (char percent) + { + this.percent = percent; + } + + /** + * This method sets the character used as the per mille character. + * + * @param perMill The per mille character. + */ + public void setPerMill (char perMill) + { + this.perMill = perMill; + } + + /** + * This method sets the character used to represent the digit zero. + * + * @param zeroDigit The character used to represent the digit zero. + */ + public void setZeroDigit (char zeroDigit) + { + this.zeroDigit = zeroDigit; + } + + /** + * @serial A string used for the local currency + */ + private String currencySymbol; + /** + * @serial The char used to separate decimals in a number. + */ + private char decimalSeparator; + /** + * @serial This is the char used to represent a digit in + * a format specification. + */ + private char digit; + /** + * @serial This is the char used to represent the exponent + * separator in exponential notation. + */ + private char exponential; + /** + * @serial This separates groups of thousands in numbers. + */ + private char groupingSeparator; + /** + * @serial This string represents infinity. + */ + private String infinity; + /** + * @serial This string represents the local currency in an international + * context, eg, "C$" for Canadian dollars. + */ + private String intlCurrencySymbol; + /** + * @serial This is the character used to represent the minus sign. + */ + private char minusSign; + /** + * @serial This character is used to separate decimals when formatting + * currency values. + */ + private char monetarySeparator; + /** + * @serial This string is used the represent the Java NaN value for + * "not a number". + */ + private String NaN; + /** + * @serial This is the character used to separate positive and negative + * subpatterns in a format pattern. + */ + private char patternSeparator; + /** + * @serial This is the percent symbols + */ + private char percent; + /** + * @serial This character is used for the mille percent sign. + */ + private char perMill; + /** + * @serial This value represents the type of object being de-serialized. + * 0 indicates a pre-Java 1.1.6 version, 1 indicates 1.1.6 or later. + * 0 indicates a pre-Java 1.1.6 version, 1 indicates 1.1.6 or later, + * 2 indicates 1.4 or later + */ + private int serialVersionOnStream = 2; + /** + * @serial This is the character used to represent 0. + */ + private char zeroDigit; + + /** + * @serial The locale of these currency symbols. + */ + private Locale locale; + + /** + * The currency used for the symbols in this instance. + * This is stored temporarily for efficiency reasons, + * as well as to ensure that the correct instance + * is restored from the currency code. + * + * @serial Ignored. + */ + private transient Currency currency; + + private static final long serialVersionUID = 5772796243397350300L; + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + if (serialVersionOnStream < 1) + { + monetarySeparator = decimalSeparator; + exponential = 'E'; + } + if (serialVersionOnStream < 2) + locale = Locale.getDefault(); + + serialVersionOnStream = 2; + } +} diff --git a/libjava/classpath/java/text/FieldPosition.java b/libjava/classpath/java/text/FieldPosition.java new file mode 100644 index 00000000000..427c07e8e11 --- /dev/null +++ b/libjava/classpath/java/text/FieldPosition.java @@ -0,0 +1,232 @@ +/* FieldPosition.java -- Keeps track of field positions while formatting + Copyright (C) 1998, 1999, 2001, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +/** + * This class is used by the java.text formatting classes to track + * field positions. A field position is defined by an identifier value + * and begin and end index positions. The formatting classes in java.text + * typically define constant values for the field identifiers. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Per Bothner (bothner@cygnus.com) + */ +public class FieldPosition +{ + /** + * This is the field identifier value. + */ + private int field_id; + + /** + * This is the beginning index of the field. + */ + private int begin; + + /** + * This is the ending index of the field. + */ + private int end; + + /** + * This is the field attribute value. + */ + private Format.Field field_attribute; + + /** + * This method initializes a new instance of FieldPosition + * to have the specified field attribute. The attribute will be used as + * an id. It is formally equivalent to calling FieldPosition(field, -1). + * + * @param field The field format attribute. + */ + public FieldPosition (Format.Field field) + { + this(field, -1); + } + + /** + * This method initializes a new instance of FieldPosition + * to have the specified field attribute. The attribute will be used as + * an id is non null. The integer field id is only used if the Format.Field + * attribute is not used by the formatter. + * + * @param field The field format attribute. + * @param field_id The field identifier value. + */ + public FieldPosition (Format.Field field, int field_id) + { + this.field_attribute = field; + this.field_id = field_id; + } + + /** + * This method initializes a new instance of FieldPosition to + * have the specified field id. + * + * @param field_id The field identifier value. + */ + public FieldPosition (int field_id) + { + this.field_id = field_id; + } + + /** + * This method returns the field identifier value for this object. + * + * @return The field identifier. + */ + public int getField () + { + return field_id; + } + + public Format.Field getFieldAttribute () + { + return field_attribute; + } + + /** + * This method returns the beginning index for this field. + * + * @return The beginning index. + */ + public int getBeginIndex () + { + return begin; + } + + /** + * This method sets the beginning index of this field to the specified value. + * + * @param begin The new beginning index. + */ + public void setBeginIndex (int begin) + { + this.begin = begin; + } + + /** + * This method returns the ending index for the field. + * + * @return The ending index. + */ + public int getEndIndex () + { + return end; + } + + /** + * This method sets the ending index of this field to the specified value. + * + * @param end The new ending index. + */ + public void setEndIndex (int end) + { + this.end = end; + } + + /** + * This method tests this object for equality against the specified object. + * The objects will be considered equal if and only if: + *

+ *

    + *
  • The specified object is not null. + *
  • The specified object has the same class as this object. + *
  • The specified object has the same field identifier, field attribute + * and beginning and ending index as this object. + *
+ * + * @param obj The object to test for equality to this object. + * + * @return true if the specified object is equal to + * this object, false otherwise. + */ + public boolean equals (Object obj) + { + if (this == obj) + return true; + + if (obj == null || obj.getClass() != this.getClass()) + return false; + + FieldPosition fp = (FieldPosition) obj; + return (field_id == fp.field_id + && (field_attribute == fp.field_attribute + || (field_attribute != null + && field_attribute.equals(fp.field_attribute))) + && begin == fp.begin + && end == fp.end); + } + + + /** + * This method returns a hash value for this object + * + * @return A hash value for this object. + */ + public int hashCode () + { + int hash = 5; + + hash = 31 * hash + field_id; + hash = 31 * hash + begin; + hash = 31 * hash + end; + hash = 31 * hash + + (null == field_attribute ? 0 : field_attribute.hashCode()); + + return hash; + } + + /** + * This method returns a String representation of this + * object. + * + * @return A String representation of this object. + */ + public String toString () + { + return (getClass ().getName () + + "[field=" + getField () + + ",attribute=" + getFieldAttribute () + + ",beginIndex=" + getBeginIndex () + + ",endIndex=" + getEndIndex () + + "]"); + } +} diff --git a/libjava/classpath/java/text/Format.java b/libjava/classpath/java/text/Format.java new file mode 100644 index 00000000000..38fda34ff64 --- /dev/null +++ b/libjava/classpath/java/text/Format.java @@ -0,0 +1,181 @@ +/* Format.java -- Abstract superclass for formatting/parsing strings. + Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import gnu.java.text.FormatCharacterIterator; + +import java.io.Serializable; + +/** + * This class is the abstract superclass of classes that format and parse + * data to/from Strings. It is guaranteed that any + * String produced by a concrete subclass of Format + * will be parseable by that same subclass. + *

+ * In addition to implementing the abstract methods in this class, subclasses + * should provide static factory methods of the form + * getInstance() and getInstance(Locale) if the + * subclass loads different formatting/parsing schemes based on locale. + * These subclasses should also implement a static method called + * getAvailableLocales() which returns an array of + * available locales in the current runtime environment. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Per Bothner (bothner@cygnus.com) + */ +public abstract class Format implements Serializable, Cloneable +{ + /** + * For compatability with Sun's JDK 1.4.2 rev. 5 + */ + static final long serialVersionUID = -299282585814624189L; + + public static class Field extends AttributedCharacterIterator.Attribute + { + static final long serialVersionUID = 276966692217360283L; + + protected Field(String name) + { + super(name); + } + } + + /** + * This method initializes a new instance of Format. + * It performs no actions, but acts as a default constructor for + * subclasses. + */ + public Format () + { + } + + /** + * This method formats an Object into a String. + * + * @param obj The Object to format. + * + * @return The formatted String. + * + * @exception IllegalArgumentException If the Object + * cannot be formatted. + */ + public final String format(Object obj) throws IllegalArgumentException + { + StringBuffer sb = new StringBuffer (); + format (obj, sb, new FieldPosition (0)); + return sb.toString (); + } + + /** + * This method formats an Object into a String and + * appends the String to a StringBuffer. + * + * @param obj The Object to format. + * @param sb The StringBuffer to append to. + * @param pos The desired FieldPosition, which is also + * updated by this call. + * + * @return The updated StringBuffer. + * + * @exception IllegalArgumentException If the Object + * cannot be formatted. + */ + public abstract StringBuffer format (Object obj, StringBuffer sb, + FieldPosition pos) + throws IllegalArgumentException; + + /** + * This method parses a String and converts the parsed + * contents into an Object. + * + * @param str The String to parse. + * + * @return The resulting Object. + * + * @exception ParseException If the String cannot be parsed. + */ + public Object parseObject (String str) throws ParseException + { + ParsePosition pos = new ParsePosition(0); + Object result = parseObject (str, pos); + if (result == null) + { + int index = pos.getErrorIndex(); + if (index < 0) + index = pos.getIndex(); + throw new ParseException("parseObject failed", index); + } + return result; + } + + /** + * This method parses a String and converts the parsed + * contents into an Object. + * + * @param str The String to parse. + * @param pos The starting parse index on input, the ending parse + * index on output. + * + * @return The parsed Object, or null in + * case of error. + */ + public abstract Object parseObject (String str, ParsePosition pos); + + public AttributedCharacterIterator formatToCharacterIterator(Object obj) + { + return new FormatCharacterIterator(format(obj), null, null); + } + + /** + * Creates a copy of this object. + * + * @return The copied Object. + */ + public Object clone () + { + try + { + return super.clone (); + } + catch (CloneNotSupportedException e) + { + return null; + } + } +} diff --git a/libjava/classpath/java/text/MessageFormat.java b/libjava/classpath/java/text/MessageFormat.java new file mode 100644 index 00000000000..f7a9f1687a6 --- /dev/null +++ b/libjava/classpath/java/text/MessageFormat.java @@ -0,0 +1,832 @@ +/* MessageFormat.java - Localized message formatting. + Copyright (C) 1999, 2001, 2002, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import gnu.java.text.FormatCharacterIterator; + +import java.io.InvalidObjectException; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Vector; + +public class MessageFormat extends Format +{ + /** + * @author Tom Tromey (tromey@cygnus.com) + * @author Jorge Aliss (jaliss@hotmail.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. + */ + private static 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; + + // Recompute the locale-based formatter. + void setLocale (Locale loc) + { + if (type == null) + ; + else if (type.equals("number")) + { + formatClass = java.lang.Number.class; + + 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; + df.applyPattern(style); + } + } + else if (type.equals("time") || type.equals("date")) + { + formatClass = java.util.Date.class; + + int val = DateFormat.DEFAULT; + boolean styleIsPattern = false; + if (style == null) + ; + else 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; + else + styleIsPattern = true; + + if (type.equals("time")) + format = DateFormat.getTimeInstance(val, loc); + else + format = DateFormat.getDateInstance(val, loc); + + if (styleIsPattern) + { + SimpleDateFormat sdf = (SimpleDateFormat) format; + sdf.applyPattern(style); + } + } + else if (type.equals("choice")) + { + formatClass = java.lang.Number.class; + + if (style == null) + throw new + IllegalArgumentException ("style required for choice format"); + format = new ChoiceFormat (style); + } + } + } + + private static final long serialVersionUID = 6479157306784022952L; + + public static class Field extends Format.Field + { + static final long serialVersionUID = 7899943957617360810L; + + /** + * This is the attribute set for all characters produced + * by MessageFormat during a formatting. + */ + public static final MessageFormat.Field ARGUMENT = new MessageFormat.Field("argument"); + + // For deserialization + private Field() + { + super(""); + } + + protected Field(String s) + { + super(s); + } + + /** + * invoked to resolve the true static constant by + * comparing the deserialized object to know name. + * + * @return object constant + */ + protected Object readResolve() throws InvalidObjectException + { + if (getName().equals(ARGUMENT.getName())) + return ARGUMENT; + + throw new InvalidObjectException("no such MessageFormat field called " + getName()); + } + + } + + // 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 int scanString(String pat, int index, StringBuffer buffer) + { + int max = pat.length(); + buffer.setLength(0); + boolean quoted = false; + for (; index < max; ++index) + { + char c = pat.charAt(index); + if (quoted) + { + // In a quoted context, a single quote ends the quoting. + if (c == '\'') + quoted = false; + else + buffer.append(c); + } + // Check for '', which is a single quote. + else if (c == '\'' && index + 1 < max && pat.charAt(index + 1) == '\'') + { + buffer.append(c); + ++index; + } + else if (c == '\'') + { + // Start quoting. + quoted = true; + } + else if (c == '{') + break; + else + buffer.append(c); + } + // Note that we explicitly allow an unterminated quote. This is + // done for compatibility. + return index; + } + + // This helper retrieves a single part of a format element. Returns + // the index of the terminating character. + private static int scanFormatElement(String pat, int index, + StringBuffer buffer, char term) + { + int max = pat.length(); + buffer.setLength(0); + int brace_depth = 1; + boolean quoted = false; + + for (; index < max; ++index) + { + char c = pat.charAt(index); + // First see if we should turn off quoting. + if (quoted) + { + if (c == '\'') + quoted = false; + // In both cases we fall through to inserting the + // character here. + } + // See if we have just a plain quote to insert. + else if (c == '\'' && index + 1 < max + && pat.charAt(index + 1) == '\'') + { + buffer.append(c); + ++index; + } + // See if quoting should turn on. + else if (c == '\'') + quoted = true; + else if (c == '{') + ++brace_depth; + else if (c == '}') + { + if (--brace_depth == 0) + break; + } + // Check for TERM after braces, because TERM might be `}'. + else if (c == term) + break; + // All characters, including opening and closing quotes, are + // inserted here. + buffer.append(c); + } + return index; + } + + // This is used to parse a format element and whatever non-format + // text might trail it. + private static 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) + { + IllegalArgumentException iae = new IllegalArgumentException(pat); + iae.initCause(nfx); + throw iae; + } + + // 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("Missing '}' at end of message format"); + ++index; + + // Now fetch trailing string. + index = scanString (pat, index, buffer); + mfe.trailer = buffer.toString (); + + mfe.setLocale(locale); + + return index; + } + + /** + * Applies the specified pattern to this MessageFormat. + * + * @param aPattern The Pattern + */ + 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); + } + + /** + * Overrides Format.clone() + */ + public Object clone () + { + MessageFormat c = (MessageFormat) super.clone (); + c.elements = (MessageFormatElement[]) elements.clone (); + return c; + } + + /** + * Overrides Format.equals(Object obj) + */ + public boolean equals (Object obj) + { + if (! (obj instanceof MessageFormat)) + return false; + MessageFormat mf = (MessageFormat) obj; + return (pattern.equals(mf.pattern) + && locale.equals(mf.locale)); + } + + /** + * A convinience method to format patterns. + * + * @param aPattern The pattern used when formatting. + * @param arguments The array containing the objects to be formatted. + */ + public AttributedCharacterIterator formatToCharacterIterator (Object arguments) + { + Object[] arguments_array = (Object[])arguments; + FormatCharacterIterator iterator = new FormatCharacterIterator(); + + formatInternal(arguments_array, new StringBuffer(), null, iterator); + + return iterator; + } + + /** + * A convinience method to format patterns. + * + * @param aPattern The pattern used when formatting. + * @param arguments The array containing the objects to be formatted. + */ + 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.formatInternal(arguments, sb, fp, null).toString(); + } + + /** + * Returns the pattern with the formatted objects. + * + * @param source The array containing the objects to be formatted. + * @param result The StringBuffer where the text is appened. + * @param fp A FieldPosition object (it is ignored). + */ + public final StringBuffer format (Object arguments[], StringBuffer appendBuf, + FieldPosition fp) + { + return formatInternal(arguments, appendBuf, fp, null); + } + + private StringBuffer formatInternal (Object arguments[], + StringBuffer appendBuf, + FieldPosition fp, + FormatCharacterIterator output_iterator) + { + appendBuf.append(leader); + if (output_iterator != null) + output_iterator.append(leader); + + for (int i = 0; i < elements.length; ++i) + { + Object thisArg = null; + boolean unavailable = false; + if (arguments == null || elements[i].argNumber >= arguments.length) + unavailable = true; + else + thisArg = arguments[elements[i].argNumber]; + + AttributedCharacterIterator iterator = null; + + Format formatter = null; + + if (fp != null && i == fp.getField() && fp.getFieldAttribute() == Field.ARGUMENT) + fp.setBeginIndex(appendBuf.length()); + + if (unavailable) + appendBuf.append("{" + elements[i].argNumber + "}"); + else + { + 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("Wrong format class"); + + 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 (fp != null && fp.getField() == i && fp.getFieldAttribute() == Field.ARGUMENT) + fp.setEndIndex(appendBuf.length()); + + if (formatter != null) + { + // Special-case ChoiceFormat. + if (formatter instanceof ChoiceFormat) + { + StringBuffer buf = new StringBuffer (); + formatter.format(thisArg, buf, fp); + MessageFormat mf = new MessageFormat (); + mf.setLocale(locale); + mf.applyPattern(buf.toString()); + mf.format(arguments, appendBuf, fp); + } + else + { + if (output_iterator != null) + iterator = formatter.formatToCharacterIterator(thisArg); + else + formatter.format(thisArg, appendBuf, fp); + } + + elements[i].format = formatter; + } + + if (output_iterator != null) + { + HashMap hash_argument = new HashMap(); + int position = output_iterator.getEndIndex(); + + hash_argument.put (MessageFormat.Field.ARGUMENT, + new Integer(elements[i].argNumber)); + + + if (iterator != null) + { + output_iterator.append(iterator); + output_iterator.addAttributes(hash_argument, position, + output_iterator.getEndIndex()); + } + else + output_iterator.append(thisArg.toString(), hash_argument); + + output_iterator.append(elements[i].trailer); + } + + appendBuf.append(elements[i].trailer); + } + + return appendBuf; + } + + /** + * Returns the pattern with the formatted objects. The first argument + * must be a array of Objects. + * This is equivalent to format((Object[]) objectArray, appendBuf, fpos) + * + * @param objectArray The object array to be formatted. + * @param appendBuf The StringBuffer where the text is appened. + * @param fpos A FieldPosition object (it is ignored). + */ + public final StringBuffer format (Object objectArray, StringBuffer appendBuf, + FieldPosition fpos) + { + return format ((Object[])objectArray, appendBuf, fpos); + } + + /** + * Returns an array with the Formats for + * the arguments. + */ + 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; + } + + /** + * Returns the locale. + */ + public Locale getLocale () + { + return locale; + } + + /** + * Overrides Format.hashCode() + */ + public int hashCode () + { + // FIXME: not a very good hash. + return pattern.hashCode() + locale.hashCode(); + } + + private MessageFormat () + { + } + + /** + * Creates a new MessageFormat object with + * the specified pattern + * + * @param pattern The Pattern + */ + public MessageFormat(String pattern) + { + this(pattern, Locale.getDefault()); + } + + /** + * Creates a new MessageFormat object with + * the specified pattern + * + * @param pattern The Pattern + * @param locale The Locale to use + * + * @since 1.4 + */ + public MessageFormat(String pattern, Locale locale) + { + this.locale = locale; + applyPattern (pattern); + } + + /** + * Parse a string sourceStr against the pattern specified + * to the MessageFormat constructor. + * + * @param sourceStr the string to be parsed. + * @param pos the current parse position (and eventually the error position). + * @return the array of parsed objects sorted according to their argument number + * in the 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; + if (elements[i].trailer.length() > 0) + next_index = sourceStr.indexOf(elements[i].trailer, index); + else + next_index = sourceStr.length(); + 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); + } + + /** + * Sets the format for the argument at an specified + * index. + * + * @param index The index. + * @format The Format object. + */ + public void setFormat (int variableNum, Format newFormat) + { + elements[variableNum].setFormat = newFormat; + } + + /** + * Sets the formats for the arguments. + * + * @param formats An array of Format objects. + */ + public void setFormats (Format[] newFormats) + { + if (newFormats.length < elements.length) + throw new IllegalArgumentException("Not enough format objects"); + + int len = Math.min(newFormats.length, elements.length); + for (int i = 0; i < len; ++i) + elements[i].setFormat = newFormats[i]; + } + + /** + * Sets the locale. + * + * @param locale A Locale + */ + public void setLocale (Locale loc) + { + locale = loc; + if (elements != null) + { + for (int i = 0; i < elements.length; ++i) + elements[i].setLocale(loc); + } + } + + /** + * Returns the pattern. + */ + public String toPattern () + { + return pattern; + } + + /** + * Return the formatters used sorted by argument index. It uses the + * internal table to fill in this array: if a format has been + * set using setFormat or setFormatByArgumentIndex + * then it returns it at the right index. If not it uses the detected + * formatters during a format call. If nothing is known + * about that argument index it just puts null at that position. + * To get useful informations you may have to call format + * at least once. + * + * @return an array of formatters sorted by argument index. + */ + public Format[] getFormatsByArgumentIndex() + { + int argNumMax = 0; + // First, find the greatest argument number. + for (int i=0;i argNumMax) + argNumMax = elements[i].argNumber; + + Format[] formats = new Format[argNumMax]; + for (int i=0;i + * To create an instance of a concrete subclass of NumberFormat, + * do not call a class constructor directly. Instead, use one of the + * static factory methods in this class such as + * getCurrencyInstance. + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Aaron M. Renn (arenn@urbanophile.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 getAvailableLocales. + */ +public abstract class NumberFormat extends Format implements Cloneable +{ + /** + * This is a constant used to create a FieldPosition object + * that will return the integer portion of a formatted number. + */ + public static final int INTEGER_FIELD = 0; + + /** + * This is a constant used to create a FieldPosition object + * that will return the fractional portion of a formatted number. + */ + public static final int FRACTION_FIELD = 1; + + public static class Field extends Format.Field + { + static final long serialVersionUID = 7494728892700160890L; + + /** + * Attribute set to all characters containing digits of the integer + * part. + */ + public static final NumberFormat.Field INTEGER + = new Field("integer"); + + /** + * Attribute set to all characters containing digits of the fractional + * part. + */ + public static final NumberFormat.Field FRACTION + = new Field("fraction"); + + /** + * Attribute set to all characters containing digits of the exponential + * part. + */ + public static final NumberFormat.Field EXPONENT + = new Field("exponent"); + + /** + * Attribute set to all characters containing a decimal separator. + */ + public static final NumberFormat.Field DECIMAL_SEPARATOR + = new Field("decimal separator"); + + /** + * Attribute set to all characters containing a sign (plus or minus). + */ + public static final NumberFormat.Field SIGN + = new Field("sign"); + + /** + * Attribute set to all characters containing a grouping separator (e.g. + * a comma, a white space,...). + */ + public static final NumberFormat.Field GROUPING_SEPARATOR + = new Field("grouping separator"); + + /** + * Attribute set to all characters containing an exponential symbol (e.g. + * 'E') + */ + public static final NumberFormat.Field EXPONENT_SYMBOL + = new Field("exponent symbol"); + + /** + * Attribute set to all characters containing a percent symbol (e.g. '%') + */ + public static final NumberFormat.Field PERCENT + = new Field("percent"); + + /** + * Attribute set to all characters containing a permille symbol. + */ + public static final NumberFormat.Field PERMILLE + = new Field("permille"); + + /** + * Attribute set to all characters containing the currency unit. + */ + public static final NumberFormat.Field CURRENCY + = new Field("currency"); + + /** + * Attribute set to all characters containing the exponent sign. + */ + public static final NumberFormat.Field EXPONENT_SIGN + = new Field("exponent sign"); + + /** + * Private fields to register all fields contained in this descriptor. + */ + private static final NumberFormat.Field[] allFields = + { + INTEGER, FRACTION, EXPONENT, DECIMAL_SEPARATOR, SIGN, + GROUPING_SEPARATOR, EXPONENT_SYMBOL, PERCENT, + PERMILLE, CURRENCY, EXPONENT_SIGN + }; + + /** + * This constructor is only used by the deserializer. Without it, + * it would fail to construct a valid object. + */ + private Field() + { + super(""); + } + + /** + * Create a Field instance with the specified field name. + * + * @param field_name Field name for the new Field instance. + */ + protected Field(String field_name) + { + super (field_name); + } + + /** + * This function is used by the deserializer to know which object + * to use when it encounters an encoded NumberFormat.Field in a + * serialization stream. If the stream is valid it should return + * one of the above field. In the other case we throw an exception. + * + * @return a valid official NumberFormat.Field instance. + * + * @throws InvalidObjectException if the field name is invalid. + */ + protected Object readResolve() throws InvalidObjectException + { + String s = getName(); + for (int i = 0; i < allFields.length; i++) + if (s.equals(allFields[i].getName())) + return allFields[i]; + + throw new InvalidObjectException("no such NumberFormat field called " + + s); + } + } + + /** + * This method is a specialization of the format method that performs + * a simple formatting of the specified long number. + * + * @param number The long to format. + * + * @return The formatted number + */ + 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) + { + if (obj instanceof Number) + return format(((Number) obj).doubleValue(), sbuf, pos); + else + throw new IllegalArgumentException + ("Cannot format given Object as a Number"); + } + + /** + * This method formats the specified double and appends it to + * a StringBuffer. + * + * @param number The double to format. + * @param sb The StringBuffer to append the formatted number to. + * @param pos The desired FieldPosition. + * + * @return The StringBuffer with the appended number. + */ + public abstract StringBuffer format (double number, + StringBuffer sbuf, FieldPosition pos); + + /** + * This method formats the specified long and appends it to + * a StringBuffer. + * + * @param number The long to format. + * @param sb The StringBuffer to append the formatted number to. + * @param pos The desired FieldPosition. + * + * @return The StringBuffer with the appended number. + */ + public abstract StringBuffer format (long number, + StringBuffer sbuf, FieldPosition pos); + + /** + * This method tests the specified object for equality against this object. + * This will be true if the following conditions are met: + *

+ *

    + *
  • The specified object is not null. + *
  • The specified object is an instance of NumberFormat. + *
+ *

+ * Since this method does not test much, it is highly advised that + * concrete subclasses override this method. + * + * @param obj The Object to test against equality with + * this object. + * + * @return true if the specified object is equal to + * this object, false otherwise. + */ + public boolean equals (Object obj) + { + if (! (obj instanceof NumberFormat)) + return false; + NumberFormat nf = (NumberFormat) obj; + return (groupingUsed == nf.groupingUsed + && maximumFractionDigits == nf.maximumFractionDigits + && maximumIntegerDigits == nf.maximumIntegerDigits + && minimumFractionDigits == nf.minimumFractionDigits + && minimumIntegerDigits == nf.minimumIntegerDigits + && parseIntegerOnly == nf.parseIntegerOnly); + } + + /** + * This method returns a list of locales for which concrete instances + * of NumberFormat subclasses may be created. + * + * @return The list of available locales. + */ + public static Locale[] getAvailableLocales () + { + Locale[] list = new Locale[1]; + list[0] = Locale.US; + return list; + } + + private static NumberFormat computeInstance(Locale loc, String resource, + String def) + { + ResourceBundle res; + try + { + res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + loc, ClassLoader.getSystemClassLoader()); + } + 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); + } + + /** + * This method returns an instance of NumberFormat suitable + * for formatting and parsing currency values in the default locale. + * + * @return An instance of NumberFormat for handling currencies. + */ + public static final NumberFormat getCurrencyInstance () + { + return getCurrencyInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of NumberFormat suitable + * for formatting and parsing currency values in the specified locale. + * + * @return An instance of NumberFormat for handling currencies. + */ + public static NumberFormat getCurrencyInstance (Locale loc) + { + NumberFormat format; + + format = computeInstance (loc, "currencyFormat", "$#,##0.00;($#,##0.00)"); + format.setMaximumFractionDigits(format.getCurrency().getDefaultFractionDigits()); + return format; + } + + /** + * This method returns a default instance for the default locale. This + * will be a concrete subclass of NumberFormat, but the + * actual class returned is dependent on the locale. + * + * @return An instance of the default NumberFormat class. + */ + public static final NumberFormat getInstance () + { + return getInstance (Locale.getDefault()); + } + + /** + * This method returns a default instance for the specified locale. This + * will be a concrete subclass of NumberFormat, but the + * actual class returned is dependent on the locale. + * + * @param locale The desired locale. + * + * @return An instance of the default NumberFormat class. + */ + public static NumberFormat getInstance (Locale loc) + { + // For now always return a number instance. + return getNumberInstance (loc); + } + + /** + * This method returns the maximum number of digits allowed in the fraction + * portion of a number. + * + * @return The maximum number of digits allowed in the fraction + * portion of a number. + */ + public int getMaximumFractionDigits () + { + return maximumFractionDigits; + } + + /** + * This method returns the maximum number of digits allowed in the integer + * portion of a number. + * + * @return The maximum number of digits allowed in the integer + * portion of a number. + */ + public int getMaximumIntegerDigits () + { + return maximumIntegerDigits; + } + + /** + * This method returns the minimum number of digits allowed in the fraction + * portion of a number. + * + * @return The minimum number of digits allowed in the fraction + * portion of a number. + */ + public int getMinimumFractionDigits () + { + return minimumFractionDigits; + } + + /** + * This method returns the minimum number of digits allowed in the integer + * portion of a number. + * + * @return The minimum number of digits allowed in the integer + * portion of a number. + */ + public int getMinimumIntegerDigits () + { + return minimumIntegerDigits; + } + + /** + * This method returns a default instance for the specified locale. This + * will be a concrete subclass of NumberFormat, but the + * actual class returned is dependent on the locale. + * + * @param locale The desired locale. + * + * @return An instance of the default NumberFormat class. + */ + public static final NumberFormat getNumberInstance () + { + return getNumberInstance (Locale.getDefault()); + } + + /** + * This method returns a general purpose number formatting and parsing + * class for the default locale. This will be a concrete subclass of + * NumberFormat, but the actual class returned is dependent + * on the locale. + * + * @return An instance of a generic number formatter for the default locale. + */ + public static NumberFormat getNumberInstance (Locale loc) + { + return computeInstance (loc, "numberFormat", "#,##0.###"); + } + + /** + * This method returns an integer formatting and parsing class for the + * default locale. This will be a concrete subclass of NumberFormat, + * but the actual class returned is dependent on the locale. + * + * @return An instance of an integer number formatter for the default locale. + * @since 1.4 + */ + public static final NumberFormat getIntegerInstance() + { + return getIntegerInstance (Locale.getDefault()); + } + + /** + * This method returns an integer formatting and parsing class for the + * default locale. This will be a concrete subclass of NumberFormat, + * but the actual class returned is dependent on the locale. + * + * @param locale the desired locale. + * + * @return An instance of an integer number formatter for the desired locale. + * @since 1.4 + */ + public static NumberFormat getIntegerInstance(Locale locale) + { + NumberFormat format = computeInstance (locale, "numberFormat", "#,##0"); + format.setMaximumFractionDigits(0); + format.setParseIntegerOnly (true); + return format; + } + + /** + * This method returns an instance of NumberFormat suitable + * for formatting and parsing percentage values in the default locale. + * + * @return An instance of NumberFormat for handling percentages. + */ + public static final NumberFormat getPercentInstance () + { + return getPercentInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of NumberFormat suitable + * for formatting and parsing percentage values in the specified locale. + * + * @param locale The desired locale. + * + * @return An instance of NumberFormat for handling percentages. + */ + public static NumberFormat getPercentInstance (Locale loc) + { + return computeInstance (loc, "percentFormat", "#,##0%"); + } + + /** + * This method returns a hash value for this object. + * + * @return The hash code. + */ + public int hashCode () + { + int hash = super.hashCode(); + hash ^= (maximumFractionDigits + maximumIntegerDigits + + minimumFractionDigits + minimumIntegerDigits); + if (groupingUsed) + hash ^= 0xf0f0; + if (parseIntegerOnly) + hash ^= 0x0f0f; + return hash; + } + + /** + * This method tests whether or not grouping is in use. Grouping is + * a method of marking separations in numbers, such as thousand separators + * in the US English locale. The grouping positions and symbols are all + * locale specific. As an example, with grouping disabled, the number one + * million would appear as "1000000". With grouping enabled, this number + * might appear as "1,000,000". (Both of these assume the US English + * locale). + * + * @return true if grouping is enabled, + * false otherwise. + */ + public boolean isGroupingUsed () + { + return groupingUsed; + } + + /** + * This method tests whether or not only integer values should be parsed. + * If this class is parsing only integers, parsing stops at the decimal + * point. + * + * @return true if only integers are parsed, + * false otherwise. + */ + public boolean isParseIntegerOnly () + { + return parseIntegerOnly; + } + + /** + * This is a default constructor for use by subclasses. + */ + public NumberFormat () + { + } + + /** + * This method parses the specified string into a Number. This + * will be a Long if possible, otherwise it will be a + * Double. If no number can be parsed, no exception is + * thrown. Instead, the parse position remains at its initial index. + * + * @param str The string to parse. + * @param pp The desired ParsePosition. + * + * @return The parsed Number + */ + public abstract Number parse (String sourceStr, ParsePosition pos); + + /** + * This method parses the specified string into a Number. This + * will be a Long if possible, otherwise it will be a + * Double. If no number can be parsed, an exception will be + * thrown. + * + * @param str The string to parse. + * + * @return The parsed Number + * + * @exception ParseException If no number can be parsed. + */ + 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; + } + + /** + * This method parses the specified string into an Object. This + * will be a Long if possible, otherwise it will be a + * Double. If no number can be parsed, no exception is + * thrown. Instead, the parse position remains at its initial index. + * + * @param str The string to parse. + * @param pp The desired ParsePosition. + * + * @return The parsed Object + */ + public final Object parseObject (String sourceStr, ParsePosition pos) + { + return parse (sourceStr, pos); + } + + /** + * This method sets the grouping behavior of this formatter. Grouping is + * a method of marking separations in numbers, such as thousand separators + * in the US English locale. The grouping positions and symbols are all + * locale specific. As an example, with grouping disabled, the number one + * million would appear as "1000000". With grouping enabled, this number + * might appear as "1,000,000". (Both of these assume the US English + * locale). + * + * @param groupingUsed true to enable grouping, + * false to disable it. + */ + public void setGroupingUsed (boolean newValue) + { + groupingUsed = newValue; + } + + /** + * This method sets the maximum number of digits allowed in the fraction + * portion of a number to the specified value. If this is less than the + * current minimum allowed digits, the minimum allowed digits value will + * be lowered to be equal to the new maximum allowed digits value. + * + * @param maximumFractionDigits The new maximum fraction digits value. + */ + public void setMaximumFractionDigits (int newValue) + { + maximumFractionDigits = newValue; + if (getMinimumFractionDigits () > maximumFractionDigits) + setMinimumFractionDigits (maximumFractionDigits); + } + + /** + * This method sets the maximum number of digits allowed in the integer + * portion of a number to the specified value. If this is less than the + * current minimum allowed digits, the minimum allowed digits value will + * be lowered to be equal to the new maximum allowed digits value. + * + * @param maximumIntegerDigits The new maximum integer digits value. + */ + public void setMaximumIntegerDigits (int newValue) + { + maximumIntegerDigits = newValue; + if (getMinimumIntegerDigits () > maximumIntegerDigits) + setMinimumIntegerDigits (maximumIntegerDigits); + } + + /** + * This method sets the minimum number of digits allowed in the fraction + * portion of a number to the specified value. If this is greater than the + * current maximum allowed digits, the maximum allowed digits value will + * be raised to be equal to the new minimum allowed digits value. + * + * @param minimumFractionDigits The new minimum fraction digits value. + */ + public void setMinimumFractionDigits (int newValue) + { + minimumFractionDigits = newValue; + if (getMaximumFractionDigits () < minimumFractionDigits) + setMaximumFractionDigits (minimumFractionDigits); + } + + /** + * This method sets the minimum number of digits allowed in the integer + * portion of a number to the specified value. If this is greater than the + * current maximum allowed digits, the maximum allowed digits value will + * be raised to be equal to the new minimum allowed digits value. + * + * @param minimumIntegerDigits The new minimum integer digits value. + */ + public void setMinimumIntegerDigits (int newValue) + { + minimumIntegerDigits = newValue; + if (getMaximumIntegerDigits () < minimumIntegerDigits) + setMaximumIntegerDigits (minimumIntegerDigits); + } + + /** + * This method sets the parsing behavior of this object to parse only + * integers or not. + * + * @param parseIntegerOnly true to parse only integers, + * false otherwise. + */ + public void setParseIntegerOnly (boolean value) + { + parseIntegerOnly = value; + } + + /** + * This method is a specialization of the format method that performs + * a simple formatting of the specified double number. + * + * @param number The double to format. + * + * @return The formatted number + */ + 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. + boolean groupingUsed; + int maximumFractionDigits; + private byte maxFractionDigits; + int maximumIntegerDigits; + private byte maxIntegerDigits; + int minimumFractionDigits; + private byte minFractionDigits; + int minimumIntegerDigits; + private byte minIntegerDigits; + boolean parseIntegerOnly; + private int serialVersionOnStream; + private static final long serialVersionUID = -2308460125733713944L; + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + if (serialVersionOnStream < 1) + { + maximumFractionDigits = maxFractionDigits; + maximumIntegerDigits = maxIntegerDigits; + minimumFractionDigits = minFractionDigits; + minimumIntegerDigits = minIntegerDigits; + serialVersionOnStream = 1; + } + } + + private void writeObject(ObjectOutputStream stream) throws IOException + { + maxFractionDigits = maximumFractionDigits < Byte.MAX_VALUE ? + (byte) maximumFractionDigits : Byte.MAX_VALUE; + maxIntegerDigits = maximumIntegerDigits < Byte.MAX_VALUE ? + (byte) maximumIntegerDigits : Byte.MAX_VALUE; + minFractionDigits = minimumFractionDigits < Byte.MAX_VALUE ? + (byte) minimumFractionDigits : Byte.MAX_VALUE; + minIntegerDigits = minimumIntegerDigits < Byte.MAX_VALUE ? + (byte) minimumIntegerDigits : Byte.MAX_VALUE; + serialVersionOnStream = 1; + stream.defaultWriteObject(); + } + + /** + * Returns the currency used by this number format when formatting currency + * values. + * + * The default implementation throws UnsupportedOperationException. + * + * @return The used currency object, or null. + * + * @throws UnsupportedOperationException If the number format class doesn't + * implement currency formatting. + * + * @since 1.4 + */ + public Currency getCurrency() + { + throw new UnsupportedOperationException(); + } + + /** + * Sets the currency used by this number format when formatting currency + * values. + * + * The default implementation throws UnsupportedOperationException. + * + * @param currency The new currency to be used by this number format. + * + * @throws NullPointerException If currenc is null. + * @throws UnsupportedOperationException If the number format class doesn't + * implement currency formatting. + * + * @since 1.4 + */ + public void setCurrency(Currency currency) + { + if (currency == null) + throw new NullPointerException("currency may not be null"); + + throw new UnsupportedOperationException(); + } +} diff --git a/libjava/classpath/java/text/ParseException.java b/libjava/classpath/java/text/ParseException.java new file mode 100644 index 00000000000..6d014effd2a --- /dev/null +++ b/libjava/classpath/java/text/ParseException.java @@ -0,0 +1,86 @@ +/* ParseException.java -- an error occurred while parsing + Copyright (C) 1998, 1999, 2001, 2002, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +/** + * This exception is thrown when an unexpected error occurs during parsing. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Per Bothner (bothner@cygnus.com) + * @see Format + * @see FieldPosition + * @status updated to 1.4 + */ +public class ParseException extends Exception +{ + /** + * Compatible with JDK 1.1+. + */ + private static final long serialVersionUID = 2703218443322787634L; + + /** + * This is the position where the error was encountered. + * + * @serial the zero-based offset in the string where the error occurred + */ + private final int errorOffset; + + /** + * This method initializes a new instance of ParseException + * with a detailed error message and a error position. + * + * @param msg the descriptive message describing the error + * @param offset the position where the error was encountered + */ + public ParseException(String s, int offset) + { + super(s); + errorOffset = offset; + } + + /** + * This method returns the position where the error occurred. + * + * @return the position where the error occurred + */ + public int getErrorOffset() + { + return errorOffset; + } +} // class ParseException diff --git a/libjava/classpath/java/text/ParsePosition.java b/libjava/classpath/java/text/ParsePosition.java new file mode 100644 index 00000000000..782f5e0eda2 --- /dev/null +++ b/libjava/classpath/java/text/ParsePosition.java @@ -0,0 +1,151 @@ +/* ParsePosition.java -- Keep track of position while parsing. + Copyright (C) 1998, 1999, 2001, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +/** + * This class is used to keep track of the current position during parsing + * operations. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Per Bothner (bothner@cygnus.com) + */ +public class ParsePosition +{ + /** + * This is the index of the current parse position. + */ + private int index; + + /** + * This is the index of the position where an error occurred during parsing. + */ + private int error_index; + + /** + * This method initializes a new instance of ParsePosition to + * have the specified initial index value. + * + * @param index The initial parsing index. + */ + public ParsePosition (int index) + { + this.index = index; + error_index = -1; + } + + /** + * This method returns the current parsing index. + * + * @return The current parsing index + */ + public int getIndex () + { + return index; + } + + /** + * This method sets the current parsing index to the specified value. + * + * @param index The new parsing index. + */ + public void setIndex (int index) + { + this.index = index; + } + + /** + * This method returns the error index value. This value defaults to -1 + * unless explicitly set to another value. + * + * @return The error index. + */ + public int getErrorIndex () + { + return error_index; + } + + /** + * This method sets the error index to the specified value. + * + * @param error_index The new error index + */ + public void setErrorIndex (int error_index) + { + this.error_index = error_index; + } + + /** + * This method tests the specified object for equality with this + * object. The two objects will be considered equal if and only if + * all of the following conditions are met. + *

+ *

    + *
  • The specified object is not null.
  • + *
  • The specified object is an instance of ParsePosition.
  • + *
  • The specified object has the same index and error index as + * this object.
  • + *
+ * + * @param obj The Object to test for equality against + * this object. + * + * @return true if the specified object is equal to + * this object, false otherwise. + */ + public boolean equals (Object obj) + { + if (! (obj instanceof ParsePosition)) + return false; + + ParsePosition other = (ParsePosition) obj; + return index == other.index && error_index == other.error_index; + } + + /** + * This method returns a String representation of this + * object. + * + * @return A String that represents this object. + */ + public String toString () + { + return (getClass ().getName () + "[index=" + getIndex () + + ",errorIndex=" + getErrorIndex () + "]"); + } +} diff --git a/libjava/classpath/java/text/RuleBasedCollator.java b/libjava/classpath/java/text/RuleBasedCollator.java new file mode 100644 index 00000000000..ae84a41032d --- /dev/null +++ b/libjava/classpath/java/text/RuleBasedCollator.java @@ -0,0 +1,1017 @@ +/* RuleBasedCollator.java -- Concrete Collator Class + Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import java.util.ArrayList; +import java.util.HashMap; + +/* 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 + */ + +/** + * This class is a concrete subclass of Collator suitable + * for string collation in a wide variety of languages. An instance of + * this class is normally returned by the getInstance method + * of Collator with rules predefined for the requested + * locale. However, an instance of this class can be created manually + * with any desired rules. + *

+ * Rules take the form of a String with the following syntax + *

    + *
  • Modifier: '@'
  • + *
  • Relation: '<' | ';' | ',' | '=' : <text>
  • + *
  • Reset: '&' : <text>
  • + *
+ * The modifier character indicates that accents sort backward as is the + * case with French. The modifier applies to all rules after + * the modifier but before the next primary sequence. If placed at the end + * of the sequence if applies to all unknown accented character. + * The relational operators specify how the text + * argument relates to the previous term. The relation characters have + * the following meanings: + *
    + *
  • '<' - The text argument is greater than the prior term at the primary + * difference level.
  • + *
  • ';' - The text argument is greater than the prior term at the secondary + * difference level.
  • + *
  • ',' - The text argument is greater than the prior term at the tertiary + * difference level.
  • + *
  • '=' - The text argument is equal to the prior term
  • + *
+ *

+ * As for the text argument itself, this is any sequence of Unicode + * characters not in the following ranges: 0x0009-0x000D, 0x0020-0x002F, + * 0x003A-0x0040, 0x005B-0x0060, and 0x007B-0x007E. If these characters are + * desired, they must be enclosed in single quotes. If any whitespace is + * encountered, it is ignored. (For example, "a b" is equal to "ab"). + *

+ * The reset operation inserts the following rule at the point where the + * text argument to it exists in the previously declared rule string. This + * makes it easy to add new rules to an existing string by simply including + * them in a reset sequence at the end. Note that the text argument, or + * at least the first character of it, must be present somewhere in the + * previously declared rules in order to be inserted properly. If this + * is not satisfied, a ParseException will be thrown. + *

+ * This system of configuring RuleBasedCollator is needlessly + * complex and the people at Taligent who developed it (along with the folks + * at Sun who accepted it into the Java standard library) deserve a slow + * and agonizing death. + *

+ * Here are a couple of example of rule strings: + *

+ * "< a < b < c" - This string says that a is greater than b which is + * greater than c, with all differences being primary differences. + *

+ * "< a,A < b,B < c,C" - This string says that 'A' is greater than 'a' with + * a tertiary strength comparison. Both 'b' and 'B' are greater than 'a' and + * 'A' during a primary strength comparison. But 'B' is greater than 'b' + * under a tertiary strength comparison. + *

+ * "< a < c & a < b " - This sequence is identical in function to the + * "< a < b < c" rule string above. The '&' reset symbol indicates that + * the rule "< b" is to be inserted after the text argument "a" in the + * previous rule string segment. + *

+ * "< a < b & y < z" - This is an error. The character 'y' does not appear + * anywhere in the previous rule string segment so the rule following the + * reset rule cannot be inserted. + *

+ * "< a & A @ < e & E < f& F" - This sequence is equivalent to the following + * "< a & A < E & e < f & F". + *

+ * For a description of the various comparison strength types, see the + * documentation for the Collator class. + *

+ * As an additional complication to this already overly complex rule scheme, + * if any characters precede the first rule, these characters are considered + * ignorable. They will be treated as if they did not exist during + * comparisons. For example, "- < a < b ..." would make '-' an ignorable + * character such that the strings "high-tech" and "hightech" would + * be considered identical. + *

+ * A ParseException will be thrown for any of the following + * conditions: + *

    + *
  • Unquoted punctuation characters in a text argument.
  • + *
  • A relational or reset operator not followed by a text argument
  • + *
  • A reset operator where the text argument is not present in + * the previous rule string section.
  • + *
+ * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Tom Tromey (tromey@cygnus.com) + * @author Guilhem Lavaux (guilhem@kaffe.org) + */ +public class RuleBasedCollator extends Collator +{ + /** + * This class describes what rank has a character (or a sequence of characters) + * in the lexicographic order. Each element in a rule has a collation element. + */ + static final class CollationElement + { + String key; + int primary; + short secondary; + short tertiary; + short equality; + boolean ignore; + String expansion; + + CollationElement(String key, int primary, short secondary, short tertiary, + short equality, String expansion, boolean ignore) + { + this.key = key; + this.primary = primary; + this.secondary = secondary; + this.tertiary = tertiary; + this.equality = equality; + this.ignore = ignore; + this.expansion = expansion; + } + + int getValue() + { + return (primary << 16) + (secondary << 8) + tertiary; + } + } + + /** + * Basic collation instruction (internal format) to build the series of + * collation elements. It contains an instruction which specifies the new + * state of the generator. The sequence of instruction should not contain + * RESET (it is used by + * {@link #mergeRules(int,java.lang.String,java.util.ArrayList,java.util.ArrayList)}) + * as a temporary state while merging two sets of instructions. + */ + static final class CollationSorter + { + static final int GREATERP = 0; + static final int GREATERS = 1; + static final int GREATERT = 2; + static final int EQUAL = 3; + static final int RESET = 4; + static final int INVERSE_SECONDARY = 5; + + int comparisonType; + String textElement; + int hashText; + int offset; + boolean ignore; + + String expansionOrdering; + } + + /** + * This the the original rule string. + */ + private String rules; + + /** + * This is the table of collation element values + */ + private Object[] ce_table; + + /** + * Quick-prefix finder. + */ + HashMap prefix_tree; + + /** + * This is the value of the last sequence entered into + * ce_table. It is used to compute the + * ordering value of unspecified character. + */ + private int last_primary_value; + + /** + * This is the value of the last secondary sequence of the + * primary 0, entered into + * ce_table. It is used to compute the + * ordering value of an unspecified accented character. + */ + private int last_tertiary_value; + + /** + * This variable is true if accents need to be sorted + * in the other direction. + */ + private boolean inverseAccentComparison; + + /** + * This collation element is special to unknown sequence. + * The JDK uses it to mark and sort the characters which has + * no collation rules. + */ + static final CollationElement SPECIAL_UNKNOWN_SEQ = + new CollationElement("", (short) 32767, (short) 0, (short) 0, + (short) 0, null, false); + + /** + * This method initializes a new instance of RuleBasedCollator + * with the specified collation rules. Note that an application normally + * obtains an instance of RuleBasedCollator by calling the + * getInstance method of Collator. That method + * automatically loads the proper set of rules for the desired locale. + * + * @param rules The collation rule string. + * + * @exception ParseException If the rule string contains syntax errors. + */ + public RuleBasedCollator(String rules) throws ParseException + { + if (rules.equals("")) + throw new ParseException("empty rule set", 0); + + this.rules = rules; + + buildCollationVector(parseString(rules)); + buildPrefixAccess(); + } + + /** + * This method returns the number of common characters at the beginning + * of the string of the two parameters. + * + * @param prefix A string considered as a prefix to test against + * the other string. + * @param s A string to test the prefix against. + * @return The number of common characters. + */ + static int findPrefixLength(String prefix, String s) + { + int index; + int len = prefix.length(); + + for (index = 0; index < len && index < s.length(); ++index) + { + if (prefix.charAt(index) != s.charAt(index)) + return index; + } + + + return index; + } + + /** + * Here we are merging two sets of sorting instructions: 'patch' into 'main'. This methods + * checks whether it is possible to find an anchor point for the rules to be merged and + * then insert them at that precise point. + * + * @param offset Offset in the string containing rules of the beginning of the rules + * being merged in. + * @param starter Text of the rules being merged. + * @param main Repository of all already parsed rules. + * @param patch Rules to be merged into the repository. + * @throws ParseException if it is impossible to find an anchor point for the new rules. + */ + private void mergeRules(int offset, String starter, ArrayList main, ArrayList patch) + throws ParseException + { + int insertion_point = -1; + int max_length = 0; + + /* We must check that no rules conflict with another already present. If it + * is the case delete the old rule. + */ + + /* For the moment good old O(N^2) algorithm. + */ + for (int i = 0; i < patch.size(); i++) + { + int j = 0; + + while (j < main.size()) + { + CollationSorter rule1 = (CollationSorter) patch.get(i); + CollationSorter rule2 = (CollationSorter) main.get(j); + + if (rule1.textElement.equals(rule2.textElement)) + main.remove(j); + else + j++; + } + } + + // Find the insertion point... O(N) + for (int i = 0; i < main.size(); i++) + { + CollationSorter sorter = (CollationSorter) main.get(i); + int length = findPrefixLength(starter, sorter.textElement); + + if (length > max_length) + { + max_length = length; + insertion_point = i+1; + } + } + + if (insertion_point < 0) + throw new ParseException("no insertion point found for " + starter, offset); + + if (max_length < starter.length()) + { + /* + * We need to expand the first entry. It must be sorted + * like if it was the reference key itself (like the spec + * said. So the first entry is special: the element is + * replaced by the specified text element for the sorting. + * This text replace the old one for comparisons. However + * to preserve the behaviour we replace the first key (corresponding + * to the found prefix) by a new code rightly ordered in the + * sequence. The rest of the subsequence must be appended + * to the end of the sequence. + */ + CollationSorter sorter = (CollationSorter) patch.get(0); + CollationSorter expansionPrefix = + (CollationSorter) main.get(insertion_point-1); + + sorter.expansionOrdering = starter.substring(max_length); // Skip the first good prefix element + + main.add(insertion_point, sorter); + + /* + * This is a new set of rules. Append to the list. + */ + patch.remove(0); + insertion_point++; + } + + // Now insert all elements of patch at the insertion point. + for (int i = 0; i < patch.size(); i++) + main.add(i+insertion_point, patch.get(i)); + } + + /** + * This method parses a string and build a set of sorting instructions. The parsing + * may only be partial on the case the rules are to be merged sometime later. + * + * @param stop_on_reset If this parameter is true then the parser stops when it + * encounters a reset instruction. In the other case, it tries to parse the subrules + * and merged it in the same repository. + * @param v Output vector for the set of instructions. + * @param base_offset Offset in the string to begin parsing. + * @param rules Rules to be parsed. + * @return -1 if the parser reached the end of the string, an integer representing the + * offset in the string at which it stopped parsing. + * @throws ParseException if something turned wrong during the parsing. To get details + * decode the message. + */ + private int subParseString(boolean stop_on_reset, ArrayList v, + int base_offset, String rules) + throws ParseException + { + boolean ignoreChars = (base_offset == 0); + int operator = -1; + StringBuffer sb = new StringBuffer(); + boolean doubleQuote = false; + boolean eatingChars = false; + boolean nextIsModifier = false; + boolean isModifier = false; + int i; + +main_parse_loop: + for (i = 0; i < rules.length(); i++) + { + char c = rules.charAt(i); + int type = -1; + + if (!eatingChars && + ((c >= 0x09 && c <= 0x0D) || (c == 0x20))) + continue; + + isModifier = nextIsModifier; + nextIsModifier = false; + + if (eatingChars && c != '\'') + { + doubleQuote = false; + sb.append(c); + continue; + } + if (doubleQuote && eatingChars) + { + sb.append(c); + doubleQuote = false; + continue; + } + + switch (c) + { + case '!': + throw new ParseException + ("Modifier '!' is not yet supported by Classpath", i + base_offset); + case '<': + type = CollationSorter.GREATERP; + break; + case ';': + type = CollationSorter.GREATERS; + break; + case ',': + type = CollationSorter.GREATERT; + break; + case '=': + type = CollationSorter.EQUAL; + break; + case '\'': + eatingChars = !eatingChars; + doubleQuote = true; + break; + case '@': + if (ignoreChars) + throw new ParseException + ("comparison list has not yet been started. You may only use" + + "(<,;=&)", i + base_offset); + // Inverse the order of secondaries from now on. + nextIsModifier = true; + type = CollationSorter.INVERSE_SECONDARY; + break; + case '&': + type = CollationSorter.RESET; + if (stop_on_reset) + break main_parse_loop; + break; + default: + if (operator < 0) + throw new ParseException + ("operator missing at " + (i + base_offset), i + base_offset); + if (! eatingChars + && ((c >= 0x21 && c <= 0x2F) + || (c >= 0x3A && c <= 0x40) + || (c >= 0x5B && c <= 0x60) + || (c >= 0x7B && c <= 0x7E))) + throw new ParseException + ("unquoted punctuation character '" + c + "'", i + base_offset); + + //type = ignoreChars ? CollationSorter.IGNORE : -1; + sb.append(c); + break; + } + + if (type < 0) + continue; + + if (operator < 0) + { + operator = type; + continue; + } + + if (sb.length() == 0 && !isModifier) + throw new ParseException + ("text element empty at " + (i+base_offset), i+base_offset); + + if (operator == CollationSorter.RESET) + { + /* Reposition in the sorting list at the position + * indicated by the text element. + */ + String subrules = rules.substring(i); + ArrayList sorted_rules = new ArrayList(); + int idx; + + // Parse the subrules but do not iterate through all + // sublist. This is the priviledge of the first call. + idx = subParseString(true, sorted_rules, base_offset+i, subrules); + + // Merge new parsed rules into the list. + mergeRules(base_offset+i, sb.toString(), v, sorted_rules); + sb.setLength(0); + + // Reset state to none. + operator = -1; + type = -1; + // We have found a new subrule at 'idx' but it has not been parsed. + if (idx >= 0) + { + i += idx-1; + continue main_parse_loop; + } + else + // No more rules. + break main_parse_loop; + } + + CollationSorter sorter = new CollationSorter(); + + if (operator == CollationSorter.GREATERP) + ignoreChars = false; + + sorter.comparisonType = operator; + sorter.textElement = sb.toString(); + sorter.hashText = sorter.textElement.hashCode(); + sorter.offset = base_offset+rules.length(); + sorter.ignore = ignoreChars; + sb.setLength(0); + + v.add(sorter); + operator = type; + } + + if (operator >= 0) + { + CollationSorter sorter = new CollationSorter(); + int pos = rules.length() + base_offset; + + if ((sb.length() != 0 && nextIsModifier) + || (sb.length() == 0 && !nextIsModifier && !eatingChars)) + throw new ParseException("text element empty at " + pos, pos); + + if (operator == CollationSorter.GREATERP) + ignoreChars = false; + + sorter.comparisonType = operator; + sorter.textElement = sb.toString(); + sorter.hashText = sorter.textElement.hashCode(); + sorter.offset = base_offset+pos; + sorter.ignore = ignoreChars; + v.add(sorter); + } + + if (i == rules.length()) + return -1; + else + return i; + } + + /** + * This method creates a copy of this object. + * + * @return A copy of this object. + */ + public Object clone() + { + return super.clone(); + } + + /** + * This method completely parses a string 'rules' containing sorting rules. + * + * @param rules String containing the rules to be parsed. + * @return A set of sorting instructions stored in a Vector. + * @throws ParseException if something turned wrong during the parsing. To get details + * decode the message. + */ + private ArrayList parseString(String rules) + throws ParseException + { + ArrayList v = new ArrayList(); + + // result of the first subParseString is not absolute (may be -1 or a + // positive integer). But we do not care. + subParseString(false, v, 0, rules); + + return v; + } + + /** + * This method uses the sorting instructions built by {@link #parseString} + * to build collation elements which can be directly used to sort strings. + * + * @param parsedElements Parsed instructions stored in a ArrayList. + * @throws ParseException if the order of the instructions are not valid. + */ + private void buildCollationVector(ArrayList parsedElements) + throws ParseException + { + int primary_seq = 0; + int last_tertiary_seq = 0; + short secondary_seq = 0; + short tertiary_seq = 0; + short equality_seq = 0; + boolean inverseComparisons = false; + final boolean DECREASING = false; + final boolean INCREASING = true; + boolean secondaryType = INCREASING; + ArrayList v = new ArrayList(); + + // elts is completely sorted. +element_loop: + for (int i = 0; i < parsedElements.size(); i++) + { + CollationSorter elt = (CollationSorter) parsedElements.get(i); + boolean ignoreChar = false; + + switch (elt.comparisonType) + { + case CollationSorter.GREATERP: + primary_seq++; + if (inverseComparisons) + { + secondary_seq = Short.MAX_VALUE; + secondaryType = DECREASING; + } + else + { + secondary_seq = 0; + secondaryType = INCREASING; + } + tertiary_seq = 0; + equality_seq = 0; + inverseComparisons = false; + break; + case CollationSorter.GREATERS: + if (secondaryType == DECREASING) + secondary_seq--; + else + secondary_seq++; + tertiary_seq = 0; + equality_seq = 0; + break; + case CollationSorter.INVERSE_SECONDARY: + inverseComparisons = true; + continue element_loop; + case CollationSorter.GREATERT: + tertiary_seq++; + if (primary_seq == 0) + last_tertiary_seq = tertiary_seq; + equality_seq = 0; + break; + case CollationSorter.EQUAL: + equality_seq++; + break; + case CollationSorter.RESET: + throw new ParseException + ("Invalid reached state 'RESET'. Internal error", elt.offset); + default: + throw new ParseException + ("Invalid unknown state '" + elt.comparisonType + "'", elt.offset); + } + + v.add(new CollationElement(elt.textElement, primary_seq, + secondary_seq, tertiary_seq, + equality_seq, elt.expansionOrdering, elt.ignore)); + } + + this.inverseAccentComparison = inverseComparisons; + + ce_table = v.toArray(); + + last_primary_value = primary_seq+1; + last_tertiary_value = last_tertiary_seq+1; + } + + /** + * Build a tree where all keys are the texts of collation elements and data is + * the collation element itself. The tree is used when extracting all prefix + * for a given text. + */ + private void buildPrefixAccess() + { + prefix_tree = new HashMap(); + + for (int i = 0; i < ce_table.length; i++) + { + CollationElement e = (CollationElement) ce_table[i]; + + prefix_tree.put(e.key, e); + } + } + + /** + * This method returns an integer which indicates whether the first + * specified String is less than, greater than, or equal to + * the second. The value depends not only on the collation rules in + * effect, but also the strength and decomposition settings of this object. + * + * @param source The first String to compare. + * @param target A second String to compare to the first. + * + * @return A negative integer if source < target, a positive integer + * if source > target, or 0 if source == target. + */ + public int compare(String source, String target) + { + CollationElementIterator cs, ct; + CollationElement ord1block = null; + CollationElement ord2block = null; + boolean advance_block_1 = true; + boolean advance_block_2 = true; + + cs = getCollationElementIterator(source); + ct = getCollationElementIterator(target); + + for(;;) + { + int ord1; + int ord2; + + /* + * We have to check whether the characters are ignorable. + * If it is the case then forget them. + */ + if (advance_block_1) + { + ord1block = cs.nextBlock(); + if (ord1block != null && ord1block.ignore) + continue; + } + + if (advance_block_2) + { + ord2block = ct.nextBlock(); + if (ord2block != null && ord2block.ignore) + { + advance_block_1 = false; + continue; + } + } + else + advance_block_2 = true; + + if (!advance_block_1) + advance_block_1 = true; + + if (ord1block != null) + ord1 = ord1block.getValue(); + else + { + if (ord2block == null) + return 0; + return -1; + } + + if (ord2block == null) + return 1; + + ord2 = ord2block.getValue(); + + // We know chars are totally equal, so skip + if (ord1 == ord2) + { + if (getStrength() == IDENTICAL) + if (!ord1block.key.equals(ord2block.key)) + return ord1block.key.compareTo(ord2block.key); + continue; + } + + // Check for primary strength differences + int prim1 = CollationElementIterator.primaryOrder(ord1); + int prim2 = CollationElementIterator.primaryOrder(ord2); + + if (prim1 == 0 && getStrength() < TERTIARY) + { + advance_block_2 = false; + continue; + } + else if (prim2 == 0 && getStrength() < TERTIARY) + { + advance_block_1 = false; + continue; + } + + if (prim1 < prim2) + return -1; + else if (prim1 > prim2) + return 1; + else if (getStrength() == PRIMARY) + continue; + + // Check for secondary strength differences + int sec1 = CollationElementIterator.secondaryOrder(ord1); + int sec2 = CollationElementIterator.secondaryOrder(ord2); + + if (sec1 < sec2) + return -1; + else if (sec1 > sec2) + return 1; + else if (getStrength() == SECONDARY) + continue; + + // Check for tertiary differences + int tert1 = CollationElementIterator.tertiaryOrder(ord1); + int tert2 = CollationElementIterator.tertiaryOrder(ord2); + + if (tert1 < tert2) + return -1; + else if (tert1 > tert2) + return 1; + else if (getStrength() == TERTIARY) + continue; + + // Apparently JDK does this (at least for my test case). + return ord1block.key.compareTo(ord2block.key); + } + } + + /** + * This method tests this object for equality against the specified + * object. This will be true if and only if the specified object is + * another reference to this object. + * + * @param obj The Object to compare against this object. + * + * @return true if the specified object is equal to this object, + * false otherwise. + */ + public boolean equals(Object obj) + { + if (obj == this) + return true; + else + return false; + } + + /** + * This method builds a default collation element without invoking + * the database created from the rules passed to the constructor. + * + * @param c Character which needs a collation element. + * @return A valid brand new CollationElement instance. + */ + CollationElement getDefaultElement(char c) + { + int v; + + // Preliminary support for generic accent sorting inversion (I don't know if all + // characters in the range should be sorted backward). This is the place + // to fix this if needed. + if (inverseAccentComparison && (c >= 0x02B9 && c <= 0x0361)) + v = 0x0361 - ((int) c - 0x02B9); + else + v = (short) c; + return new CollationElement("" + c, last_primary_value + v, + (short) 0, (short) 0, (short) 0, null, false); + } + + /** + * This method builds a default collation element for an accented character + * without invoking the database created from the rules passed to the constructor. + * + * @param c Character which needs a collation element. + * @return A valid brand new CollationElement instance. + */ + CollationElement getDefaultAccentedElement(char c) + { + int v; + + // Preliminary support for generic accent sorting inversion (I don't know if all + // characters in the range should be sorted backward). This is the place + // to fix this if needed. + if (inverseAccentComparison && (c >= 0x02B9 && c <= 0x0361)) + v = 0x0361 - ((int) c - 0x02B9); + else + v = (short) c; + return new CollationElement("" + c, (short) 0, + (short) 0, (short) (last_tertiary_value + v), (short) 0, null, false); + } + + /** + * This method returns an instance for CollationElementIterator + * for the specified String under the collation rules for this + * object. + * + * @param source The String to return the + * CollationElementIterator instance for. + * + * @return A CollationElementIterator for the specified + * String. + */ + public CollationElementIterator getCollationElementIterator(String source) + { + return new CollationElementIterator(this, source); + } + + /** + * This method returns an instance of CollationElementIterator + * for the String represented by the specified + * CharacterIterator. + * + * @param source The CharacterIterator with the desired String. + * + * @return A CollationElementIterator for the specified String. + */ + public CollationElementIterator getCollationElementIterator(CharacterIterator source) + { + StringBuffer expand = new StringBuffer(""); + + // Right now we assume that we will read from the beginning of the string. + for (char c = source.first(); + c != CharacterIterator.DONE; + c = source.next()) + decomposeCharacter(c, expand); + + return getCollationElementIterator(expand.toString()); + } + + /** + * This method returns an instance of CollationKey for the + * specified String. The object returned will have a + * more efficient mechanism for its comparison function that could + * provide speed benefits if multiple comparisons are performed, such + * as during a sort. + * + * @param source The String to create a CollationKey for. + * + * @return A CollationKey for the specified String. + */ + public CollationKey getCollationKey(String source) + { + CollationElementIterator cei = getCollationElementIterator(source); + ArrayList vect = new ArrayList(); + + int ord = cei.next(); + cei.reset(); //set to start of string + + while (ord != CollationElementIterator.NULLORDER) + { + // If the primary order is null, it means this is an ignorable + // character. + if (CollationElementIterator.primaryOrder(ord) == 0) + { + ord = cei.next(); + continue; + } + switch (getStrength()) + { + case PRIMARY: + ord = CollationElementIterator.primaryOrder(ord); + break; + + case SECONDARY: + ord = CollationElementIterator.primaryOrder(ord) << 8; + ord |= CollationElementIterator.secondaryOrder(ord); + + default: + break; + } + + vect.add(new Integer(ord)); + ord = cei.next(); //increment to next key + } + + Object[] objarr = vect.toArray(); + byte[] key = new byte[objarr.length * 4]; + + for (int i = 0; i < objarr.length; i++) + { + int j = ((Integer) objarr[i]).intValue(); + key [i * 4] = (byte) ((j & 0xFF000000) >> 24); + key [i * 4 + 1] = (byte) ((j & 0x00FF0000) >> 16); + key [i * 4 + 2] = (byte) ((j & 0x0000FF00) >> 8); + key [i * 4 + 3] = (byte) (j & 0x000000FF); + } + + return new CollationKey(this, source, key); + } + + /** + * This method returns a String containing the collation rules + * for this object. + * + * @return The collation rules for this object. + */ + public String getRules() + { + return rules; + } + + /** + * This method returns a hash value for this object. + * + * @return A hash value for this object. + */ + public int hashCode() + { + return System.identityHashCode(this); + } +} diff --git a/libjava/classpath/java/text/SimpleDateFormat.java b/libjava/classpath/java/text/SimpleDateFormat.java new file mode 100644 index 00000000000..789cb83d86d --- /dev/null +++ b/libjava/classpath/java/text/SimpleDateFormat.java @@ -0,0 +1,1257 @@ +/* SimpleDateFormat.java -- A class for parsing/formating simple + date constructs + Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005 + Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +import gnu.java.text.AttributedFormatBuffer; +import gnu.java.text.FormatBuffer; +import gnu.java.text.FormatCharacterIterator; +import gnu.java.text.StringFormatBuffer; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.Locale; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * SimpleDateFormat provides convenient methods for parsing and formatting + * dates using Gregorian calendars (see java.util.GregorianCalendar). + */ +public class SimpleDateFormat extends DateFormat +{ + /** + * This class is used by SimpleDateFormat as a + * compiled representation of a format string. The field + * ID, size, and character used are stored for each sequence + * of pattern characters. + */ + private class CompiledField + { + /** + * The ID of the field within the local pattern characters. + * Package private for use in out class. + */ + int field; + + /** + * The size of the character sequence. + * Package private for use in out class. + */ + int size; + + /** + * The character used. + */ + private char character; + + /** + * Constructs a compiled field using the + * the given field ID, size and character + * values. + * + * @param f the field ID. + * @param s the size of the field. + * @param c the character used. + */ + public CompiledField(int f, int s, char c) + { + field = f; + size = s; + character = c; + } + + /** + * Retrieves the ID of the field relative to + * the local pattern characters. + */ + public int getField() + { + return field; + } + + /** + * Retrieves the size of the character sequence. + */ + public int getSize() + { + return size; + } + + /** + * Retrieves the character used in the sequence. + */ + public char getCharacter() + { + return character; + } + + /** + * Returns a String representation + * of the compiled field, primarily for debugging + * purposes. + * + * @return a String representation. + */ + public String toString() + { + StringBuffer builder; + + builder = new StringBuffer(getClass().getName()); + builder.append("[field="); + builder.append(field); + builder.append(", size="); + builder.append(size); + builder.append(", character="); + builder.append(character); + builder.append("]"); + + return builder.toString(); + } + } + + /** + * A list of CompiledFields, + * representing the compiled version of the pattern. + * + * @see CompiledField + * @serial Ignored. + */ + private transient ArrayList tokens; + + /** + * The localised data used in formatting, + * such as the day and month names in the local + * language, and the localized pattern characters. + * + * @see DateFormatSymbols + * @serial The localisation data. May not be null. + */ + private DateFormatSymbols formatData; + + /** + * The date representing the start of the century + * used for interpreting two digit years. For + * example, 24/10/2004 would cause two digit + * years to be interpreted as representing + * the years between 2004 and 2104. + * + * @see get2DigitYearStart() + * @see set2DigitYearStart(java.util.Date) + * @see Date + * @serial The start date of the century for parsing two digit years. + * May not be null. + */ + private Date defaultCenturyStart; + + /** + * The year at which interpretation of two + * digit years starts. + * + * @see get2DigitYearStart() + * @see set2DigitYearStart(java.util.Date) + * @serial Ignored. + */ + private transient int defaultCentury; + + /** + * The non-localized pattern string. This + * only ever contains the pattern characters + * stored in standardChars. Localized patterns + * are translated to this form. + * + * @see applyPattern(String) + * @see applyLocalizedPattern(String) + * @see toPattern() + * @see toLocalizedPattern() + * @serial The non-localized pattern string. May not be null. + */ + private String pattern; + + /** + * The version of serialized data used by this class. + * Version 0 only includes the pattern and formatting + * data. Version 1 adds the start date for interpreting + * two digit years. + * + * @serial This specifies the version of the data being serialized. + * Version 0 (or no version) specifies just pattern + * and formatData. Version 1 adds + * the defaultCenturyStart. This implementation + * always writes out version 1 data. + */ + private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier + + /** + * For compatability. + */ + private static final long serialVersionUID = 4774881970558875024L; + + // This string is specified in the root of the CLDR. We set it here + // rather than doing a DateFormatSymbols(Locale.US).getLocalPatternChars() + // since someone could theoretically change those values (though unlikely). + private static final String standardChars = "GyMdkHmsSEDFwWahKzYeugAZ"; + + /** + * Reads the serialized version of this object. + * If the serialized data is only version 0, + * then the date for the start of the century + * for interpreting two digit years is computed. + * The pattern is parsed and compiled following the process + * of reading in the serialized data. + * + * @param stream the object stream to read the data from. + * @throws IOException if an I/O error occurs. + * @throws ClassNotFoundException if the class of the serialized data + * could not be found. + * @throws InvalidObjectException if the pattern is invalid. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + if (serialVersionOnStream < 1) + { + computeCenturyStart (); + serialVersionOnStream = 1; + } + else + // Ensure that defaultCentury gets set. + set2DigitYearStart(defaultCenturyStart); + + // Set up items normally taken care of by the constructor. + tokens = new ArrayList(); + try + { + compileFormat(pattern); + } + catch (IllegalArgumentException e) + { + throw new InvalidObjectException("The stream pattern was invalid."); + } + } + + /** + * Compiles the supplied non-localized pattern into a form + * from which formatting and parsing can be performed. + * This also detects errors in the pattern, which will + * be raised on later use of the compiled data. + * + * @param pattern the non-localized pattern to compile. + * @throws IllegalArgumentException if the pattern is invalid. + */ + private void compileFormat(String pattern) + { + // Any alphabetical characters are treated as pattern characters + // unless enclosed in single quotes. + + char thisChar; + int pos; + int field; + CompiledField current = null; + + for (int i=0; i= 'A' && thisChar <= 'Z') + || (thisChar >= 'a' && thisChar <= 'z')) { + // Not a valid letter + throw new IllegalArgumentException("Invalid letter " + thisChar + + "encountered at character " + i + + "."); + } else if (thisChar == '\'') { + // Quoted text section; skip to next single quote + pos = pattern.indexOf('\'',i+1); + if (pos == -1) { + throw new IllegalArgumentException("Quotes starting at character " + + i + " not closed."); + } + if ((pos+1 < pattern.length()) && (pattern.charAt(pos+1) == '\'')) { + tokens.add(pattern.substring(i+1,pos+1)); + } else { + tokens.add(pattern.substring(i+1,pos)); + } + i = pos; + } else { + // A special character + tokens.add(new Character(thisChar)); + } + } else { + // A valid field + if ((current != null) && (field == current.field)) { + current.size++; + } else { + current = new CompiledField(field,1,thisChar); + tokens.add(current); + } + } + } + } + + /** + * Returns a string representation of this + * class. + * + * @return a string representation of the SimpleDateFormat + * instance. + */ + public String toString() + { + StringBuffer output = new StringBuffer(getClass().getName()); + output.append("[tokens="); + output.append(tokens); + output.append(", formatData="); + output.append(formatData); + output.append(", defaultCenturyStart="); + output.append(defaultCenturyStart); + output.append(", defaultCentury="); + output.append(defaultCentury); + output.append(", pattern="); + output.append(pattern); + output.append(", serialVersionOnStream="); + output.append(serialVersionOnStream); + output.append(", standardChars="); + output.append(standardChars); + output.append("]"); + return output.toString(); + } + + /** + * Constructs a SimpleDateFormat using the default pattern for + * the default locale. + */ + public SimpleDateFormat() + { + /* + * There does not appear to be a standard API for determining + * what the default pattern for a locale is, so use package-scope + * variables in DateFormatSymbols to encapsulate this. + */ + super(); + Locale locale = Locale.getDefault(); + calendar = new GregorianCalendar(locale); + computeCenturyStart(); + tokens = new ArrayList(); + formatData = new DateFormatSymbols(locale); + pattern = (formatData.dateFormats[DEFAULT] + ' ' + + formatData.timeFormats[DEFAULT]); + compileFormat(pattern); + numberFormat = NumberFormat.getInstance(locale); + numberFormat.setGroupingUsed (false); + numberFormat.setParseIntegerOnly (true); + numberFormat.setMaximumFractionDigits (0); + } + + /** + * Creates a date formatter using the specified non-localized pattern, + * with the default DateFormatSymbols for the default locale. + * + * @param pattern the pattern to use. + * @throws NullPointerException if the pattern is null. + * @throws IllegalArgumentException if the pattern is invalid. + */ + public SimpleDateFormat(String pattern) + { + this(pattern, Locale.getDefault()); + } + + /** + * Creates a date formatter using the specified non-localized pattern, + * with the default DateFormatSymbols for the given locale. + * + * @param pattern the non-localized pattern to use. + * @param locale the locale to use for the formatting symbols. + * @throws NullPointerException if the pattern is null. + * @throws IllegalArgumentException if the pattern is invalid. + */ + public SimpleDateFormat(String pattern, Locale locale) + { + super(); + calendar = new GregorianCalendar(locale); + computeCenturyStart(); + tokens = new ArrayList(); + formatData = new DateFormatSymbols(locale); + compileFormat(pattern); + this.pattern = pattern; + numberFormat = NumberFormat.getInstance(locale); + numberFormat.setGroupingUsed (false); + numberFormat.setParseIntegerOnly (true); + numberFormat.setMaximumFractionDigits (0); + } + + /** + * Creates a date formatter using the specified non-localized + * pattern. The specified DateFormatSymbols will be used when + * formatting. + * + * @param pattern the non-localized pattern to use. + * @param formatData the formatting symbols to use. + * @throws NullPointerException if the pattern or formatData is null. + * @throws IllegalArgumentException if the pattern is invalid. + */ + public SimpleDateFormat(String pattern, DateFormatSymbols formatData) + { + super(); + calendar = new GregorianCalendar(); + computeCenturyStart (); + tokens = new ArrayList(); + if (formatData == null) + throw new NullPointerException("formatData"); + this.formatData = formatData; + compileFormat(pattern); + this.pattern = pattern; + numberFormat = NumberFormat.getInstance(); + numberFormat.setGroupingUsed (false); + numberFormat.setParseIntegerOnly (true); + numberFormat.setMaximumFractionDigits (0); + } + + /** + * This method returns a string with the formatting pattern being used + * by this object. This string is unlocalized. + * + * @return The format string. + */ + public String toPattern() + { + return pattern; + } + + /** + * This method returns a string with the formatting pattern being used + * by this object. This string is localized. + * + * @return The format string. + */ + public String toLocalizedPattern() + { + String localChars = formatData.getLocalPatternChars(); + return translateLocalizedPattern(pattern, standardChars, localChars); + } + + /** + * This method sets the formatting pattern that should be used by this + * object. This string is not localized. + * + * @param pattern The new format pattern. + * @throws NullPointerException if the pattern is null. + * @throws IllegalArgumentException if the pattern is invalid. + */ + public void applyPattern(String pattern) + { + tokens = new ArrayList(); + compileFormat(pattern); + this.pattern = pattern; + } + + /** + * This method sets the formatting pattern that should be used by this + * object. This string is localized. + * + * @param pattern The new format pattern. + * @throws NullPointerException if the pattern is null. + * @throws IllegalArgumentException if the pattern is invalid. + */ + public void applyLocalizedPattern(String pattern) + { + String localChars = formatData.getLocalPatternChars(); + pattern = translateLocalizedPattern(pattern, localChars, standardChars); + applyPattern(pattern); + } + + /** + * Translates either from or to a localized variant of the pattern + * string. For example, in the German locale, 't' (for 'tag') is + * used instead of 'd' (for 'date'). This method translates + * a localized pattern (such as 'ttt') to a non-localized pattern + * (such as 'ddd'), or vice versa. Non-localized patterns use + * a standard set of characters, which match those of the U.S. English + * locale. + * + * @param pattern the pattern to translate. + * @param oldChars the old set of characters (used in the pattern). + * @param newChars the new set of characters (which will be used in the + * pattern). + * @return a version of the pattern using the characters in + * newChars. + */ + private String translateLocalizedPattern(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(); + } + + /** + * Returns the start of the century used for two digit years. + * + * @return A Date representing the start of the century + * for two digit years. + */ + public Date get2DigitYearStart() + { + return defaultCenturyStart; + } + + /** + * Sets the start of the century used for two digit years. + * + * @param date A Date representing the start of the century for + * two digit years. + */ + public void set2DigitYearStart(Date date) + { + defaultCenturyStart = date; + calendar.clear(); + calendar.setTime(date); + int year = calendar.get(Calendar.YEAR); + defaultCentury = year - (year % 100); + } + + /** + * This method returns a copy of the format symbol information used + * for parsing and formatting dates. + * + * @return a copy of the date format symbols. + */ + public DateFormatSymbols getDateFormatSymbols() + { + return (DateFormatSymbols) formatData.clone(); + } + + /** + * This method sets the format symbols information used for parsing + * and formatting dates. + * + * @param formatData The date format symbols. + * @throws NullPointerException if formatData is null. + */ + public void setDateFormatSymbols(DateFormatSymbols formatData) + { + if (formatData == null) + { + throw new + NullPointerException("The supplied format data was null."); + } + this.formatData = formatData; + } + + /** + * This methods tests whether the specified object is equal to this + * object. This will be true if and only if the specified object: + *

+ *

    + *
  • Is not null.
  • + *
  • Is an instance of SimpleDateFormat.
  • + *
  • Is equal to this object at the superclass (i.e., DateFormat) + * level.
  • + *
  • Has the same formatting pattern.
  • + *
  • Is using the same formatting symbols.
  • + *
  • Is using the same century for two digit years.
  • + *
+ * + * @param obj The object to compare for equality against. + * + * @return true if the specified object is equal to this object, + * false otherwise. + */ + public boolean equals(Object o) + { + if (!super.equals(o)) + return false; + + if (!(o instanceof SimpleDateFormat)) + return false; + + SimpleDateFormat sdf = (SimpleDateFormat)o; + + if (defaultCentury != sdf.defaultCentury) + return false; + + if (!toPattern().equals(sdf.toPattern())) + return false; + + if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols())) + return false; + + return true; + } + + /** + * This method returns a hash value for this object. + * + * @return A hash value for this object. + */ + public int hashCode() + { + return super.hashCode() ^ toPattern().hashCode() ^ defaultCentury ^ + getDateFormatSymbols().hashCode(); + } + + + /** + * Formats the date input according to the format string in use, + * appending to the specified StringBuffer. The input StringBuffer + * is returned as output for convenience. + */ + private void formatWithAttribute(Date date, FormatBuffer buffer, FieldPosition pos) + { + String temp; + AttributedCharacterIterator.Attribute attribute; + calendar.setTime(date); + + // go through vector, filling in fields where applicable, else toString + Iterator iter = tokens.iterator(); + while (iter.hasNext()) + { + Object o = iter.next(); + if (o instanceof CompiledField) + { + CompiledField cf = (CompiledField) o; + int beginIndex = buffer.length(); + + switch (cf.getField()) + { + case ERA_FIELD: + buffer.append (formatData.eras[calendar.get (Calendar.ERA)], DateFormat.Field.ERA); + break; + case YEAR_FIELD: + // If we have two digits, then we truncate. Otherwise, we + // use the size of the pattern, and zero pad. + buffer.setDefaultAttribute (DateFormat.Field.YEAR); + if (cf.getSize() == 2) + { + temp = "00"+String.valueOf (calendar.get (Calendar.YEAR)); + buffer.append (temp.substring (temp.length() - 2)); + } + else + withLeadingZeros (calendar.get (Calendar.YEAR), cf.getSize(), buffer); + break; + case MONTH_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.MONTH); + if (cf.getSize() < 3) + withLeadingZeros (calendar.get (Calendar.MONTH) + 1, cf.getSize(), buffer); + else if (cf.getSize() < 4) + buffer.append (formatData.shortMonths[calendar.get (Calendar.MONTH)]); + else + buffer.append (formatData.months[calendar.get (Calendar.MONTH)]); + break; + case DATE_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_MONTH); + withLeadingZeros (calendar.get (Calendar.DATE), cf.getSize(), buffer); + break; + case HOUR_OF_DAY1_FIELD: // 1-24 + buffer.setDefaultAttribute(DateFormat.Field.HOUR_OF_DAY1); + withLeadingZeros ( ((calendar.get (Calendar.HOUR_OF_DAY) + 23) % 24) + 1, + cf.getSize(), buffer); + break; + case HOUR_OF_DAY0_FIELD: // 0-23 + buffer.setDefaultAttribute (DateFormat.Field.HOUR_OF_DAY0); + withLeadingZeros (calendar.get (Calendar.HOUR_OF_DAY), cf.getSize(), buffer); + break; + case MINUTE_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.MINUTE); + withLeadingZeros (calendar.get (Calendar.MINUTE), + cf.getSize(), buffer); + break; + case SECOND_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.SECOND); + withLeadingZeros(calendar.get (Calendar.SECOND), + cf.getSize(), buffer); + break; + case MILLISECOND_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.MILLISECOND); + withLeadingZeros (calendar.get (Calendar.MILLISECOND), cf.getSize(), buffer); + break; + case DAY_OF_WEEK_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK); + if (cf.getSize() < 4) + buffer.append (formatData.shortWeekdays[calendar.get (Calendar.DAY_OF_WEEK)]); + else + buffer.append (formatData.weekdays[calendar.get (Calendar.DAY_OF_WEEK)]); + break; + case DAY_OF_YEAR_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_YEAR); + withLeadingZeros (calendar.get (Calendar.DAY_OF_YEAR), cf.getSize(), buffer); + break; + case DAY_OF_WEEK_IN_MONTH_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK_IN_MONTH); + withLeadingZeros (calendar.get (Calendar.DAY_OF_WEEK_IN_MONTH), + cf.getSize(), buffer); + break; + case WEEK_OF_YEAR_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_YEAR); + withLeadingZeros (calendar.get (Calendar.WEEK_OF_YEAR), + cf.getSize(), buffer); + break; + case WEEK_OF_MONTH_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_MONTH); + withLeadingZeros (calendar.get (Calendar.WEEK_OF_MONTH), + cf.getSize(), buffer); + break; + case AM_PM_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.AM_PM); + buffer.append (formatData.ampms[calendar.get (Calendar.AM_PM)]); + break; + case HOUR1_FIELD: // 1-12 + buffer.setDefaultAttribute (DateFormat.Field.HOUR1); + withLeadingZeros (((calendar.get (Calendar.HOUR) + 11) % 12) + 1, + cf.getSize(), buffer); + break; + case HOUR0_FIELD: // 0-11 + buffer.setDefaultAttribute (DateFormat.Field.HOUR0); + withLeadingZeros (calendar.get (Calendar.HOUR), cf.getSize(), buffer); + break; + case TIMEZONE_FIELD: + buffer.setDefaultAttribute (DateFormat.Field.TIME_ZONE); + TimeZone zone = calendar.getTimeZone(); + boolean isDST = calendar.get (Calendar.DST_OFFSET) != 0; + // FIXME: XXX: This should be a localized time zone. + String zoneID = zone.getDisplayName + (isDST, cf.getSize() > 3 ? TimeZone.LONG : TimeZone.SHORT); + buffer.append (zoneID); + break; + case RFC822_TIMEZONE_FIELD: + buffer.setDefaultAttribute(DateFormat.Field.RFC822_TIME_ZONE); + int pureMinutes = (calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET)) / (1000 * 60); + String sign = (pureMinutes < 0) ? "-" : "+"; + int hours = pureMinutes / 60; + int minutes = pureMinutes % 60; + buffer.append(sign); + withLeadingZeros(hours, 2, buffer); + withLeadingZeros(minutes, 2, buffer); + break; + default: + throw new IllegalArgumentException ("Illegal pattern character " + + cf.getCharacter()); + } + if (pos != null && (buffer.getDefaultAttribute() == pos.getFieldAttribute() + || cf.getField() == pos.getField())) + { + pos.setBeginIndex(beginIndex); + pos.setEndIndex(buffer.length()); + } + } + else + { + buffer.append(o.toString(), null); + } + } + } + + public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos) + { + formatWithAttribute(date, new StringFormatBuffer (buffer), pos); + + return buffer; + } + + public AttributedCharacterIterator formatToCharacterIterator(Object date) + throws IllegalArgumentException + { + if (date == null) + throw new NullPointerException("null argument"); + if (!(date instanceof Date)) + throw new IllegalArgumentException("argument should be an instance of java.util.Date"); + + AttributedFormatBuffer buf = new AttributedFormatBuffer(); + formatWithAttribute((Date)date, buf, + null); + buf.sync(); + + return new FormatCharacterIterator(buf.getBuffer().toString(), + buf.getRanges(), + buf.getAttributes()); + } + + private void withLeadingZeros(int value, int length, FormatBuffer buffer) + { + String valStr = String.valueOf(value); + for (length -= valStr.length(); length > 0; length--) + buffer.append('0'); + buffer.append(valStr); + } + + private 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; + } + + /** + * This method parses the specified string into a date. + * + * @param dateStr The date string to parse. + * @param pos The input and output parse position + * + * @return The parsed date, or null if the string cannot be + * parsed. + */ + public Date parse (String dateStr, ParsePosition pos) + { + int fmt_index = 0; + int fmt_max = pattern.length(); + + calendar.clear(); + boolean saw_timezone = false; + int quote_start = -1; + boolean is2DigitYear = false; + try + { + 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 (dateStr, 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 (dateStr, pos, ch)) + return null; + continue; + } + + // We've arrived at a potential pattern character in the + // pattern. + int fmt_count = 1; + while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch) + { + ++fmt_count; + } + + // We might need to limit the number of digits to parse in + // some cases. We look to the next pattern character to + // decide. + boolean limit_digits = false; + if (fmt_index < fmt_max + && standardChars.indexOf(pattern.charAt(fmt_index)) >= 0) + limit_digits = true; + --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; + int offset = 0; + boolean maybe2DigitYear = false; + boolean oneBasedHour = false; + boolean oneBasedHourOfDay = false; + Integer simpleOffset; + String[] set1 = null; + String[] set2 = null; + 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; + set1 = formatData.getWeekdays(); + set2 = formatData.getShortWeekdays(); + 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 (fmt_count <= 2) + offset = -1; + else + { + is_numeric = false; + set1 = formatData.getMonths(); + set2 = formatData.getShortMonths(); + } + break; + case 'y': + calendar_field = Calendar.YEAR; + if (fmt_count <= 2) + maybe2DigitYear = true; + break; + case 'K': + calendar_field = Calendar.HOUR; + break; + case 'h': + calendar_field = Calendar.HOUR; + oneBasedHour = true; + break; + case 'H': + calendar_field = Calendar.HOUR_OF_DAY; + break; + case 'k': + calendar_field = Calendar.HOUR_OF_DAY; + oneBasedHourOfDay = true; + 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; + set1 = formatData.getAmPmStrings(); + break; + case 'z': + 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.ZONE_OFFSET; + String[][] zoneStrings = formatData.getZoneStrings(); + int zoneCount = zoneStrings.length; + int index = pos.getIndex(); + boolean found_zone = false; + simpleOffset = computeOffset(dateStr.substring(index), pos); + if (simpleOffset != null) + { + found_zone = true; + saw_timezone = true; + calendar.set(Calendar.DST_OFFSET, 0); + offset = simpleOffset.intValue(); + } + else + { + for (int j = 0; j < zoneCount; j++) + { + String[] strings = zoneStrings[j]; + int k; + for (k = 0; k < strings.length; ++k) + { + if (dateStr.startsWith(strings[k], index)) + break; + } + if (k != strings.length) + { + found_zone = true; + saw_timezone = true; + TimeZone tz = TimeZone.getTimeZone (strings[0]); + // Check if it's a DST zone or ordinary + if(k == 3 || k == 4) + calendar.set (Calendar.DST_OFFSET, tz.getDSTSavings()); + else + calendar.set (Calendar.DST_OFFSET, 0); + offset = tz.getRawOffset (); + 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; + int index = -1; + if (is_numeric) + { + numberFormat.setMinimumIntegerDigits(fmt_count); + if (limit_digits) + numberFormat.setMaximumIntegerDigits(fmt_count); + if (maybe2DigitYear) + index = pos.getIndex(); + Number n = numberFormat.parse(dateStr, pos); + if (pos == null || ! (n instanceof Long)) + return null; + value = n.intValue() + offset; + } + else if (set1 != null) + { + index = pos.getIndex(); + int i; + boolean found = false; + for (i = offset; i < set1.length; ++i) + { + if (set1[i] != null) + if (dateStr.toUpperCase().startsWith(set1[i].toUpperCase(), + index)) + { + found = true; + pos.setIndex(index + set1[i].length()); + break; + } + } + if (!found && set2 != null) + { + for (i = offset; i < set2.length; ++i) + { + if (set2[i] != null) + if (dateStr.toUpperCase().startsWith(set2[i].toUpperCase(), + index)) + { + found = true; + pos.setIndex(index + set2[i].length()); + break; + } + } + } + if (!found) + { + pos.setErrorIndex(index); + return null; + } + value = i; + } + else + value = offset; + + if (maybe2DigitYear) + { + // Parse into default century if the numeric year string has + // exactly 2 digits. + int digit_count = pos.getIndex() - index; + if (digit_count == 2) + { + is2DigitYear = true; + value += defaultCentury; + } + } + + // Calendar uses 0-based hours. + // I.e. 00:00 AM is midnight, not 12 AM or 24:00 + if (oneBasedHour && value == 12) + value = 0; + + if (oneBasedHourOfDay && value == 24) + value = 0; + + // Assign the value and move on. + calendar.set(calendar_field, value); + } + + if (is2DigitYear) + { + // Apply the 80-20 heuristic to dermine the full year based on + // defaultCenturyStart. + int year = calendar.get(Calendar.YEAR); + if (calendar.getTime().compareTo(defaultCenturyStart) < 0) + calendar.set(Calendar.YEAR, year + 100); + } + if (! saw_timezone) + { + // Use the real rules to determine whether or not this + // particular time is in daylight savings. + calendar.clear (Calendar.DST_OFFSET); + calendar.clear (Calendar.ZONE_OFFSET); + } + return calendar.getTime(); + } + catch (IllegalArgumentException x) + { + pos.setErrorIndex(pos.getIndex()); + return null; + } + } + + /** + *

+ * Computes the time zone offset in milliseconds + * relative to GMT, based on the supplied + * String representation. + *

+ *

+ * The supplied String must be a three + * or four digit signed number, with an optional 'GMT' + * prefix. The first one or two digits represents the hours, + * while the last two represent the minutes. The + * two sets of digits can optionally be separated by + * ':'. The mandatory sign prefix (either '+' or '-') + * indicates the direction of the offset from GMT. + *

+ *

+ * For example, 'GMT+0200' specifies 2 hours after + * GMT, while '-05:00' specifies 5 hours prior to + * GMT. The special case of 'GMT' alone can be used + * to represent the offset, 0. + *

+ *

+ * If the String can not be parsed, + * the result will be null. The resulting offset + * is wrapped in an Integer object, in + * order to allow such failure to be represented. + *

+ * + * @param zoneString a string in the form + * (GMT)? sign hours : minutes + * where sign = '+' or '-', hours + * is a one or two digits representing + * a number between 0 and 23, and + * minutes is two digits representing + * a number between 0 and 59. + * @return the parsed offset, or null if parsing + * failed. + */ + private Integer computeOffset(String zoneString, ParsePosition pos) + { + Pattern pattern = + Pattern.compile("(GMT)?([+-])([012])?([0-9]):?([0-9]{2})"); + Matcher matcher = pattern.matcher(zoneString); + + // Match from start, but ignore trailing parts + boolean hasAll = matcher.lookingAt(); + try + { + // Do we have at least the sign, hour and minute? + matcher.group(2); + matcher.group(4); + matcher.group(5); + } + catch (IllegalStateException ise) + { + hasAll = false; + } + if (hasAll) + { + int sign = matcher.group(2).equals("+") ? 1 : -1; + int hour = Integer.parseInt(matcher.group(4)); + if (!matcher.group(3).equals("")) + hour += (Integer.parseInt(matcher.group(3)) * 10); + int minutes = Integer.parseInt(matcher.group(5)); + + if (hour > 23) + return null; + int offset = sign * ((hour * 60) + minutes) * 60000; + + // advance the index + pos.setIndex(pos.getIndex() + matcher.end()); + return new Integer(offset); + } + else if (zoneString.startsWith("GMT")) + { + pos.setIndex(pos.getIndex() + 3); + return new Integer(0); + } + return null; + } + + // Compute the start of the current century as defined by + // get2DigitYearStart. + private void computeCenturyStart() + { + int year = calendar.get(Calendar.YEAR); + calendar.set(Calendar.YEAR, year - 80); + set2DigitYearStart(calendar.getTime()); + } + + /** + * Returns a copy of this instance of + * SimpleDateFormat. The copy contains + * clones of the formatting symbols and the 2-digit + * year century start date. + */ + public Object clone() + { + SimpleDateFormat clone = (SimpleDateFormat) super.clone(); + clone.setDateFormatSymbols((DateFormatSymbols) formatData.clone()); + clone.set2DigitYearStart((Date) defaultCenturyStart.clone()); + return clone; + } + +} diff --git a/libjava/classpath/java/text/StringCharacterIterator.java b/libjava/classpath/java/text/StringCharacterIterator.java new file mode 100644 index 00000000000..e3adc857e51 --- /dev/null +++ b/libjava/classpath/java/text/StringCharacterIterator.java @@ -0,0 +1,356 @@ +/* StringCharacterIterator.java -- Iterate over a character range in a string + Copyright (C) 1998, 1999, 2001, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.text; + +/** + * This class iterates over a range of characters in a String. + * For a given range of text, a beginning and ending index, + * as well as a current index are defined. These values can be queried + * by the methods in this interface. Additionally, various methods allow + * the index to be set. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Tom Tromey (tromey@cygnus.com) + */ +public final class StringCharacterIterator implements CharacterIterator +{ + /** + * This is the string to iterate over + */ + private String text; + + /** + * This is the value of the start position of the text range. + */ + private int begin; + + /** + * This is the value of the ending position of the text range. + */ + private int end; + + /** + * This is the current value of the scan index. + */ + private int index; + + /** + * This method initializes a new instance of + * StringCharacterIterator to iterate over the entire + * text of the specified String. The initial index + * value will be set to the first character in the string. + * + * @param text The String to iterate through. + */ + public StringCharacterIterator (String text) + { + this (text, 0, text.length (), 0); + } + + /*************************************************************************/ + + /** + * This method initializes a new instance of + * StringCharacterIterator to iterate over the entire + * text of the specified String. The initial index + * value will be set to the specified value. + * + * @param text The String to iterate through. + * @param index The initial index position. + */ + public StringCharacterIterator (String text, int index) + { + this (text, 0, text.length (), index); + } + + /*************************************************************************/ + + /** + * This method initializes a new instance of + * StringCharacterIterator that iterates over the text + * in a subrange of the specified String. The + * beginning and end of the range are specified by the caller, as is + * the initial index position. + * + * @param text The String to iterate through. + * @param begin The beginning position in the character range. + * @param end The ending position in the character range. + * @param index The initial index position. + * + * @param IllegalArgumentException If any of the range values are + * invalid. + */ + public StringCharacterIterator (String text, int begin, int end, int index) + { + int len = text.length (); + + if ((begin < 0) || (begin > len)) + throw new IllegalArgumentException ("Bad begin position"); + + if ((end < begin) || (end > len)) + throw new IllegalArgumentException ("Bad end position"); + + if ((index < begin) || (index > end)) + throw new IllegalArgumentException ("Bad initial index position"); + + this.text = text; + this.begin = begin; + this.end = end; + this.index = index; + } + + /** + * This is a package level constructor that copies the text out of + * an existing StringCharacterIterator and resets the beginning and + * ending index. + * + * @param scci The StringCharacterIterator to copy the info from + * @param begin The beginning index of the range we are interested in. + * @param end The ending index of the range we are interested in. + */ + StringCharacterIterator (StringCharacterIterator sci, int begin, int end) + { + this (sci.text, begin, end, begin); + } + + /** + * This method returns the character at the current index position + * + * @return The character at the current index position. + */ + public char current () + { + return (index < end) ? text.charAt (index) : DONE; + } + + /*************************************************************************/ + + /** + * This method increments the current index and then returns the + * character at the new index value. If the index is already at + * getEndIndex () - 1, it will not be incremented. + * + * @return The character at the position of the incremented index + * value, or DONE if the index has reached + * getEndIndex () - 1. + */ + public char next () + { + if (index == end) + return DONE; + + ++index; + return current (); + } + + /*************************************************************************/ + + /** + * This method decrements the current index and then returns the + * character at the new index value. If the index value is already + * at the beginning index, it will not be decremented. + * + * @return The character at the position of the decremented index + * value, or DONE if index was already equal to the + * beginning index value. + */ + public char previous () + { + if (index == begin) + return DONE; + + --index; + return current (); + } + + /*************************************************************************/ + + /** + * This method sets the index value to the beginning of the range and returns + * the character there. + * + * @return The character at the beginning of the range, or + * DONE if the range is empty. + */ + public char first () + { + index = begin; + return current (); + } + + /*************************************************************************/ + + /** + * This method sets the index value to getEndIndex () - 1 and + * returns the character there. If the range is empty, then the index value + * will be set equal to the beginning index. + * + * @return The character at the end of the range, or + * DONE if the range is empty. + */ + public char last () + { + if (end == begin) + return DONE; + + index = end - 1; + return current (); + } + + /*************************************************************************/ + + /** + * This method returns the current value of the index. + * + * @return The current index value + */ + public int getIndex () + { + return index; + } + + /*************************************************************************/ + + /** + * This method sets the value of the index to the specified value, then + * returns the character at that position. + * + * @param index The new index value. + * + * @return The character at the new index value or DONE + * if the index value is equal to getEndIndex. + * + * @exception IllegalArgumentException If the specified index is not valid + */ + public char setIndex (int index) + { + if ((index < begin) || (index > end)) + throw new IllegalArgumentException ("Bad index specified"); + + this.index = index; + return current (); + } + + /*************************************************************************/ + + /** + * This method returns the character position of the first character in the + * range. + * + * @return The index of the first character in the range. + */ + public int getBeginIndex () + { + return begin; + } + + /*************************************************************************/ + + /** + * This method returns the character position of the end of the text range. + * This will actually be the index of the first character following the + * end of the range. In the event the text range is empty, this will be + * equal to the first character in the range. + * + * @return The index of the end of the range. + */ + public int getEndIndex () + { + return end; + } + + /*************************************************************************/ + + /** + * This method creates a copy of this CharacterIterator. + * + * @return A copy of this CharacterIterator. + */ + public Object clone () + { + return new StringCharacterIterator (text, begin, end, index); + } + + /*************************************************************************/ + + /** + * This method tests this object for equality againt the specified + * object. This will be true if and only if the specified object: + *

+ *

    + *
  • is not null.
  • + *
  • is an instance of StringCharacterIterator
  • + *
  • has the same text as this object
  • + *
  • has the same beginning, ending, and current index as this object.
  • + *
+ * + * @param obj The object to test for equality against. + * + * @return true if the specified object is equal to this + * object, false otherwise. + */ + public boolean equals (Object obj) + { + if (! (obj instanceof StringCharacterIterator)) + return false; + + StringCharacterIterator sci = (StringCharacterIterator) obj; + + return (begin == sci.begin + && end == sci.end + && index == sci.index + && text.equals (sci.text)); + } + + /*************************************************************************/ + + /** + * This method allows other classes in java.text to change the value + * of the underlying text being iterated through. + * + * @param text The new String to iterate through. + */ + public void setText (String text) + { + this.text = text; + this.begin = 0; + this.end = text.length (); + this.index = 0; + } +} diff --git a/libjava/classpath/java/text/class-dependencies.conf b/libjava/classpath/java/text/class-dependencies.conf new file mode 100644 index 00000000000..011b146ce10 --- /dev/null +++ b/libjava/classpath/java/text/class-dependencies.conf @@ -0,0 +1,220 @@ +# This property file contains dependencies of classes, methods, and +# field on other methods or classes. +# +# Syntax: +# +# : [... ] +# +# means that when is included, (... ) must +# be included as well. +# +# and are of the form +# +# +# +# or just +# +# +# +# Within dependencies, variables can be used. A variable is defined as +# follows: +# +# {variable}: value1 value2 ... value +# +# variables can be used on the right side of dependencies as follows: +# +# : com.bla.blu.{variable}.Class.m()V +# +# The use of the variable will expand to dependencies of the form +# +# : com.bla.blu.value1.Class.m()V +# : com.bla.blu.value2.Class.m()V +# ... +# : com.bla.blu.value.Class.m()V +# +# Variables can be redefined when building a system to select the +# required support for features like encodings, protocols, etc. +# +# Hints: +# +# - For methods and fields, the signature is mandatory. For +# specification, please see the Java Virtual Machine Specification by +# SUN. Unlike in the spec, field signatures (types) are in brackets. +# +# - Package names must be separated by '/' (and not '.'). E.g., +# java/lang/Class (this is necessary, because the '.' is used to +# separate method or field names from classes) +# +# - In case refers to a class, only the class itself will be +# included in the resulting binary, NOT necessarily all its methods +# and fields. If you want to refer to all methods and fields, you can +# write class.* as an abbreviation. +# +# - Abbreviations for packages are also possible: my/package/* means all +# methods and fields of all classes in my/package. +# +# - A line with a trailing '\' continues in the next line. + +# end of file + +# All locales supported are loaded via classes from java.text (see below) +# from class gnu/java/locale/LocaleInformation_ +# +# This introduces a dependency for all locales. To allow an easy selection +# and addition of locales, the library variable {text_locales} can be set to +# the set of supported locales. +# + +{text_locales}: \ + af_ZA \ + ar_AE \ + ar_BH \ + ar_DZ \ + ar_EG \ + ar_IN \ + ar_IQ \ + ar_JO \ + ar_KW \ + ar_LB \ + ar_LY \ + ar_MA \ + ar_OM \ + ar_QA \ + ar_SD \ + ar_SY \ + ar_TN \ + ar_YE \ + be_BY \ + bn_IN \ + br_FR \ + bs_BA \ + ca_ES \ + cs_CZ \ + cy_GB \ + da_DK \ + de \ + de_AT \ + de_BE \ + de_CH \ + de_DE \ + de_LU \ + el_GR \ + en \ + en_AU \ + en_BW \ + en_CA \ + en_DK \ + en_GB \ + en_HK \ + en_IE \ + en_IN \ + en_NZ \ + en_PH \ + en_SG \ + en_US \ + en_ZA \ + en_ZW \ + es_AR \ + es_BO \ + es_CL \ + es_CO \ + es_CR \ + es_DO \ + es_EC \ + es_ES \ + es_GT \ + es_HN \ + es_MX \ + es_NI \ + es_PA \ + es_PE \ + es_PR \ + es_PY \ + es_SV \ + es_US \ + es_UY \ + es_VE \ + et_EE \ + eu_ES \ + fa_IR \ + fi_FI \ + fo_FO \ + fr_BE \ + fr_CA \ + fr_CH \ + fr_FR \ + fr_LU \ + ga_IE \ + gd_GB \ + gl_ES \ + gv_GB \ + he_IL \ + hi_IN \ + hr_HR \ + hu_HU \ + id_ID \ + it_CH \ + it_IT \ + iw_IL \ + ja_JP \ + ka_GE \ + kl_GL \ + ko_KR \ + kw_GB \ + lt_LT \ + lv_LV \ + mi_NZ \ + mk_MK \ + mr_IN \ + mt_MT \ + nl \ + nl_BE \ + nl_NL \ + nn_NO \ + no_NO \ + oc_FR \ + pl_PL \ + pt_BR \ + pt_PT \ + ro_RO \ + ru_RU \ + ru_UA \ + se_NO \ + sk_SK \ + sl_SI \ + sq_AL \ + sr_YU \ + sv_FI \ + sv_SE \ + ta_IN \ + te_IN \ + tg_TJ \ + tl_PH \ + tr_TR \ + uk_UA \ + ur_PK \ + uz_UZ \ + vi_VN \ + yi_US \ + zh_CN \ + zh_HK \ + zh_SG \ + zh_TW + +java/text/Collator.getInstance(Ljava/util/Locale;)Ljava/text/Collator;: \ + gnu/java/locale/LocaleInformation_{text_locales}.* + +java/text/DateFormatSymbols.(Ljava/util/Locale;)V: \ + gnu/java/locale/LocaleInformation_{text_locales}.* + +java/text/DecimalFormatSymbols.(Ljava/util/Locale;)V: \ + gnu/java/locale/LocaleInformation_{text_locales}.* + +java/text/BreakIterator.getInstance(Ljava/lang/String;Ljava/util/Locale;)Ljava/text/BreakIterator;: \ + gnu/java/locale/LocaleInformation_{text_locales}.* + +java/text/NumberFormat.computeInstance(Ljava/util/Locale;Ljava/lang/String;Ljava/lang/String;)Ljava/text/NumberFormat;: \ + gnu/java/locale/LocaleInformation_{text_locales}.* + +java/text/DateFormat.computeInstance(IILjava/util/Locale;ZZ)Ljava/text/DateFormat;: \ + gnu/java/locale/LocaleInformation_{text_locales}.* diff --git a/libjava/classpath/java/text/package.html b/libjava/classpath/java/text/package.html new file mode 100644 index 00000000000..3c2e22ba5e4 --- /dev/null +++ b/libjava/classpath/java/text/package.html @@ -0,0 +1,47 @@ + + + + +GNU Classpath - java.text + + +

Classes to iterate over strings and to format texts according to a +specific locale.

+ + + -- cgit v1.2.1