diff options
Diffstat (limited to 'libjava/classpath/java/text')
32 files changed, 14933 insertions, 0 deletions
diff --git a/libjava/classpath/java/text/Annotation.java b/libjava/classpath/java/text/Annotation.java new file mode 100644 index 000000000..7c4d001f6 --- /dev/null +++ b/libjava/classpath/java/text/Annotation.java @@ -0,0 +1,112 @@ +/* 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 <code>Annotation</code> to + * wrapper the specified text attribute object. + * + * @param attrib The text attribute <code>Object</code> to wrapper. + */ +public +Annotation(Object attrib) +{ + this.attrib = attrib; +} + +/*************************************************************************/ + +/* + * Instance Variables + */ + +/** + * This method returns the text attribute object this <code>Annotation</code> + * instance is wrappering. + * + * @return The text attribute object for this <code>Annotation</code>. + */ +public Object +getValue() +{ + return(attrib); +} + +/*************************************************************************/ + +/** + * This method returns a <code>String</code> representation of this + * object. + * + * @return This object as a <code>String</code>. + */ +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 000000000..72c0ebbfe --- /dev/null +++ b/libjava/classpath/java/text/AttributedCharacterIterator.java @@ -0,0 +1,277 @@ +/* AttributedCharacterIterator.java -- Iterate over attributes + Copyright (C) 1998, 1999, 2004, 2006, 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 <code>CharacterIterator</code> interface + * in order to support iteration over character attributes as well as + * over the characters themselves. + * <p> + * 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. + * + * @since 1.2 + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @since 1.2 + */ +public interface AttributedCharacterIterator extends CharacterIterator +{ + /** + * 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 <code>Locale</code>. + */ + 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 <code>Annotation</code> which wrappers a + * <code>String</code>. + */ + 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 <code>Annotation</code> which wrapper + * a <code>String</code>. + */ + public static final Attribute INPUT_METHOD_SEGMENT = + new Attribute("input_method_segment"); + + /** + * The name of the attribute key + * @serial + */ + private String name; + + /** + * 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; + } + + /** + * Returns the name of this attribute. + * + * @return The attribute name + */ + protected String getName() + { + return name; + } + + /** + * Resolves an instance of + * <code>AttributedCharacterIterator.Attribute</code> + * 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 (getName().equals(READING.getName())) + return READING; + + if (getName().equals(LANGUAGE.getName())) + return LANGUAGE; + + if (getName().equals(INPUT_METHOD_SEGMENT.getName())) + return INPUT_METHOD_SEGMENT; + + throw new InvalidObjectException ("Can't resolve Attribute: " + + getName()); + } + + /** + * Tests this object for equality against the specified object. + * The two objects will be considered equal if and only if: + * <ul> + * <li>The specified object is not <code>null</code>. + * <li>The specified object is an instance of + * <code>AttributedCharacterIterator.Attribute</code>. + * <li>The specified object has the same attribute name as this object. + * </ul> + * + * @param obj the <code>Object</code> to test for equality against this + * object. + * + * @return <code>true</code> if the specified object is equal to this one, + * <code>false</code> otherwise. + */ + public final boolean equals(Object obj) + { + if (obj == this) + return true; + else + return false; + } + + /** + * Returns a hash value for this object. + * + * @return A hash value for this object. + */ + public final int hashCode() + { + return super.hashCode(); + } + + /** + * Returns a <code>String</code> representation of this object. + * + * @return A <code>String</code> representation of this object. + */ + public String toString() + { + return getClass().getName() + "(" + getName() + ")"; + } + + } // Inner class Attribute + + /** + * 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<Attribute> getAllAttributeKeys(); + + /** + * Returns a <code>Map</code> of the attributes defined for the current + * character. + * + * @return A <code>Map</code> of the attributes for the current character. + */ + Map<Attribute, Object> getAttributes(); + + /** + * Returns the value of the specified attribute for the + * current character. If the attribute is not defined for the current + * character, <code>null</code> is returned. + * + * @param attrib The attribute to retrieve the value of. + * + * @return The value of the specified attribute + */ + Object getAttribute(AttributedCharacterIterator.Attribute attrib); + + /** + * 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(); + + /** + * Returns the index of the first character in the run that + * contains all attributes in the specified <code>Set</code> defined for + * the current character. + * + * @param attribs The <code>Set</code> of attributes. + * + * @return The start index of the run. + */ + int getRunStart(Set<? extends Attribute> attribs); + + /** + * 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); + + /** + * Returns the index of the character after the end of the run + * that contains all attributes defined for the current character. + * + * @return The end index of the run. + */ + int getRunLimit(); + + /** + * Returns the index of the character after the end of the run + * that contains all attributes in the specified <code>Set</code> defined + * for the current character. + * + * @param attribs The <code>Set</code> of attributes. + * + * @return The end index of the run. + */ + int getRunLimit(Set<? extends Attribute> attribs); + + /** + * 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 000000000..7ffb3d4c0 --- /dev/null +++ b/libjava/classpath/java/text/AttributedString.java @@ -0,0 +1,378 @@ +/* AttributedString.java -- Models text with attributes + Copyright (C) 1998, 1999, 2004, 2005, 2006, 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.lang.CPStringBuilder; + +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 <code>String</code> with attributes over various + * subranges of the string. It allows applications to access this + * information via the <code>AttributedCharacterIterator</code> interface. + * + * @since 1.2 + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @since 1.2 + */ +public class AttributedString +{ + + /** + * The attributes and ranges of text over which those attributes apply. + */ + final class AttributeRange + { + + /** A Map of the attributes */ + Map attribs; + + /** The beginning index of the attributes */ + int beginIndex; + + /** The ending index of the attributes */ + int endIndex; + + /** + * Creates a new attribute range. + * + * @param attribs the attributes. + * @param beginIndex the start index. + * @param endIndex the end index. + */ + AttributeRange(Map attribs, int beginIndex, int endIndex) + { + this.attribs = attribs; + this.beginIndex = beginIndex; + this.endIndex = endIndex; + } + + } // Inner class AttributeRange + + /** The string we are representing. */ + private StringCharacterIterator sci; + + /** The attribute information */ + private AttributeRange[] attribs; + + /** + * Creates a new instance of <code>AttributedString</code> + * that represents the specified <code>String</code> with no attributes. + * + * @param str The <code>String</code> to be attributed (<code>null</code> not + * permitted). + * + * @throws NullPointerException if <code>str</code> is <code>null</code>. + */ + public AttributedString(String str) + { + sci = new StringCharacterIterator(str); + attribs = new AttributeRange[0]; + } + + /** + * Creates a new instance of <code>AttributedString</code> + * that represents that specified <code>String</code> with the specified + * attributes over the entire length of the <code>String</code>. + * + * @param str The <code>String</code> to be attributed. + * @param attributes The attribute list. + */ + public AttributedString(String str, + Map<? extends AttributedCharacterIterator.Attribute, ?> attributes) + { + this(str); + + attribs = new AttributeRange[1]; + attribs[0] = new AttributeRange(attributes, 0, str.length()); + } + + /** + * Initializes a new instance of <code>AttributedString</code> + * that will use the text and attribute information from the specified + * <code>AttributedCharacterIterator</code>. + * + * @param aci The <code>AttributedCharacterIterator</code> containing the + * text and attribute information (<code>null</code> not + * permitted). + * + * @throws NullPointerException if <code>aci</code> is <code>null</code>. + */ + public AttributedString(AttributedCharacterIterator aci) + { + this(aci, aci.getBeginIndex(), aci.getEndIndex(), null); + } + + /** + * Initializes a new instance of <code>AttributedString</code> + * that will use the text and attribute information from the specified + * subrange of the specified <code>AttributedCharacterIterator</code>. + * + * @param aci The <code>AttributedCharacterIterator</code> containing the + * text and attribute information. + * @param beginIndex The beginning index of the text subrange. + * @param endIndex The ending index of the text subrange. + */ + public AttributedString(AttributedCharacterIterator aci, int beginIndex, + int endIndex) + { + this(aci, beginIndex, endIndex, null); + } + + /** + * Initializes a new instance of <code>AttributedString</code> + * that will use the text and attribute information from the specified + * subrange of the specified <code>AttributedCharacterIterator</code>. + * 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 <code>AttributedCharacterIterator</code> containing the + * text and attribute information. + * @param begin The beginning index of the text subrange. + * @param end The ending index of the text subrange. + * @param attributes A list of attributes to include from the iterator, or + * <code>null</code> to include all attributes. + */ + public AttributedString(AttributedCharacterIterator aci, int begin, int end, + AttributedCharacterIterator.Attribute[] attributes) + { + // Validate some arguments + if ((begin < 0) || (end < begin) || end > aci.getEndIndex()) + throw new IllegalArgumentException("Bad index values"); + + CPStringBuilder sb = new CPStringBuilder(""); + + // Get the valid attribute list + Set allAttribs = aci.getAllAttributeKeys(); + if (attributes != null) + allAttribs.retainAll(Arrays.asList(attributes)); + + // Loop through and extract the attributes + char c = aci.setIndex(begin); + + ArrayList accum = new ArrayList(); + do + { + sb.append(c); + + Iterator iter = allAttribs.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. + Object attribObj = aci.getAttribute(attrib); + if (attribObj == null) + continue; + int rl = aci.getRunLimit(attrib); + if (rl > end) + rl = end; + rl -= begin; + + // Check to see if we already processed this one + int rs = aci.getRunStart(attrib); + if ((rs < aci.getIndex()) && (aci.getIndex() != begin)) + continue; + + // If the attribute run starts before the beginning index, we + // need to junk it if it is an Annotation. + rs -= begin; + if (rs < 0) + { + if (attribObj instanceof Annotation) + continue; + + rs = 0; + } + + // Create a map object. Yes this will only contain one attribute + Map newMap = new Hashtable(); + newMap.put(attrib, attribObj); + + // Add it to the attribute list. + accum.add(new AttributeRange(newMap, rs, rl)); + } + + c = aci.next(); + } + while( aci.getIndex() < end ); + + attribs = new AttributeRange[accum.size()]; + attribs = (AttributeRange[]) accum.toArray(attribs); + + sci = new StringCharacterIterator(sb.toString()); + } + + /** + * 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()); + } + + /** + * 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 <code>null</code>. + * @param begin The beginning index of the subrange. + * @param end The ending index of the subrange. + * + * @exception IllegalArgumentException If attribute is <code>null</code> or + * the subrange is not valid. + */ + public void addAttribute(AttributedCharacterIterator.Attribute attrib, + Object value, int begin, int end) + { + if (attrib == null) + throw new IllegalArgumentException("null attribute"); + if (end <= begin) + throw new IllegalArgumentException("Requires end > begin"); + HashMap hm = new HashMap(); + hm.put(attrib, value); + + addAttributes(hm, begin, end); + } + + /** + * Adds all of the attributes in the specified list to the + * specified subrange of the string. + * + * @param attributes The list of attributes. + * @param beginIndex The beginning index. + * @param endIndex The ending index + * + * @throws NullPointerException if <code>attributes</code> is + * <code>null</code>. + * @throws IllegalArgumentException if the subrange is not valid. + */ + public void addAttributes(Map<? extends AttributedCharacterIterator.Attribute, ?> attributes, + int beginIndex, int endIndex) + { + if (attributes == null) + throw new NullPointerException("null attribute"); + + if ((beginIndex < 0) || (endIndex > sci.getEndIndex()) || + (endIndex <= beginIndex)) + 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, beginIndex, + endIndex); + } + + /** + * Returns an <code>AttributedCharacterIterator</code> that + * will iterate over the entire string. + * + * @return An <code>AttributedCharacterIterator</code> for the entire string. + */ + public AttributedCharacterIterator getIterator() + { + return(new AttributedStringIterator(sci, attribs, 0, sci.getEndIndex(), + null)); + } + + /** + * Returns an <code>AttributedCharacterIterator</code> 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 <code>null</code>, all attributes will be returned. + * + * @param attributes A list of attributes to include in the returned iterator. + * + * @return An <code>AttributedCharacterIterator</code> for this string. + */ + public AttributedCharacterIterator getIterator( + AttributedCharacterIterator.Attribute[] attributes) + { + return(getIterator(attributes, 0, sci.getEndIndex())); + } + + /** + * Returns an <code>AttributedCharacterIterator</code> 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 <code>null</code>, all attributes will be + * returned. + * + * @param attributes A list of attributes to include in the returned iterator. + * @param beginIndex The beginning index of the subrange. + * @param endIndex The ending index of the subrange. + * + * @return An <code>AttributedCharacterIterator</code> for this string. + */ + public AttributedCharacterIterator getIterator( + AttributedCharacterIterator.Attribute[] attributes, + int beginIndex, int endIndex) + { + if ((beginIndex < 0) || (endIndex > sci.getEndIndex()) || + (endIndex < beginIndex)) + throw new IllegalArgumentException("bad range"); + + return(new AttributedStringIterator(sci, attribs, beginIndex, endIndex, + attributes)); + } + +} // class AttributedString diff --git a/libjava/classpath/java/text/AttributedStringIterator.java b/libjava/classpath/java/text/AttributedStringIterator.java new file mode 100644 index 000000000..429bd7063 --- /dev/null +++ b/libjava/classpath/java/text/AttributedStringIterator.java @@ -0,0 +1,389 @@ +/* AttributedStringIterator.java -- Class to iterate over AttributedString + Copyright (C) 1998, 1999, 2004, 2005, 2006, 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 +{ + + /*************************************************************************/ + + /** 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; + + /*************************************************************************/ + + /** + * Creates a new instance. + * + * @param sci an iterator for the string content. + * @param attribs the attribute ranges. + * @param beginIndex the start index. + * @param endIndex the end index. + * @param restricts the attributes that the user is interested in. + */ + AttributedStringIterator(StringCharacterIterator sci, + AttributedString.AttributeRange[] attribs, + int beginIndex, int endIndex, + AttributedCharacterIterator.Attribute[] restricts) + { + this.ci = new StringCharacterIterator(sci, beginIndex, endIndex); + this.attribs = attribs; + this.restricts = restricts; + } + + /*************************************************************************/ + + // 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].beginIndex > getEndIndex() + || attribs[i].endIndex <= 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(getAllAttributeKeys()); + } + + public int getRunLimit(AttributedCharacterIterator.Attribute attrib) + { + HashSet s = new HashSet(); + s.add(attrib); + return(getRunLimit(s)); + } + + public synchronized int getRunLimit(Set attributeSet) + { + if (attributeSet == null) + return ci.getEndIndex(); + + int current = ci.getIndex(); + int end = ci.getEndIndex(); + int limit = current; + if (current == end) + return end; + Map runValues = getAttributes(); + while (limit < end) + { + Iterator iterator = attributeSet.iterator(); + while (iterator.hasNext()) + { + Attribute attributeKey = (Attribute) iterator.next(); + Object v1 = runValues.get(attributeKey); + Object v2 = getAttribute(attributeKey, limit + 1); + boolean changed = false; + // check for equal or both null, if NO return start + if (v1 != null) + { + changed = !v1.equals(v2); + } + else + { + changed = (v2 != null); + } + if (changed) + return limit + 1; + } + // no differences, so increment limit and next and loop again + limit++; + } + return end; + } + + /*************************************************************************/ + + /** + * Various methods that determine where the run begins for various + * attribute combinations. + */ + + /** + * Returns the index of the first character in the run containing the current + * character and defined by all the attributes defined for that character + * position. + * + * @return The run start index. + */ + public int getRunStart() + { + return(getRunStart(getAttributes().keySet())); + } + + /** + * Returns the index of the first character in the run, defined by the + * specified attribute, that contains the current character. + * + * @param attrib the attribute (<code>null</code> permitted). + * + * return The index of the first character in the run. + */ + public int getRunStart(AttributedCharacterIterator.Attribute attrib) + { + if (attrib == null) + return ci.getBeginIndex(); + HashSet s = new HashSet(); + s.add(attrib); + return(getRunStart(s)); + } + + /** + * Returns the index of the first character in the run, defined by the + * specified attribute set, that contains the current character. + * + * @param attributeSet the attribute set (<code>null</code> permitted). + * + * return The index of the first character in the run. + */ + public int getRunStart(Set attributeSet) + { + if (attributeSet == null) + return ci.getBeginIndex(); + + int current = ci.getIndex(); + int begin = ci.getBeginIndex(); + int start = current; + if (start == begin) + return begin; + Map runValues = getAttributes(); + int prev = start - 1; + while (start > begin) + { + Iterator iterator = attributeSet.iterator(); + while (iterator.hasNext()) + { + Attribute attributeKey = (Attribute) iterator.next(); + Object v1 = runValues.get(attributeKey); + Object v2 = getAttribute(attributeKey, prev); + boolean changed = false; + // check for equal or both null, if NO return start + if (v1 != null) + { + changed = !v1.equals(v2); + } + else + { + changed = (v2 != null); + } + if (changed) + return start; + } + // no differences, so decrement start and prev and loop again + start--; + prev--; + } + return start; + } + + /*************************************************************************/ + + /** + * Returns the value for an attribute at the specified position. If the + * attribute key (<code>key</code>) is <code>null</code>, the method returns + * <code>null</code>. + * + * @param key the key (<code>null</code> permitted). + * @param pos the character position. + * + * @return The attribute value (possibly <code>null</code>). + */ + private Object getAttribute(AttributedCharacterIterator.Attribute key, + int pos) + { + if (attribs == null) + return null; + for (int i = attribs.length - 1; i >= 0; i--) + { + if (pos >= attribs[i].beginIndex && pos < attribs[i].endIndex) + { + Set keys = attribs[i].attribs.keySet(); + if (keys.contains(key)) + { + return attribs[i].attribs.get(key); + } + } + } + return null; + } + + /** + * Returns the value for an attribute at the current position. If the + * attribute key (<code>key</code>) is <code>null</code>, the method returns + * <code>null</code>. + * + * @param key the key (<code>null</code> permitted). + * + * @return The attribute value (possibly <code>null</code>). + */ + public Object getAttribute(AttributedCharacterIterator.Attribute key) + { + return getAttribute(key, ci.getIndex()); + } + + /*************************************************************************/ + + /** + * 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].beginIndex) && + (ci.getIndex() < attribs[i].endIndex)) + m.putAll(attribs[i].attribs); + } + + return(m); + } + +} // class AttributedStringIterator diff --git a/libjava/classpath/java/text/Bidi.java b/libjava/classpath/java/text/Bidi.java new file mode 100644 index 000000000..6a7bd0750 --- /dev/null +++ b/libjava/classpath/java/text/Bidi.java @@ -0,0 +1,1001 @@ +/* Bidi.java -- Bidirectional Algorithm implementation + Copyright (C) 2005, 2006 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.awt.font.NumericShaper; +import java.awt.font.TextAttribute; +import java.util.ArrayList; + + +/** + * Bidirectional Algorithm implementation. + * + * The full algorithm is + * <a href="http://www.unicode.org/unicode/reports/tr9/">Unicode Standard + * Annex #9: The Bidirectional Algorithm</a>. + * + * @since 1.4 + */ +public final class Bidi +{ + /** + * This indicates that a strongly directional character in the text should + * set the initial direction, but if no such character is found, then the + * initial direction will be left-to-right. + */ + public static final int DIRECTION_DEFAULT_LEFT_TO_RIGHT = -2; + + /** + * This indicates that a strongly directional character in the text should + * set the initial direction, but if no such character is found, then the + * initial direction will be right-to-left. + */ + public static final int DIRECTION_DEFAULT_RIGHT_TO_LEFT = -1; + + /** + * This indicates that the initial direction should be left-to-right. + */ + public static final int DIRECTION_LEFT_TO_RIGHT = 0; + + /** + * This indicates that the initial direction should be right-to-left. + */ + public static final int DIRECTION_RIGHT_TO_LEFT = 1; + + // Flags used when computing the result. + private static final int LTOR = 1 << DIRECTION_LEFT_TO_RIGHT; + private static final int RTOL = 1 << DIRECTION_RIGHT_TO_LEFT; + + // The text we are examining, and the starting offset. + // If we had a better way to handle createLineBidi, we wouldn't + // need this at all -- which for the String case would be an + // efficiency win. + private char[] text; + private int textOffset; + // The embeddings corresponding to the text, and the starting offset. + private byte[] embeddings; + private int embeddingOffset; + // The length of the text (and embeddings) to use. + private int length; + // The flags. + private int flags; + + // All instance fields following this point are initialized + // during analysis. Fields before this must be set by the constructor. + + // The initial embedding level. + private int baseEmbedding; + // The type of each character in the text. + private byte[] types; + // The levels we compute. + private byte[] levels; + + // A list of indices where a formatting code was found. These + // are indicies into the original text -- not into the text after + // the codes have been removed. + private ArrayList formatterIndices; + + // Indices of the starts of runs in the text. + private int[] runs; + + // A convenience field where we keep track of what kinds of runs + // we've seen. + private int resultFlags; + + /** + * Create a new Bidi object given an attributed character iterator. + * This constructor will examine various attributes of the text: + * <ul> + * <li> {@link TextAttribute#RUN_DIRECTION} is used to determine the + * paragraph's base embedding level. This constructor will recognize + * either {@link TextAttribute#RUN_DIRECTION_LTR} or + * {@link TextAttribute#RUN_DIRECTION_RTL}. If neither is given, + * {@link #DIRECTION_DEFAULT_LEFT_TO_RIGHT} is assumed. + * </li> + * + * <li> If {@link TextAttribute#NUMERIC_SHAPING} is seen, then numeric + * shaping will be done before the Bidi algorithm is run. + * </li> + * + * <li> If {@link TextAttribute#BIDI_EMBEDDING} is seen on a given + * character, then the value of this attribute will be used as an + * embedding level override. + * </li> + * </ul> + * @param iter the attributed character iterator to use + */ + public Bidi(AttributedCharacterIterator iter) + { + // If set, this attribute should be set on all characters. + // We don't check this (should we?) but we do assume that we + // can simply examine the first character. + Object val = iter.getAttribute(TextAttribute.RUN_DIRECTION); + if (val == TextAttribute.RUN_DIRECTION_LTR) + this.flags = DIRECTION_LEFT_TO_RIGHT; + else if (val == TextAttribute.RUN_DIRECTION_RTL) + this.flags = DIRECTION_RIGHT_TO_LEFT; + else + this.flags = DIRECTION_DEFAULT_LEFT_TO_RIGHT; + + // Likewise this attribute should be specified on the whole text. + // We read it here and then, if it is set, we apply the numeric shaper + // to the text before processing it. + NumericShaper shaper = null; + val = iter.getAttribute(TextAttribute.NUMERIC_SHAPING); + if (val instanceof NumericShaper) + shaper = (NumericShaper) val; + + char[] text = new char[iter.getEndIndex() - iter.getBeginIndex()]; + this.embeddings = new byte[this.text.length]; + this.embeddingOffset = 0; + this.length = text.length; + for (int i = 0; i < this.text.length; ++i) + { + this.text[i] = iter.current(); + + val = iter.getAttribute(TextAttribute.BIDI_EMBEDDING); + if (val instanceof Integer) + { + int ival = ((Integer) val).intValue(); + byte bval; + if (ival < -62 || ival > 62) + bval = 0; + else + bval = (byte) ival; + this.embeddings[i] = bval; + } + } + + // Invoke the numeric shaper, if specified. + if (shaper != null) + shaper.shape(this.text, 0, this.length); + + runBidi(); + } + + /** + * Create a new Bidi object with the indicated text and, possibly, explicit + * embedding settings. + * + * If the embeddings array is null, it is ignored. Otherwise it is taken to + * be explicit embedding settings corresponding to the text. Positive values + * from 1 to 61 are embedding levels, and negative values from -1 to -61 are + * embedding overrides. (FIXME: not at all clear what this really means.) + * + * @param text the text to use + * @param offset the offset of the first character of the text + * @param embeddings the explicit embeddings, or null if there are none + * @param embedOffset the offset of the first embedding value to use + * @param length the length of both the text and the embeddings + * @param flags a flag indicating the base embedding direction + */ + public Bidi(char[] text, int offset, byte[] embeddings, int embedOffset, + int length, int flags) + { + if (flags != DIRECTION_DEFAULT_LEFT_TO_RIGHT + && flags != DIRECTION_DEFAULT_RIGHT_TO_LEFT + && flags != DIRECTION_LEFT_TO_RIGHT + && flags != DIRECTION_RIGHT_TO_LEFT) + throw new IllegalArgumentException("unrecognized 'flags' argument: " + + flags); + this.text = text; + this.textOffset = offset; + this.embeddings = embeddings; + this.embeddingOffset = embedOffset; + this.length = length; + this.flags = flags; + + runBidi(); + } + + /** + * Create a new Bidi object using the contents of the given String + * as the text. + * @param text the text to use + * @param flags a flag indicating the base embedding direction + */ + public Bidi(String text, int flags) + { + if (flags != DIRECTION_DEFAULT_LEFT_TO_RIGHT + && flags != DIRECTION_DEFAULT_RIGHT_TO_LEFT + && flags != DIRECTION_LEFT_TO_RIGHT + && flags != DIRECTION_RIGHT_TO_LEFT) + throw new IllegalArgumentException("unrecognized 'flags' argument: " + + flags); + + // This is inefficient, but it isn't clear whether it matters. + // If it does we can change our implementation a bit to allow either + // a String or a char[]. + this.text = text.toCharArray(); + this.textOffset = 0; + this.embeddings = null; + this.embeddingOffset = 0; + this.length = text.length(); + this.flags = flags; + + runBidi(); + } + + /** + * Implementation function which computes the initial type of + * each character in the input. + */ + private void computeTypes() + { + types = new byte[length]; + for (int i = 0; i < length; ++i) + types[i] = Character.getDirectionality(text[textOffset + i]); + } + + /** + * An internal function which implements rules P2 and P3. + * This computes the base embedding level. + * @return the paragraph's base embedding level + */ + private int computeParagraphEmbeddingLevel() + { + // First check to see if the user supplied a directionality override. + if (flags == DIRECTION_LEFT_TO_RIGHT + || flags == DIRECTION_RIGHT_TO_LEFT) + return flags; + + // This implements rules P2 and P3. + // (Note that we don't need P1, as the user supplies + // a paragraph.) + for (int i = 0; i < length; ++i) + { + int dir = types[i]; + if (dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT) + return DIRECTION_LEFT_TO_RIGHT; + if (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT + || dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT) + return DIRECTION_RIGHT_TO_LEFT; + } + return (flags == DIRECTION_DEFAULT_LEFT_TO_RIGHT + ? DIRECTION_LEFT_TO_RIGHT + : DIRECTION_RIGHT_TO_LEFT); + } + + /** + * An internal function which implements rules X1 through X9. + * This computes the initial levels for the text, handling + * explicit overrides and embeddings. + */ + private void computeExplicitLevels() + { + levels = new byte[length]; + byte currentEmbedding = (byte) baseEmbedding; + // The directional override is a Character directionality + // constant. -1 means there is no override. + byte directionalOverride = -1; + // The stack of pushed embeddings, and the stack pointer. + // Note that because the direction is inherent in the depth, + // and because we have a bit left over in a byte, we can encode + // the override, if any, directly in this value on the stack. + final int MAX_DEPTH = 62; + byte[] embeddingStack = new byte[MAX_DEPTH]; + int sp = 0; + + for (int i = 0; i < length; ++i) + { + // If we see an explicit embedding, we use that, even if + // the current character is itself a directional override. + if (embeddings != null && embeddings[embeddingOffset + i] != 0) + { + // It isn't at all clear what we're supposed to do here. + // What does a negative value really mean? + // Should we push on the embedding stack here? + currentEmbedding = embeddings[embeddingOffset + i]; + if (currentEmbedding < 0) + { + currentEmbedding = (byte) -currentEmbedding; + directionalOverride + = (((currentEmbedding % 2) == 0) + ? Character.DIRECTIONALITY_LEFT_TO_RIGHT + : Character.DIRECTIONALITY_RIGHT_TO_LEFT); + } + else + directionalOverride = -1; + continue; + } + // No explicit embedding. + boolean isLtoR = false; + boolean isSpecial = true; + switch (types[i]) + { + case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: + case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: + isLtoR = true; + // Fall through. + case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: + case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: + { + byte newEmbedding; + if (isLtoR) + { + // Least greater even. + newEmbedding = (byte) ((currentEmbedding & ~1) + 2); + } + else + { + // Least greater odd. + newEmbedding = (byte) ((currentEmbedding + 1) | 1); + } + // FIXME: we don't properly handle invalid pushes. + if (newEmbedding < MAX_DEPTH) + { + // The new level is valid. Push the old value. + // See above for a comment on the encoding here. + if (directionalOverride != -1) + currentEmbedding |= Byte.MIN_VALUE; + embeddingStack[sp++] = currentEmbedding; + currentEmbedding = newEmbedding; + if (types[i] == Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE) + directionalOverride = Character.DIRECTIONALITY_LEFT_TO_RIGHT; + else if (types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE) + directionalOverride = Character.DIRECTIONALITY_RIGHT_TO_LEFT; + else + directionalOverride = -1; + } + } + break; + case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT: + { + // FIXME: we don't properly handle a pop with a corresponding + // invalid push. + if (sp == 0) + { + // We saw a pop without a push. Just ignore it. + break; + } + byte newEmbedding = embeddingStack[--sp]; + currentEmbedding = (byte) (newEmbedding & 0x7f); + if (newEmbedding < 0) + directionalOverride + = (((newEmbedding & 1) == 0) + ? Character.DIRECTIONALITY_LEFT_TO_RIGHT + : Character.DIRECTIONALITY_RIGHT_TO_LEFT); + else + directionalOverride = -1; + } + break; + default: + isSpecial = false; + break; + } + levels[i] = currentEmbedding; + if (isSpecial) + { + // Mark this character for removal. + if (formatterIndices == null) + formatterIndices = new ArrayList(); + formatterIndices.add(Integer.valueOf(i)); + } + else if (directionalOverride != -1) + types[i] = directionalOverride; + } + + // Remove the formatting codes and update both the arrays + // and 'length'. It would be more efficient not to remove + // these codes, but it is also more complicated. Also, the + // Unicode algorithm reference does not properly describe + // how this is to be done -- from what I can tell, their suggestions + // in this area will not yield the correct results. + if (formatterIndices == null) + return; + int output = 0, input = 0; + final int size = formatterIndices.size(); + for (int i = 0; i <= size; ++i) + { + int nextFmt; + if (i == size) + nextFmt = length; + else + nextFmt = ((Integer) formatterIndices.get(i)).intValue(); + // Non-formatter codes are from 'input' to 'nextFmt'. + int len = nextFmt - input; + System.arraycopy(levels, input, levels, output, len); + System.arraycopy(types, input, types, output, len); + output += len; + input = nextFmt + 1; + } + length -= formatterIndices.size(); + } + + /** + * An internal function to compute the boundaries of runs + * in the text. It isn't strictly necessary to do this, but + * it lets us write some following passes in a less complicated + * way. Also it lets us efficiently implement some of the public + * methods. A run is simply a sequence of characters at the + * same level. + */ + private void computeRuns() + { + int runCount = 0; + int currentEmbedding = baseEmbedding; + for (int i = 0; i < length; ++i) + { + if (levels[i] != currentEmbedding) + { + currentEmbedding = levels[i]; + ++runCount; + } + } + + // This may be called multiple times. If so, and if + // the number of runs has not changed, then don't bother + // allocating a new array. + if (runs == null || runs.length != runCount + 1) + runs = new int[runCount + 1]; + int where = 0; + int lastRunStart = 0; + currentEmbedding = baseEmbedding; + for (int i = 0; i < length; ++i) + { + if (levels[i] != currentEmbedding) + { + runs[where++] = lastRunStart; + lastRunStart = i; + currentEmbedding = levels[i]; + } + } + runs[where++] = lastRunStart; + } + + /** + * An internal method to resolve weak types. This implements + * rules W1 through W7. + */ + private void resolveWeakTypes() + { + final int runCount = getRunCount(); + + int previousLevel = baseEmbedding; + for (int run = 0; run < runCount; ++run) + { + int start = getRunStart(run); + int end = getRunLimit(run); + int level = getRunLevel(run); + + // These are the names used in the Bidi algorithm. + byte sor = (((Math.max(previousLevel, level) % 2) == 0) + ? Character.DIRECTIONALITY_LEFT_TO_RIGHT + : Character.DIRECTIONALITY_RIGHT_TO_LEFT); + int nextLevel; + if (run == runCount - 1) + nextLevel = baseEmbedding; + else + nextLevel = getRunLevel(run + 1); + byte eor = (((Math.max(level, nextLevel) % 2) == 0) + ? Character.DIRECTIONALITY_LEFT_TO_RIGHT + : Character.DIRECTIONALITY_RIGHT_TO_LEFT); + + byte prevType = sor; + byte prevStrongType = sor; + for (int i = start; i < end; ++i) + { + final byte nextType = (i == end - 1) ? eor : types[i + 1]; + + // Rule W1: change NSM to the prevailing direction. + if (types[i] == Character.DIRECTIONALITY_NONSPACING_MARK) + types[i] = prevType; + else + prevType = types[i]; + + // Rule W2: change EN to AN in some cases. + if (types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + { + if (prevStrongType == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) + types[i] = Character.DIRECTIONALITY_ARABIC_NUMBER; + } + else if (types[i] == Character.DIRECTIONALITY_LEFT_TO_RIGHT + || types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT + || types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) + prevStrongType = types[i]; + + // Rule W3: change AL to R. + if (types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) + types[i] = Character.DIRECTIONALITY_RIGHT_TO_LEFT; + + // Rule W4: handle separators between two numbers. + if (prevType == Character.DIRECTIONALITY_EUROPEAN_NUMBER + && nextType == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + { + if (types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR + || types[i] == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) + types[i] = nextType; + } + else if (prevType == Character.DIRECTIONALITY_ARABIC_NUMBER + && nextType == Character.DIRECTIONALITY_ARABIC_NUMBER + && types[i] == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) + types[i] = nextType; + + // Rule W5: change a sequence of european terminators to + // european numbers, if they are adjacent to european numbers. + // We also include BN characters in this. + if (types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR + || types[i] == Character.DIRECTIONALITY_BOUNDARY_NEUTRAL) + { + if (prevType == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + types[i] = prevType; + else + { + // Look ahead to see if there is an EN terminating this + // sequence of ETs. + int j = i + 1; + while (j < end + && (types[j] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR + || types[j] == Character.DIRECTIONALITY_BOUNDARY_NEUTRAL)) + ++j; + if (j < end + && types[j] == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + { + // Change them all to EN now. + for (int k = i; k < j; ++k) + types[k] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; + } + } + } + + // Rule W6: separators and terminators change to ON. + // Again we include BN. + if (types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR + || types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR + || types[i] == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR + || types[i] == Character.DIRECTIONALITY_BOUNDARY_NEUTRAL) + types[i] = Character.DIRECTIONALITY_OTHER_NEUTRALS; + + // Rule W7: change european number types. + if (prevStrongType == Character.DIRECTIONALITY_LEFT_TO_RIGHT + && types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + types[i] = prevStrongType; + } + + previousLevel = level; + } + } + + /** + * An internal method to resolve neutral types. This implements + * rules N1 and N2. + */ + private void resolveNeutralTypes() + { + // This implements rules N1 and N2. + final int runCount = getRunCount(); + + int previousLevel = baseEmbedding; + for (int run = 0; run < runCount; ++run) + { + int start = getRunStart(run); + int end = getRunLimit(run); + int level = getRunLevel(run); + + byte embeddingDirection + = (((level % 2) == 0) ? Character.DIRECTIONALITY_LEFT_TO_RIGHT + : Character.DIRECTIONALITY_RIGHT_TO_LEFT); + // These are the names used in the Bidi algorithm. + byte sor = (((Math.max(previousLevel, level) % 2) == 0) + ? Character.DIRECTIONALITY_LEFT_TO_RIGHT + : Character.DIRECTIONALITY_RIGHT_TO_LEFT); + int nextLevel; + if (run == runCount - 1) + nextLevel = baseEmbedding; + else + nextLevel = getRunLevel(run + 1); + byte eor = (((Math.max(level, nextLevel) % 2) == 0) + ? Character.DIRECTIONALITY_LEFT_TO_RIGHT + : Character.DIRECTIONALITY_RIGHT_TO_LEFT); + + byte prevStrong = sor; + int neutralStart = -1; + for (int i = start; i <= end; ++i) + { + byte newStrong = -1; + byte thisType = i == end ? eor : types[i]; + switch (thisType) + { + case Character.DIRECTIONALITY_LEFT_TO_RIGHT: + newStrong = Character.DIRECTIONALITY_LEFT_TO_RIGHT; + break; + case Character.DIRECTIONALITY_RIGHT_TO_LEFT: + case Character.DIRECTIONALITY_ARABIC_NUMBER: + case Character.DIRECTIONALITY_EUROPEAN_NUMBER: + newStrong = Character.DIRECTIONALITY_RIGHT_TO_LEFT; + break; + case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL: + case Character.DIRECTIONALITY_OTHER_NEUTRALS: + case Character.DIRECTIONALITY_SEGMENT_SEPARATOR: + case Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR: + case Character.DIRECTIONALITY_WHITESPACE: + if (neutralStart == -1) + neutralStart = i; + break; + } + // If we see a strong character, update all the neutrals. + if (newStrong != -1) + { + if (neutralStart != -1) + { + byte override = (prevStrong == newStrong + ? prevStrong + : embeddingDirection); + for (int j = neutralStart; j < i; ++j) + types[j] = override; + } + prevStrong = newStrong; + neutralStart = -1; + } + } + + previousLevel = level; + } + } + + /** + * An internal method to resolve implicit levels. + * This implements rules I1 and I2. + */ + private void resolveImplicitLevels() + { + // This implements rules I1 and I2. + for (int i = 0; i < length; ++i) + { + if ((levels[i] & 1) == 0) + { + if (types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) + ++levels[i]; + else if (types[i] == Character.DIRECTIONALITY_ARABIC_NUMBER + || types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + levels[i] += 2; + } + else + { + if (types[i] == Character.DIRECTIONALITY_LEFT_TO_RIGHT + || types[i] == Character.DIRECTIONALITY_ARABIC_NUMBER + || types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + ++levels[i]; + } + + // Update the result flags. + resultFlags |= 1 << (levels[i] & 1); + } + // One final update of the result flags, using the base level. + resultFlags |= 1 << baseEmbedding; + } + + /** + * This reinserts the formatting codes that we removed early on. + * Actually it does not insert formatting codes per se, but rather + * simply inserts new levels at the appropriate locations in the + * 'levels' array. + */ + private void reinsertFormattingCodes() + { + if (formatterIndices == null) + return; + int input = length; + int output = levels.length; + // Process from the end as we are copying the array over itself here. + for (int index = formatterIndices.size() - 1; index >= 0; --index) + { + int nextFmt = ((Integer) formatterIndices.get(index)).intValue(); + + // nextFmt points to a location in the original array. So, + // nextFmt+1 is the target of our copying. output is the location + // to which we last copied, thus we can derive the length of the + // copy from it. + int len = output - nextFmt - 1; + output = nextFmt; + input -= len; + // Note that we no longer need 'types' at this point, so we + // only edit 'levels'. + if (nextFmt + 1 < levels.length) + System.arraycopy(levels, input, levels, nextFmt + 1, len); + + // Now set the level at the reinsertion point. + int rightLevel; + if (output == levels.length - 1) + rightLevel = baseEmbedding; + else + rightLevel = levels[output + 1]; + int leftLevel; + if (input == 0) + leftLevel = baseEmbedding; + else + leftLevel = levels[input]; + levels[output] = (byte) Math.max(leftLevel, rightLevel); + } + length = levels.length; + } + + /** + * This is the main internal entry point. After a constructor + * has initialized the appropriate local state, it will call + * this method to do all the work. + */ + private void runBidi() + { + computeTypes(); + baseEmbedding = computeParagraphEmbeddingLevel(); + computeExplicitLevels(); + computeRuns(); + resolveWeakTypes(); + resolveNeutralTypes(); + resolveImplicitLevels(); + // We're done with the types. Let the GC clean up. + types = null; + reinsertFormattingCodes(); + // After resolving the implicit levels, the number + // of runs may have changed. + computeRuns(); + } + + /** + * Return true if the paragraph base embedding is left-to-right, + * false otherwise. + */ + public boolean baseIsLeftToRight() + { + return baseEmbedding == DIRECTION_LEFT_TO_RIGHT; + } + + /** + * Create a new Bidi object for a single line of text, taken + * from the text used when creating the current Bidi object. + * @param start the index of the first character of the line + * @param end the index of the final character of the line + * @return a new Bidi object for the indicated line of text + */ + public Bidi createLineBidi(int start, int end) + { + // This isn't the most efficient implementation possible. + // This probably does not matter, so we choose simplicity instead. + int level = getLevelAt(start); + int flag = (((level % 2) == 0) + ? DIRECTION_LEFT_TO_RIGHT + : DIRECTION_RIGHT_TO_LEFT); + return new Bidi(text, textOffset + start, + embeddings, embeddingOffset + start, + end - start, flag); + } + + /** + * Return the base embedding level of the paragraph. + */ + public int getBaseLevel() + { + return baseEmbedding; + } + + /** + * Return the length of the paragraph, in characters. + */ + public int getLength() + { + return length; + } + + /** + * Return the level at the indicated character. If the + * supplied index is less than zero or greater than the length + * of the text, then the paragraph's base embedding level will + * be returned. + * @param offset the character to examine + * @return the level of that character + */ + public int getLevelAt(int offset) + { + if (offset < 0 || offset >= length) + return getBaseLevel(); + return levels[offset]; + } + + /** + * Return the number of runs in the result. A run is + * a sequence of characters at the same embedding level. + */ + public int getRunCount() + { + return runs.length; + } + + /** + * Return the level of the indicated run. + * @param which the run to examine + * @return the level of that run + */ + public int getRunLevel(int which) + { + return levels[runs[which]]; + } + + /** + * Return the index of the character just following the end + * of the indicated run. + * @param which the run to examine + * @return the index of the character after the final character + * of the run + */ + public int getRunLimit(int which) + { + if (which == runs.length - 1) + return length; + return runs[which + 1]; + } + + /** + * Return the index of the first character in the indicated run. + * @param which the run to examine + * @return the index of the first character of the run + */ + public int getRunStart(int which) + { + return runs[which]; + } + + /** + * Return true if the text is entirely left-to-right, and the + * base embedding is also left-to-right. + */ + public boolean isLeftToRight() + { + return resultFlags == LTOR; + } + + /** + * Return true if the text consists of mixed left-to-right and + * right-to-left runs, or if the text consists of one kind of run + * which differs from the base embedding direction. + */ + public boolean isMixed() + { + return resultFlags == (LTOR | RTOL); + } + + /** + * Return true if the text is entirely right-to-left, and the + * base embedding is also right-to-left. + */ + public boolean isRightToLeft() + { + return resultFlags == RTOL; + } + + /** + * Return a String describing the internal state of this object. + * This is only useful for debugging. + */ + public String toString() + { + return "Bidi Bidi Bidi I like you, Buck!"; + } + + /** + * Reorder objects according to the levels passed in. This implements + * reordering as defined by the Unicode bidirectional layout specification. + * The levels are integers from 0 to 62; even numbers represent left-to-right + * runs, and odd numbers represent right-to-left runs. + * + * @param levels the levels associated with each object + * @param levelOffset the index of the first level to use + * @param objs the objects to reorder according to the levels + * @param objOffset the index of the first object to use + * @param count the number of objects (and levels) to manipulate + */ + public static void reorderVisually(byte[] levels, int levelOffset, + Object[] objs, int objOffset, int count) + { + // We need a copy of the 'levels' array, as we are going to modify it. + // This is unfortunate but difficult to avoid. + byte[] levelCopy = new byte[count]; + // Do this explicitly so we can also find the maximum depth at the + // same time. + int max = 0; + int lowestOdd = 63; + for (int i = 0; i < count; ++i) + { + levelCopy[i] = levels[levelOffset + i]; + max = Math.max(levelCopy[i], max); + if (levelCopy[i] % 2 != 0) + lowestOdd = Math.min(lowestOdd, levelCopy[i]); + } + + // Reverse the runs starting with the deepest. + for (int depth = max; depth >= lowestOdd; --depth) + { + int start = 0; + while (start < count) + { + // Find the start of a run >= DEPTH. + while (start < count && levelCopy[start] < depth) + ++start; + if (start == count) + break; + // Find the end of the run. + int end = start + 1; + while (end < count && levelCopy[end] >= depth) + ++end; + + // Reverse this run. + for (int i = 0; i < (end - start) / 2; ++i) + { + byte tmpb = levelCopy[end - i - 1]; + levelCopy[end - i - 1] = levelCopy[start + i]; + levelCopy[start + i] = tmpb; + Object tmpo = objs[objOffset + end - i - 1]; + objs[objOffset + end - i - 1] = objs[objOffset + start + i]; + objs[objOffset + start + i] = tmpo; + } + + // Handle the next run. + start = end + 1; + } + } + } + + /** + * Returns false if all characters in the text between start and end + * are all left-to-right text. This implementation is just calls + * <code>Character.getDirectionality(char)</code> on all characters + * and makes sure all characters are either explicitly left-to-right + * or neutral in directionality (character types L, EN, ES, ET, AN, + * CS, S and WS). + */ + public static boolean requiresBidi(char[] text, int start, int end) + { + for (int i = start; i < end; i++) + { + byte dir = Character.getDirectionality(text[i]); + if (dir != Character.DIRECTIONALITY_LEFT_TO_RIGHT + && dir != Character.DIRECTIONALITY_EUROPEAN_NUMBER + && dir != Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR + && dir != Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR + && dir != Character.DIRECTIONALITY_ARABIC_NUMBER + && dir != Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR + && dir != Character.DIRECTIONALITY_SEGMENT_SEPARATOR + && dir != Character.DIRECTIONALITY_WHITESPACE + && dir != Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR) + return true; + } + + return false; + } +} diff --git a/libjava/classpath/java/text/BreakIterator.java b/libjava/classpath/java/text/BreakIterator.java new file mode 100644 index 000000000..628cb7235 --- /dev/null +++ b/libjava/classpath/java/text/BreakIterator.java @@ -0,0 +1,444 @@ +/* BreakIterator.java -- Breaks text into elements + Copyright (C) 1998, 1999, 2001, 2004, 2005, 2007 + 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.locale.LocaleHelper; + +import gnu.java.text.CharacterBreakIterator; +import gnu.java.text.LineBreakIterator; +import gnu.java.text.SentenceBreakIterator; +import gnu.java.text.WordBreakIterator; + +import java.text.spi.BreakIteratorProvider; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.ServiceLoader; + +/** + * 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 <code>next()</code> and + * <code>previous</code> 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 <code>BreakIterator</code>. + * 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 pos The text index from which to find the next text boundary. + * + * @return 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 + * <code>BreakIterator</code> 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 <code>BreakIterator</code> that will + * iterate over characters as defined in the default locale. + * + * @return A <code>BreakIterator</code> instance for the default locale. + */ + public static BreakIterator getCharacterInstance () + { + return getCharacterInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of <code>BreakIterator</code> that will + * iterate over characters as defined in the specified locale. + * + * @param locale The desired locale. + * + * @return A <code>BreakIterator</code> instance for the specified locale. + */ + public static BreakIterator getCharacterInstance (Locale locale) + { + BreakIterator r = getInstance("CharacterIterator", locale); + if (r != null) + return r; + for (BreakIteratorProvider p : + ServiceLoader.load(BreakIteratorProvider.class)) + { + for (Locale loc : p.getAvailableLocales()) + { + if (loc.equals(locale)) + { + BreakIterator bi = p.getCharacterInstance(locale); + if (bi != null) + return bi; + break; + } + } + } + if (locale.equals(Locale.ROOT)) + return new CharacterBreakIterator(); + return getCharacterInstance(LocaleHelper.getFallbackLocale(locale)); + } + + /** + * This method returns an instance of <code>BreakIterator</code> that will + * iterate over line breaks as defined in the default locale. + * + * @return A <code>BreakIterator</code> instance for the default locale. + */ + public static BreakIterator getLineInstance () + { + return getLineInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of <code>BreakIterator</code> that will + * iterate over line breaks as defined in the specified locale. + * + * @param locale The desired locale. + * + * @return A <code>BreakIterator</code> instance for the default locale. + */ + public static BreakIterator getLineInstance (Locale locale) + { + BreakIterator r = getInstance ("LineIterator", locale); + if (r != null) + return r; + for (BreakIteratorProvider p : + ServiceLoader.load(BreakIteratorProvider.class)) + { + for (Locale loc : p.getAvailableLocales()) + { + if (loc.equals(locale)) + { + BreakIterator bi = p.getLineInstance(locale); + if (bi != null) + return bi; + break; + } + } + } + if (locale.equals(Locale.ROOT)) + return new LineBreakIterator(); + return getLineInstance(LocaleHelper.getFallbackLocale(locale)); + } + + /** + * This method returns an instance of <code>BreakIterator</code> that will + * iterate over sentences as defined in the default locale. + * + * @return A <code>BreakIterator</code> instance for the default locale. + */ + public static BreakIterator getSentenceInstance () + { + return getSentenceInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of <code>BreakIterator</code> that will + * iterate over sentences as defined in the specified locale. + * + * @param locale The desired locale. + * + * @return A <code>BreakIterator</code> instance for the default locale. + */ + public static BreakIterator getSentenceInstance (Locale locale) + { + BreakIterator r = getInstance ("SentenceIterator", locale); + if (r != null) + return r; + for (BreakIteratorProvider p : + ServiceLoader.load(BreakIteratorProvider.class)) + { + for (Locale loc : p.getAvailableLocales()) + { + if (loc.equals(locale)) + { + BreakIterator bi = p.getSentenceInstance(locale); + if (bi != null) + return bi; + break; + } + } + } + if (locale.equals(Locale.ROOT)) + return new SentenceBreakIterator(); + return getSentenceInstance(LocaleHelper.getFallbackLocale(locale)); + } + + /** + * This method returns the text this object is iterating over as a + * <code>CharacterIterator</code>. + * + * @return The text being iterated over. + */ + public abstract CharacterIterator getText (); + + /** + * This method returns an instance of <code>BreakIterator</code> that will + * iterate over words as defined in the default locale. + * + * @return A <code>BreakIterator</code> instance for the default locale. + */ + public static BreakIterator getWordInstance () + { + return getWordInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of <code>BreakIterator</code> that will + * iterate over words as defined in the specified locale. + * + * @param locale The desired locale. + * + * @return A <code>BreakIterator</code> instance for the default locale. + */ + public static BreakIterator getWordInstance (Locale locale) + { + BreakIterator r = getInstance ("WordIterator", locale); + if (r != null) + return r; + for (BreakIteratorProvider p : + ServiceLoader.load(BreakIteratorProvider.class)) + { + for (Locale loc : p.getAvailableLocales()) + { + if (loc.equals(locale)) + { + BreakIterator bi = p.getWordInstance(locale); + if (bi != null) + return bi; + break; + } + } + } + if (locale.equals(Locale.ROOT)) + return new WordBreakIterator(); + return getWordInstance(LocaleHelper.getFallbackLocale(locale)); + } + + /** + * This method tests whether or not the specified position is a text + * element boundary. + * + * @param pos The text position to test. + * + * @return <code>true</code> if the position is a boundary, + * <code>false</code> 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 pos 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 newText The <code>String</code> to iterate over. + */ + public void setText (String newText) + { + setText (new StringCharacterIterator (newText)); + } + + /** + * This method sets the text to iterate over from the specified + * <code>CharacterIterator</code>. + * + * @param newText The desired <code>CharacterIterator</code>. + */ + 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 000000000..e202d91df --- /dev/null +++ b/libjava/classpath/java/text/CharacterIterator.java @@ -0,0 +1,148 @@ +/* CharacterIterator.java -- Iterate over a character range + Copyright (C) 1998, 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 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 + * <code>getEndIndex() - 1</code>, it will not be incremented. + * + * @return The character at the position of the incremented index value, + * or {@link #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 {@link #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 {@link #DONE} if + * the range is empty. + */ + char first(); + + /** + * This method sets the index value to <code>getEndIndex() - 1</code> 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 {@link #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 {@link #DONE} if the index + * value is equal to {@link #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 <code>CharacterIterator</code>. + * + * @return A copy of this <code>CharacterIterator</code>. + */ + 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 000000000..4842f491d --- /dev/null +++ b/libjava/classpath/java/text/ChoiceFormat.java @@ -0,0 +1,506 @@ +/* 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 gnu.java.lang.CPStringBuilder; + +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 + * <code>i</code> will be selected for value <code>X</code> if + * <code>terminator[i] <= X < limit[i + 1]</code>. 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. + * <p> + * This sounds complicated, but that is because I did a poor job of + * explaining it. Consider the following example: + * <p> + * +<pre>terminators = { 1, ChoiceFormat.nextDouble(1) } +formats = { "file", "files" }</pre> + * + * <p> + * 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 <code>nextDouble</code>. 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. + * <p> + * 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 newPattern 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 (); + final CPStringBuilder buf = new CPStringBuilder (); + + 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 = Double.valueOf (newPattern.substring(dstart, index)); + + if (newPattern.charAt(index) == '<') + d = Double.valueOf (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 <code>ChoiceFormat</code> 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 <code>applyPattern</code> + * method. + * + * @param newPattern 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 <code>ChoiceFormat</code> 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: + * <ul> + * <li>The specified object is not <code>null</code>.</li> + * <li>The specified object is an instance of <code>ChoiceFormat</code>.</li> + * <li>The termination ranges and format strings are identical to + * this object's. </li> + * </ul> + * + * @param obj The object to test for equality against. + * + * @return <code>true</code> if the specified object is equal to + * this one, <code>false</code> 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 + * <code>StringBuffer</code> based on the supplied <code>long</code> + * argument. + * + * @param num The number used for determine (based on the range + * terminators) which format string to append. + * @param appendBuf The <code>StringBuffer</code> to append the format string + * to. + * @param pos Unused. + * + * @return The <code>StringBuffer</code> 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 + * <code>StringBuffer</code> based on the supplied <code>double</code> + * argument. + * + * @param num The number used for determine (based on the range + * terminators) which format string to append. + * @param appendBuf The <code>StringBuffer</code> to append the format string to. + * @param pos Unused. + * + * @return The <code>StringBuffer</code> 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 + * <code>Double.NaN</code> 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 + * <code>true</code>, 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 next <code>true</code> to return the next highest + * double, <code>false</code> 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 Double.valueOf (choiceLimits[i]); + } + } + pos.setErrorIndex(index); + return Double.valueOf (Double.NaN); + } + + /** + * This method returns the highest possible double less than the + * specified double. If the specified double value is equal to + * <code>Double.NaN</code> 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 (CPStringBuilder 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 <code>String</code> suitable for using with the + * <code>applyPattern</code> method. + * + * @return A pattern string for this object + */ + public String toPattern () + { + CPStringBuilder result = new CPStringBuilder (); + 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 000000000..0ca23d074 --- /dev/null +++ b/libjava/classpath/java/text/CollationElementIterator.java @@ -0,0 +1,490 @@ +/* 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 gnu.java.lang.CPStringBuilder; + +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 + * <code>String</code> as defined by the collation rules in an instance of + * <code>RuleBasedCollator</code>. There is no public constructor for + * this class. An instance is created by calling the + * <code>getCollationElementIterator</code> method on + * <code>RuleBasedCollator</code>. + * + * @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. + */ + CharacterIterator 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 <code>CollationElementIterator</code> + * to iterate over the specified <code>String</code> using the rules in the + * specified <code>RuleBasedCollator</code>. + * + * @param collator The <code>RuleBasedCollation</code> used for calculating collation values + * @param text The <code>String</code> to iterate over. + */ + CollationElementIterator(RuleBasedCollator collator, String text) + { + this.collator = collator; + + setText (text); + } + + /** + * This method initializes a new instance of <code>CollationElementIterator</code> + * to iterate over the specified <code>String</code> using the rules in the + * specified <code>RuleBasedCollator</code>. + * + * @param collator The <code>RuleBasedCollation</code> used for calculating collation values + * @param text The character iterator to iterate over. + */ + CollationElementIterator(RuleBasedCollator collator, CharacterIterator 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 <code>NULLORDER</code> 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 <code>NULLORDER</code> 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 order The collation value returned from <code>next()</code> or + * <code>previous()</code>. + * + * @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 <code>String</code> again. + */ + public void reset() + { + index = 0; + textIndex = 0; + } + + /** + * This method returns the secondary order value for the given collation + * value. + * + * @param order The collation value returned from <code>next()</code> or + * <code>previous()</code>. + * + * @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 order The collation value returned from <code>next()</code> or + * <code>previous()</code>. + * + * @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 <code>String</code> that it is iterating over + * to the specified <code>String</code>. + * + * @param text The new <code>String</code> 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 = new StringCharacterIterator(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 <code>String</code> that it is iterating over + * to the <code>String</code> represented by the specified + * <code>CharacterIterator</code>. + * + * @param source The <code>CharacterIterator</code> containing the new + * <code>String</code> to iterate over. + */ + public void setText(CharacterIterator source) + { + CPStringBuilder expand = new CPStringBuilder(); + + // 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 <code>String</code> + * 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 + * <code>String</code> to the specified value. This value must not + * be negative and must not be greater than the last index position + * in the <code>String</code>. + * + * @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.getEndIndex() - 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 + * + * @return 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 000000000..ce9db5f0d --- /dev/null +++ b/libjava/classpath/java/text/CollationKey.java @@ -0,0 +1,186 @@ +/* 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; + +import java.util.Arrays; + +/* 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 + * <code>String</code> for under a particular <code>Collator</code>. This + * value may be compared bitwise against another <code>CollationKey</code> + * representing a different <code>String</code> under the same + * <code>Collator</code> in a manner than is usually more efficient than + * using the raw <code>Collator</code> compare methods. There is overhead + * associated with calculating this value, so it is generally not + * advisable to compute <code>CollationKey</code>'s unless multiple + * comparisons against a <code>String</code> will be done. (For example, + * in a sort routine). + * <p> + * This class cannot be instantiated directly. Instead, a + * <code>CollationKey</code> is created by calling the + * <code>getCollationKey</code> method on an instance of <code>Collator</code>. + * + * @author Aaron M. Renn (arenn@urbanophile.com) + * @author Tom Tromey (tromey@cygnus.com) + * @date March 25, 1999 + */ +public class CollationKey implements Comparable<CollationKey> +{ + /** + * This is the <code>Collator</code> this object was created from. + */ + private Collator collator; + + /** + * This is the <code>String</code> 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 <code>CollationKey</code> 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 tests the specified <code>Object</code> for equality with + * this object. This will be true if and only if: + * <p> + * <ul> + * <li>The specified object must not be <code>null</code></li> + * <li>The specified object is an instance of <code>CollationKey</code>.</li> + * <li>The specified object was created from the same <code>Collator</code> + * as this object.</li> + * <li>The specified object has the same source string and bit key as + * this object.</li> + * </ul> + * + * @param obj The <code>Object</code> to test for equality. + * + * @return <code>true</code> if the specified object is equal to this one, <code>false</code> 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 (! Arrays.equals (ck.toByteArray (), toByteArray ())) + return false; + + return true; + } + + /** + * This method returns the <code>String</code> that this object was created + * from. + * + * @return The source <code>String</code> 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. + * + * @return 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 000000000..a9fc55ccf --- /dev/null +++ b/libjava/classpath/java/text/Collator.java @@ -0,0 +1,425 @@ +/* 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 gnu.java.locale.LocaleHelper; + +import java.text.spi.CollatorProvider; + +import java.util.Comparator; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.ServiceLoader; + +/** + * This class is the abstract superclass of classes which perform + * locale dependent <code>String</code> comparisons. A caller requests + * an instance of <code>Collator</code> for a particular locale using + * the <code>getInstance()</code> static method in this class. That method + * will return a locale specific subclass of <code>Collator</code> which + * can be used to perform <code>String</code> comparisons for that locale. + * If a subclass of <code>Collator</code> 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 <code>String</code> 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 + */ +public abstract class Collator implements Comparator<Object>, 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 <code>Collator</code> 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 <code>getInstance()</code> methods of this class to instantiate + * <code>Collation</code> objects for the desired locale. + */ + protected Collator () + { + strength = TERTIARY; + decmp = CANONICAL_DECOMPOSITION; + } + + /** + * This method compares the two <code>String</code>'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 + * <code>Collator</code> and the strength and decomposition rules in + * effect. + * + * @param source The first object to compare + * @param target 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 <code>Object</code>'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 <code>String</code>'s or an exception will be thrown. + * + * @param o1 The first object to compare + * @param o2 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 <code>String</code>. + */ + 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: + * <ul> + * <li>The specified object is not <code>null</code>.</li> + * <li>The specified object is an instance of <code>Collator</code>.</li> + * <li>The specified object has the same strength and decomposition + * settings as this object.</li> + * </ul> + * + * @param obj The <code>Object</code> to test for equality against + * this object. + * + * @return <code>true</code> if the specified object is equal to + * this one, <code>false</code> 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 <code>String</code>'s are equal + * according to the collation rules for the locale of this object and + * the current strength and decomposition settings. + * + * @param source The first <code>String</code> to compare + * @param target The second <code>String</code> to compare + * + * @return <code>true</code> if the two strings are equal, + * <code>false</code> otherwise. + */ + public boolean equals (String source, String target) + { + return compare (source, target) == 0; + } + + /** + * This method returns a copy of this <code>Collator</code> object. + * + * @return A duplicate of this object. + */ + public Object clone () + { + try + { + return super.clone (); + } + catch (CloneNotSupportedException _) + { + return null; + } + } + + /** + * This method returns an array of <code>Locale</code> objects which is + * the list of locales for which <code>Collator</code> objects exist. + * + * @return The list of locales for which <code>Collator</code>'s exist. + */ + public static synchronized Locale[] getAvailableLocales () + { + return LocaleHelper.getCollatorLocales(); + } + + /** + * This method transforms the specified <code>String</code> into a + * <code>CollationKey</code> for faster comparisons. This is useful when + * comparisons against a string might be performed multiple times, such + * as during a sort operation. + * + * @param source The <code>String</code> to convert. + * + * @return A <code>CollationKey</code> for the specified <code>String</code>. + */ + 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 <code>Collator</code> for the + * default locale. + * + * @return A <code>Collator</code> for the default locale. + */ + public static Collator getInstance () + { + return getInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of <code>Collator</code> for the + * specified locale. If no <code>Collator</code> exists for the desired + * locale, the fallback procedure described in + * {@link java.util.spi.LocaleServiceProvider} is invoked. + * + * @param loc The desired locale to load a <code>Collator</code> for. + * + * @return A <code>Collator</code> for the requested locale + */ + public static Collator getInstance (Locale loc) + { + String pattern; + try + { + ResourceBundle res = + ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + loc, ClassLoader.getSystemClassLoader()); + return new RuleBasedCollator(res.getString("collation_rules")); + } + catch (MissingResourceException x) + { + /* This means runtime support for the locale + * is not available, so we check providers. */ + } + catch (ParseException x) + { + throw (InternalError)new InternalError().initCause(x); + } + for (CollatorProvider p : ServiceLoader.load(CollatorProvider.class)) + { + for (Locale l : p.getAvailableLocales()) + { + if (l.equals(loc)) + { + Collator c = p.getInstance(loc); + if (c != null) + return c; + break; + } + } + } + if (loc.equals(Locale.ROOT)) + { + try + { + return new RuleBasedCollator("<0<1<2<3<4<5<6<7<8<9<A,a<b,B<c," + + "C<d,D<e,E<f,F<g,G<h,H<i,I<j,J<k,K" + + "<l,L<m,M<n,N<o,O<p,P<q,Q<r,R<s,S<t,"+ + "T<u,U<v,V<w,W<x,X<y,Y<z,Z"); + } + catch (ParseException x) + { + throw (InternalError)new InternalError().initCause(x); + } + } + return getInstance(LocaleHelper.getFallbackLocale(loc)); + } + + /** + * This method returns the current strength setting for this object. This + * will be one of PRIMARY, SECONDARY, TERTIARY, or IDENTICAL. See the + * documentation for those constants for an explanation of this setting. + * + * @return The current strength setting. + */ + public synchronized int getStrength () + { + return strength; + } + + /** + * This method returns a hash code value for this object. + * + * @return A hash value for this object. + */ + public abstract int hashCode (); + + /** + * This method sets the decomposition setting for this object to the + * specified value. This must be one of NO_DECOMPOSITION, + * CANONICAL_DECOMPOSITION, or FULL_DECOMPOSITION. Otherwise an + * exception will be thrown. See the documentation for those + * contants for an explanation of this setting. + * + * @param mode The new decomposition setting. + * + * @exception IllegalArgumentException If the requested + * decomposition setting is not valid. + */ + public synchronized void setDecomposition (int mode) + { + if (mode != NO_DECOMPOSITION + && mode != CANONICAL_DECOMPOSITION + && mode != FULL_DECOMPOSITION) + throw new IllegalArgumentException (); + decmp = mode; + } + + /** + * This method sets the strength setting for this object to the specified + * value. This must be one of PRIMARY, SECONDARY, TERTIARY, or IDENTICAL. + * Otherwise an exception is thrown. See the documentation for these + * constants for an explanation of this setting. + * + * @param strength The new strength setting. + * + * @exception IllegalArgumentException If the requested strength + * setting value is not valid. + */ + public synchronized void setStrength (int strength) + { + if (strength != PRIMARY && strength != SECONDARY + && strength != TERTIARY && strength != IDENTICAL) + throw new IllegalArgumentException (); + this.strength = strength; + } + + // Decompose a single character and append results to the buffer. + // FIXME: for libgcj this is a native method which handles + // decomposition. For Classpath, for now, it does nothing. + /* + final void decomposeCharacter (char c, StringBuffer buf) + { + buf.append (c); + } + */ + + /** + * This is the current collation decomposition setting. + */ + int decmp; + + /** + * This is the current collation strength setting. + */ + int strength; +} diff --git a/libjava/classpath/java/text/DateFormat.java b/libjava/classpath/java/text/DateFormat.java new file mode 100644 index 000000000..1757a0cb9 --- /dev/null +++ b/libjava/classpath/java/text/DateFormat.java @@ -0,0 +1,955 @@ +/* DateFormat.java -- Class for formatting/parsing date/times + 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.locale.LocaleHelper; + +import java.text.spi.DateFormatProvider; + +import java.io.InvalidObjectException; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.TimeZone; + +/** + * @author Per Bothner (bothner@cygnus.com) + * @date October 25, 1998. + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 beta from http://www.javasoft.com. + * Status: Mostly complete; search for FIXME to see omissions. + */ + +public abstract class DateFormat extends Format implements Cloneable +{ + private static final long serialVersionUID = 7218322306649953788L; + + // Names fixed by serialization spec. + protected Calendar calendar; + protected NumberFormat numberFormat; + + // (Values determined using a test program.) + public static final int FULL = 0; + public static final int LONG = 1; + public static final int MEDIUM = 2; + public static final int SHORT = 3; + public static final int DEFAULT = MEDIUM; + + /* These constants need to have these exact values. They + * correspond to index positions within the localPatternChars + * string for a given locale. Each locale may specify its + * own character for a particular field, but the position + * of these characters must correspond to an appropriate field + * number (as listed below), in order for their meaning to + * be determined. For example, the US locale uses + * the string "GyMdkHmsSEDFwWahKzYeugAZ", where 'G' is the character + * for era, 'y' for year, and so on down to 'Z' for time zone. + */ + /** + * Represents the position of the era + * pattern character in the array of + * localized pattern characters. + * For example, 'AD' is an era used + * in the Gregorian calendar system. + * In the U.S. locale, this is 'G'. + */ + public static final int ERA_FIELD = 0; + /** + * Represents the position of the year + * pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'y'. + */ + public static final int YEAR_FIELD = 1; + /** + * Represents the position of the month + * pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'M'. + */ + public static final int MONTH_FIELD = 2; + /** + * Represents the position of the date + * or day of the month pattern character + * in the array of localized pattern + * characters. In the U.S. locale, + * this is 'd'. + */ + public static final int DATE_FIELD = 3; + /** + * Represents the position of the 24 + * hour pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'k'. + * This field numbers hours from 1 to 24. + */ + public static final int HOUR_OF_DAY1_FIELD = 4; + /** + * Represents the position of the 24 + * hour pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'H'. + * This field numbers hours from 0 to 23. + */ + public static final int HOUR_OF_DAY0_FIELD = 5; + /** + * Represents the position of the minute + * pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'm'. + */ + public static final int MINUTE_FIELD = 6; + /** + * Represents the position of the second + * pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 's'. + */ + public static final int SECOND_FIELD = 7; + /** + * Represents the position of the millisecond + * pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'S'. + */ + public static final int MILLISECOND_FIELD = 8; + /** + * Represents the position of the day of the + * week pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'E'. + */ + public static final int DAY_OF_WEEK_FIELD = 9; + /** + * Represents the position of the day of the + * year pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'D'. + */ + public static final int DAY_OF_YEAR_FIELD = 10; + /** + * Represents the position of the day of the + * week in the month pattern character in the + * array of localized pattern characters. + * In the U.S. locale, this is 'F'. + */ + public static final int DAY_OF_WEEK_IN_MONTH_FIELD = 11; + /** + * Represents the position of the week of the + * year pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'w'. + */ + public static final int WEEK_OF_YEAR_FIELD = 12; + /** + * Represents the position of the week of the + * month pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'W'. + */ + public static final int WEEK_OF_MONTH_FIELD = 13; + /** + * Represents the position of the am/pm + * pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'a'. + */ + public static final int AM_PM_FIELD = 14; + /** + * Represents the position of the 12 + * hour pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'h'. + * This field numbers hours from 1 to 12. + */ + public static final int HOUR1_FIELD = 15; + /** + * Represents the position of the 12 + * hour pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'K'. + * This field numbers hours from 0 to 11. + */ + public static final int HOUR0_FIELD = 16; + /** + * Represents the position of the generic + * timezone pattern character in the array of + * localized pattern characters. + * In the U.S. locale, this is 'z'. + */ + public static final int TIMEZONE_FIELD = 17; + + public static class Field extends Format.Field + { + static final long serialVersionUID = 7441350119349544720L; + + private int calendarField; + + public static final DateFormat.Field ERA + = new Field("era", Calendar.ERA); + public static final DateFormat.Field YEAR + = new Field("year", Calendar.YEAR); + public static final DateFormat.Field MONTH + = new Field("month", Calendar.MONTH); + public static final DateFormat.Field DAY_OF_MONTH + = new Field("day of month", Calendar.DAY_OF_MONTH); + public static final DateFormat.Field HOUR_OF_DAY1 + = new Field("hour of day 1", Calendar.HOUR_OF_DAY); + public static final DateFormat.Field HOUR_OF_DAY0 + = new Field("hour of day 0", Calendar.HOUR_OF_DAY); + public static final DateFormat.Field MINUTE + = new Field("minute", Calendar.MINUTE); + public static final DateFormat.Field SECOND + = new Field("second", Calendar.SECOND); + public static final DateFormat.Field MILLISECOND + = new Field("millisecond", Calendar.MILLISECOND); + public static final DateFormat.Field DAY_OF_WEEK + = new Field("day of week", Calendar.DAY_OF_WEEK); + public static final DateFormat.Field DAY_OF_YEAR + = new Field("day of year", Calendar.DAY_OF_YEAR); + public static final DateFormat.Field DAY_OF_WEEK_IN_MONTH + = new Field("day of week in month", Calendar.DAY_OF_WEEK_IN_MONTH); + public static final DateFormat.Field WEEK_OF_YEAR + = new Field("week of year", Calendar.WEEK_OF_YEAR); + public static final DateFormat.Field WEEK_OF_MONTH + = new Field("week of month", Calendar.WEEK_OF_MONTH); + public static final DateFormat.Field AM_PM + = new Field("am/pm", Calendar.AM_PM); + public static final DateFormat.Field HOUR1 + = new Field("hour1", Calendar.HOUR); + public static final DateFormat.Field HOUR0 + = new Field("hour0", Calendar.HOUR); + public static final DateFormat.Field TIME_ZONE + = new Field("timezone", Calendar.ZONE_OFFSET); + + static final DateFormat.Field[] allFields = + { + ERA, YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY1, + HOUR_OF_DAY0, MINUTE, SECOND, MILLISECOND, + DAY_OF_WEEK, DAY_OF_YEAR, DAY_OF_WEEK_IN_MONTH, + WEEK_OF_YEAR, WEEK_OF_MONTH, AM_PM, HOUR1, HOUR0, + TIME_ZONE + }; + + // For deserialization + private Field() + { + super(""); + } + + protected Field(String name, int calendarField) + { + super(name); + this.calendarField = calendarField; + } + + public int getCalendarField() + { + return calendarField; + } + + public static Field ofCalendarField(int calendarField) + { + if (calendarField >= 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;i<allFields.length;i++) + if (s.equals(allFields[i].getName())) + return allFields[i]; + + throw new InvalidObjectException("no such DateFormat field called " + s); + } + } + + /** + * This method initializes a new instance of <code>DateFormat</code>. + */ + 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: + * <P> + * <ul> + * <li>Is not <code>null</code>.</li> + * <li>Is an instance of <code>DateFormat</code>.</li> + * <li>Has equal numberFormat field as this object.</li> + * <li>Has equal (Calendar) TimeZone rules as this object.</li> + * <li>Has equal (Calendar) isLenient results.</li> + * <li>Has equal Calendar first day of week and minimal days in week + * values.</li> + * </ul> + * Note that not all properties of the Calendar are relevant for a + * DateFormat. For formatting only the fact whether or not the + * TimeZone has the same rules and whether the calendar is lenient + * and has the same week rules is compared for this implementation + * of equals. Other properties of the Calendar (such as the time) + * are not taken into account. + * + * @param obj The object to test for equality against. + * + * @return <code>true</code> if the specified object is equal to this object, + * <code>false</code> otherwise. + */ + public boolean equals (Object obj) + { + if (!(obj instanceof DateFormat)) + return false; + + DateFormat d = (DateFormat) obj; + TimeZone tz = getTimeZone(); + TimeZone tzd = d.getTimeZone(); + if (tz.hasSameRules(tzd)) + if (isLenient() == d.isLenient()) + { + Calendar c = getCalendar(); + Calendar cd = d.getCalendar(); + if ((c == null && cd == null) + || + (c.getFirstDayOfWeek() == cd.getFirstDayOfWeek() + && + c.getMinimalDaysInFirstWeek() + == cd.getMinimalDaysInFirstWeek())) + return ((numberFormat == null && d.numberFormat == null) + || numberFormat.equals(d.numberFormat)); + } + + return false; + } + + /** + * 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 <code>Object</code> into a date string + * and appends it to the specified <code>StringBuffer</code>. + * The specified object must be an instance of <code>Number</code> or + * <code>Date</code> or an <code>IllegalArgumentException</code> will be + * thrown. + * + * @param obj The <code>Object</code> to format. + * @param buf The <code>StringBuffer</code> to append the resultant + * <code>String</code> to. + * @param pos Is updated to the start and end index of the + * specified field. + * + * @return The <code>StringBuffer</code> 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 <code>Date</code> into a string and appends it + * to the specified <code>StringBuffer</code>. + * + * @param date The <code>Date</code> value to format. + * @param buf The <code>StringBuffer</code> to append the resultant + * <code>String</code> to. + * @param pos Is updated to the start and end index of the + * specified field. + * + * @return The <code>StringBuffer</code> 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 <code>Calendar</code> object being used by + * this object to parse/format datetimes. + * + * @return The <code>Calendar</code> 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) + throws MissingResourceException + { + if (loc.equals(Locale.ROOT)) + return computeDefault(dateStyle,timeStyle,use_date,use_time); + + ResourceBundle res = + ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + loc, ClassLoader.getSystemClassLoader()); + + 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); + } + + private static DateFormat computeDefault (int dateStyle, int timeStyle, + boolean use_date, boolean use_time) + { + String pattern = null; + if (use_date) + { + switch (dateStyle) + { + case FULL: + pattern = "EEEE MMMM d, yyyy G"; + break; + case LONG: + pattern = "MMMM d, yyyy"; + break; + case MEDIUM: + pattern = "d-MMM-yy"; + break; + case SHORT: + pattern = "M/d/yy"; + default: + throw new IllegalArgumentException (); + } + } + + if (use_time) + { + if (pattern == null) + pattern = ""; + else + pattern += " "; + + switch (timeStyle) + { + case FULL: + pattern += "h:mm:ss;S 'o''clock' a z"; + break; + case LONG: + pattern += "h:mm:ss a z"; + break; + case MEDIUM: + pattern += "h:mm:ss a"; + break; + case SHORT: + pattern += "h:mm a"; + break; + default: + throw new IllegalArgumentException (); + } + } + + return new SimpleDateFormat (pattern, Locale.ROOT); + } + + /** + * This method returns an instance of <code>DateFormat</code> that will + * format using the default formatting style for dates. + * + * @return A new <code>DateFormat</code> instance. + */ + public static final DateFormat getDateInstance () + { + return getDateInstance (DEFAULT, Locale.getDefault()); + } + + /** + * This method returns an instance of <code>DateFormat</code> that will + * format using the specified formatting style for dates. + * + * @param style The type of formatting to perform. + * + * @return A new <code>DateFormat</code> instance. + */ + public static final DateFormat getDateInstance (int style) + { + return getDateInstance (style, Locale.getDefault()); + } + + /** + * This method returns an instance of <code>DateFormat</code> 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 loc The desired locale. + * + * @return A new <code>DateFormat</code> instance. + */ + public static final DateFormat getDateInstance (int style, Locale loc) + { + try + { + return computeInstance (style, loc, true, false); + } + catch (MissingResourceException e) + { + for (DateFormatProvider p : + ServiceLoader.load(DateFormatProvider.class)) + { + for (Locale l : p.getAvailableLocales()) + { + if (l.equals(loc)) + { + DateFormat df = p.getDateInstance(style, loc); + if (df != null) + return df; + break; + } + } + } + return getDateInstance(style, + LocaleHelper.getFallbackLocale(loc)); + } + } + + /** + * This method returns a new instance of <code>DateFormat</code> that + * formats both dates and times using the <code>SHORT</code> style. + * + * @return A new <code>DateFormat</code>instance. + */ + public static final DateFormat getDateTimeInstance () + { + return getDateTimeInstance (DEFAULT, DEFAULT, Locale.getDefault()); + } + + /** + * This method returns a new instance of <code>DateFormat</code> that + * formats both dates and times using the <code>DEFAULT</code> style. + * + * @return A new <code>DateFormat</code>instance. + */ + public static final DateFormat getDateTimeInstance (int dateStyle, + int timeStyle) + { + return getDateTimeInstance (dateStyle, timeStyle, Locale.getDefault()); + } + + /** + * This method returns a new instance of <code>DateFormat</code> 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 <code>DateFormat</code>instance. + */ + public static final DateFormat getDateTimeInstance (int dateStyle, + int timeStyle, + Locale loc) + { + try + { + return computeInstance (dateStyle, timeStyle, loc, true, true); + } + catch (MissingResourceException e) + { + for (DateFormatProvider p : + ServiceLoader.load(DateFormatProvider.class)) + { + for (Locale l : p.getAvailableLocales()) + { + if (l.equals(loc)) + { + DateFormat df = p.getDateTimeInstance(dateStyle, + timeStyle, loc); + if (df != null) + return df; + break; + } + } + } + return getDateTimeInstance(dateStyle, timeStyle, + LocaleHelper.getFallbackLocale(loc)); + } + } + + /** + * This method returns a new instance of <code>DateFormat</code> that + * formats both dates and times using the <code>SHORT</code> style. + * + * @return A new <code>DateFormat</code>instance. + */ + public static final DateFormat getInstance () + { + // JCL book says SHORT. + return getDateTimeInstance (SHORT, SHORT, Locale.getDefault()); + } + + /** + * This method returns the <code>NumberFormat</code> object being used + * by this object to parse/format time values. + * + * @return The <code>NumberFormat</code> in use by this object. + */ + public NumberFormat getNumberFormat () + { + return numberFormat; + } + + /** + * This method returns an instance of <code>DateFormat</code> that will + * format using the default formatting style for times. + * + * @return A new <code>DateFormat</code> instance. + */ + public static final DateFormat getTimeInstance () + { + return getTimeInstance (DEFAULT, Locale.getDefault()); + } + + /** + * This method returns an instance of <code>DateFormat</code> that will + * format using the specified formatting style for times. + * + * @param style The type of formatting to perform. + * + * @return A new <code>DateFormat</code> instance. + */ + public static final DateFormat getTimeInstance (int style) + { + return getTimeInstance (style, Locale.getDefault()); + } + + /** + * This method returns an instance of <code>DateFormat</code> 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 loc The desired locale. + * + * @return A new <code>DateFormat</code> instance. + */ + public static final DateFormat getTimeInstance (int style, Locale loc) + { + try + { + return computeInstance (style, loc, false, true); + } + catch (MissingResourceException e) + { + for (DateFormatProvider p : + ServiceLoader.load(DateFormatProvider.class)) + { + for (Locale l : p.getAvailableLocales()) + { + if (l.equals(loc)) + { + DateFormat df = p.getTimeInstance(style, loc); + if (df != null) + return df; + break; + } + } + } + return getTimeInstance(style, + LocaleHelper.getFallbackLocale(loc)); + } + } + + /** + * This method returns the <code>TimeZone</code> 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 <code>true</code> if date/time parsing is lenient, + * <code>false</code> 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 <code>String</code> into a + * <code>Date</code>. The <code>pos</code> argument contains the + * starting parse position on method entry and the ending parse + * position on method exit. + * + * @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 <code>null</code> if the string cannot + * be parsed. + */ + public abstract Date parse (String source, ParsePosition pos); + + /** + * This method is identical to <code>parse(String, ParsePosition)</code>, + * but returns its result as an <code>Object</code> instead of a + * <code>Date</code>. + * + * @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 <code>null</code> if the string cannot + * be parsed. + */ + public Object parseObject (String source, ParsePosition pos) + { + return parse(source, pos); + } + + /** + * This method specified the <code>Calendar</code> that should be used + * by this object to parse/format datetimes. + * + * @param calendar The new <code>Calendar</code> 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 <code>true</code> if parsing should be lenient, + * <code>false</code> otherwise. + */ + public void setLenient (boolean lenient) + { + calendar.setLenient(lenient); + } + + /** + * This method specifies the <code>NumberFormat</code> object that should + * be used by this object to parse/format times. + * + * @param numberFormat The <code>NumberFormat</code> 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 timeZone 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 000000000..c22dd38f7 --- /dev/null +++ b/libjava/classpath/java/text/DateFormatSymbols.java @@ -0,0 +1,761 @@ +/* DateFormatSymbols.java -- Format over a range of numbers + Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005, 2006 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.locale.LocaleHelper; + +import java.io.IOException; + +import java.text.spi.DateFormatSymbolsProvider; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.TimeZone; + +import java.util.spi.TimeZoneNameProvider; + +/** + * 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) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @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; + + /** + * The set of properties for obtaining the metazone data. + */ + private static transient final Properties properties; + + /** + * Reads in the properties. + */ + static + { + properties = new Properties(); + try + { + properties.load(DateFormatSymbols.class.getResourceAsStream("metazones.properties")); + } + catch (IOException exception) + { + System.out.println("Failed to load weeks resource: " + exception); + } + } + + /** + * The timezone strings supplied by the runtime. + */ + private String[][] runtimeZoneStrings; + + /** + * Custom timezone strings supplied by {@link #setZoneStrings()}. + */ + 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, Locale locale) + { + List<String[]> allZones = new ArrayList<String[]>(); + try + { + Map<String,String[]> systemZones = new HashMap<String,String[]>(); + while (true) + { + int index = 0; + String country = locale.getCountry(); + String data = res.getString("zoneStrings"); + String[] zones = data.split("\u00a9"); + for (int a = 0; a < zones.length; ++a) + { + String[] strings = zones[a].split("\u00ae"); + String type = properties.getProperty(strings[0] + "." + country); + if (type == null) + type = properties.getProperty(strings[0] + ".DEFAULT"); + if (type != null) + strings[0] = type; + if (strings.length < 5) + { + String[] newStrings = new String[5]; + System.arraycopy(strings, 0, newStrings, 0, strings.length); + for (int b = strings.length; b < newStrings.length; ++b) + newStrings[b] = ""; + strings = newStrings; + } + String[] existing = systemZones.get(strings[0]); + if (existing != null && existing.length > 1) + { + for (int b = 1; b < existing.length; ++b) + if (!existing[b].equals("")) + strings[b] = existing[b]; + } + systemZones.put(strings[0], strings); + } + if (res.getLocale() == Locale.ROOT) + break; + else + res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + LocaleHelper.getFallbackLocale(res.getLocale()), + ClassLoader.getSystemClassLoader()); + } + /* Final sanity check for missing values */ + for (String[] zstrings : systemZones.values()) + { + if (zstrings[1].equals("") && zstrings[2].equals("")) + { + for (Map.Entry<Object,Object> entry : properties.entrySet()) + { + String val = (String) entry.getValue(); + if (val.equals(zstrings[0])) + { + String key = (String) entry.getKey(); + String metazone = key.substring(0, key.indexOf(".")); + String type = properties.getProperty(metazone + "." + locale.getCountry()); + if (type == null) + type = properties.getProperty(metazone + ".DEFAULT"); + if (type != null) + { + String[] ostrings = systemZones.get(type); + zstrings[1] = ostrings[1]; + zstrings[2] = ostrings[2]; + } + } + } + } + } + allZones.addAll(systemZones.values()); + } + catch (MissingResourceException e) + { + /* This means runtime support for the locale + * is not available, so we just include providers. */ + } + for (TimeZoneNameProvider p : + ServiceLoader.load(TimeZoneNameProvider.class)) + { + for (Locale loc : p.getAvailableLocales()) + { + if (loc.equals(locale)) + { + for (String id : TimeZone.getAvailableIDs()) + { + String[] z = new String[5]; + z[0] = id; + z[1] = p.getDisplayName(id, false, + TimeZone.LONG, + locale); + z[2] = p.getDisplayName(id, false, + TimeZone.SHORT, + locale); + z[3] = p.getDisplayName(id, true, + TimeZone.LONG, + locale); + z[4] = p.getDisplayName(id, true, + TimeZone.SHORT, + locale); + allZones.add(z); + } + break; + } + } + } + return allZones.toArray(new String[allZones.size()][]); + } + + 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 <code>DateFormatSymbols</code> + * by loading the date format information for the specified locale. + * This constructor only obtains instances using the runtime's resources; + * to also include {@link java.text.spi.DateFormatSymbolsProvider} instances, + * call {@link #getInstance(java.util.Locale)} instead. + * + * @param locale The locale for which date formatting symbols should + * be loaded. + * @throws MissingResourceException if the resources for the specified + * locale could not be found or loaded. + * @see #getInstance(java.util.Locale) + */ + 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"); + dateFormats = formatsForKey(res, "DateFormat"); + timeFormats = formatsForKey(res, "TimeFormat"); + runtimeZoneStrings = getZoneStrings(res, locale); + } + + /** + * This method loads the format symbol information for the default + * locale. This constructor only obtains instances using the runtime's resources; + * to also include {@link java.text.spi.DateFormatSymbolsProvider} instances, + * call {@link #getInstance()} instead. + * + * @throws MissingResourceException if the resources for the default + * locale could not be found or loaded. + * @see #getInstance() + */ + 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 <code>String</code> array indexed by + * <code>Calendar.AM</code> and <code>Calendar.PM</code> + * + * @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 <code>String</code> + * array indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>. + * + * @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 + * <code>SimpleDateFormat</code>. 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): + * <p> + * <ul> + * <li>0 - era (G)</li> + * <li>1 - year (y)</li> + * <li>2 - month (M)</li> + * <li>3 - day of month (d)</li> + * <li>4 - hour out of 12, from 1-12 (h)</li> + * <li>5 - hour out of 24, from 0-23 (H)</li> + * <li>6 - minute (m)</li> + * <li>7 - second (s)</li> + * <li>8 - millisecond (S)</li> + * <li>9 - date of week (E)</li> + * <li>10 - date of year (D)</li> + * <li>11 - day of week in month, eg. "4th Thur in Nov" (F)</li> + * <li>12 - week in year (w)</li> + * <li>13 - week in month (W)</li> + * <li>14 - am/pm (a)</li> + * <li>15 - hour out of 24, from 1-24 (k)</li> + * <li>16 - hour out of 12, from 0-11 (K)</li> + * <li>17 - time zone (z)</li> + * </ul> + * + * @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 <code>Calendar.JANUARY</code> through + * <code>Calendar.UNDECEMBER</code>. 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 + * <code>String</code> array indexed by <code>Calendar.JANUARY</code> + * through <code>Calendar.UNDECEMBER</code>. 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 + * <code>String</code> array indexed by <code>Calendar.SUNDAY</code> + * through <code>Calendar.SATURDAY</code>. 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 + * <code>String</code> array indexed by <code>Calendar.SUNDAY</code> + * through <code>Calendar.SATURDAY</code>. 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 <code>String</code> array where each row in + * the array contains five values: + * <P> + * <ul> + * <li>0 - The non-localized time zone id string.</li> + * <li>1 - The long name of the time zone (standard time).</li> + * <li>2 - The short name of the time zone (standard time).</li> + * <li>3 - The long name of the time zone (daylight savings time).</li> + * <li>4 - the short name of the time zone (daylight savings time).</li> + * </ul> + * <p> + * If {@link #setZoneStrings(String[][])} has been called, then the value + * passed to this will be returned. Otherwise the returned array contains + * zone names provided by the runtime environment and any + * {@link java.util.spi.TimeZoneProvider} instances. + * </p> + * + * @return The list of time zone display strings. + * @see #setZoneStrings(String[][]) + */ + public String[][] getZoneStrings() + { + if (zoneStrings != null) + return zoneStrings; + return runtimeZoneStrings; + } + + /** + * This method sets the list of strings used to display AM/PM values to + * the specified list. + * This is a two element <code>String</code> array indexed by + * <code>Calendar.AM</code> and <code>Calendar.PM</code> + * + * @param value The new list of AM/PM display strings. + */ + public void setAmPmStrings (String[] value) + { + if(value==null) + throw new NullPointerException(); + ampms = value; + } + + /** + * This method sets the list of strings used to display time eras to + * to the specified list. + * This is a two element <code>String</code> + * array indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>. + * + * @param labels The new list of era display strings. + */ + public void setEras (String[] labels) + { + if(labels==null) + throw new NullPointerException(); + eras = labels; + } + + /** + * 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 + * <code>SimpleDateFormat</code>. 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): + * <p> + * <ul> + * <li>0 - era (G)</li> + * <li>1 - year (y)</li> + * <li>2 - month (M)</li> + * <li>3 - day of month (d)</li> + * <li>4 - hour out of 12, from 1-12 (h)</li> + * <li>5 - hour out of 24, from 0-23 (H)</li> + * <li>6 - minute (m)</li> + * <li>7 - second (s)</li> + * <li>8 - millisecond (S)</li> + * <li>9 - date of week (E)</li> + * <li>10 - date of year (D)</li> + * <li>11 - day of week in month, eg. "4th Thur in Nov" (F)</li> + * <li>12 - week in year (w)</li> + * <li>13 - week in month (W)</li> + * <li>14 - am/pm (a)</li> + * <li>15 - hour out of 24, from 1-24 (k)</li> + * <li>16 - hour out of 12, from 0-11 (K)</li> + * <li>17 - time zone (z)</li> + * </ul> + * + * @param chars The new format pattern characters + */ + public void setLocalPatternChars (String chars) + { + if(chars==null) + throw new NullPointerException(); + localPatternChars = chars; + } + + /** + * This method sets the list of strings used to display month names. + * This is a thirteen element + * string array indexed by <code>Calendar.JANUARY</code> through + * <code>Calendar.UNDECEMBER</code>. Note that there are thirteen + * elements because some calendars have thriteen months. + * + * @param labels The list of month display strings. + */ + public void setMonths (String[] labels) + { + if(labels==null) + throw new NullPointerException(); + months = labels; + } + + /** + * This method sets the list of strings used to display abbreviated month + * names. + * This is a thirteen element + * <code>String</code> array indexed by <code>Calendar.JANUARY</code> + * through <code>Calendar.UNDECEMBER</code>. Note that there are thirteen + * elements because some calendars have thirteen months. + * + * @param labels The new list of abbreviated month display strings. + */ + public void setShortMonths (String[] labels) + { + if(labels==null) + throw new NullPointerException(); + shortMonths = labels; + } + + /** + * This method sets the list of strings used to display abbreviated + * weekday names. + * This is an eight element + * <code>String</code> array indexed by <code>Calendar.SUNDAY</code> + * through <code>Calendar.SATURDAY</code>. Note that the first element + * of this array is ignored. + * + * @param labels This list of abbreviated weekday display strings. + */ + public void setShortWeekdays (String[] labels) + { + if(labels==null) + throw new NullPointerException(); + shortWeekdays = labels; + } + + /** + * This method sets the list of strings used to display weekday names. + * This is an eight element + * <code>String</code> array indexed by <code>Calendar.SUNDAY</code> + * through <code>Calendar.SATURDAY</code>. Note that the first element + * of this array is ignored. + * + * @param labels This list of weekday display strings. + */ + public void setWeekdays (String[] labels) + { + if(labels==null) + throw new NullPointerException(); + weekdays = labels; + } + + /** + * This method sets the list of display strings for time zones. + * This is a two dimensional <code>String</code> array where each row in + * the array contains five values: + * <P> + * <ul> + * <li>0 - The non-localized time zone id string.</li> + * <li>1 - The long name of the time zone (standard time).</li> + * <li>2 - The short name of the time zone (standard time).</li> + * <li>3 - The long name of the time zone (daylight savings time).</li> + * <li>4 - the short name of the time zone (daylight savings time).</li> + * </ul> + * + * @params zones The list of time zone display strings. + */ + public void setZoneStrings (String[][] zones) + { + if(zones==null) + throw new NullPointerException(); + zoneStrings = zones; + } + + /* 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: + * <p> + * <ul> + * <li> Is not <code>null</code>.</li> + * <li> Is an instance of <code>DateFormatSymbols</code>.</li> + * <li> Contains identical formatting symbols to this object.</li> + * </ul> + * + * @param obj The <code>Object</code> to test for equality against. + * + * @return <code>true</code> if the specified object is equal to this one, + * <code>false</code> 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. + * + * @return 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)); + } + + /** + * Returns a {@link DateFormatSymbols} instance for the + * default locale obtained from either the runtime itself + * or one of the installed + * {@link java.text.spi.DateFormatSymbolsProvider} instances. + * This is equivalent to calling + * <code>getInstance(Locale.getDefault())</code>. + * + * @return a {@link DateFormatSymbols} instance for the default + * locale. + * @since 1.6 + */ + public static final DateFormatSymbols getInstance() + { + return getInstance(Locale.getDefault()); + } + + /** + * Returns a {@link DateFormatSymbols} instance for the + * specified locale obtained from either the runtime itself + * or one of the installed + * {@link java.text.spi.DateFormatSymbolsProvider} instances. + * + * @param locale the locale for which an instance should be + * returned. + * @return a {@link DateFormatSymbols} instance for the specified + * locale. + * @throws NullPointerException if <code>locale</code> is + * <code>null</code>. + * @since 1.6 + */ + public static final DateFormatSymbols getInstance(Locale locale) + { + try + { + DateFormatSymbols syms = new DateFormatSymbols(locale); + return syms; + } + catch (MissingResourceException e) + { + /* This means runtime support for the locale + * is not available, so we check providers. */ + } + for (DateFormatSymbolsProvider p : + ServiceLoader.load(DateFormatSymbolsProvider.class)) + { + for (Locale loc : p.getAvailableLocales()) + { + if (loc.equals(locale)) + { + DateFormatSymbols syms = p.getInstance(locale); + if (syms != null) + return syms; + break; + } + } + } + return getInstance(LocaleHelper.getFallbackLocale(locale)); + } + +} diff --git a/libjava/classpath/java/text/DecimalFormat.java b/libjava/classpath/java/text/DecimalFormat.java new file mode 100644 index 000000000..9f02bb8d4 --- /dev/null +++ b/libjava/classpath/java/text/DecimalFormat.java @@ -0,0 +1,2278 @@ +/* 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. */ + +/* + * This class contains few bits from ICU4J (http://icu.sourceforge.net/), + * Copyright by IBM and others and distributed under the + * distributed under MIT/X. + */ + +package java.text; + +import gnu.java.lang.CPStringBuilder; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.Currency; +import java.util.Locale; + +/* + * This note is here for historical reasons and because I had not the courage + * to remove it :) + * + * @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. + */ + +/** + * This class is a concrete implementation of NumberFormat used to format + * decimal numbers. The class can format numbers given a specific locale. + * Generally, to get an instance of DecimalFormat you should call the factory + * methods in the <code>NumberFormat</code> base class. + * + * @author Mario Torre (neugens@limasoftware.net) + * @author Tom Tromey (tromey@cygnus.com) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + */ +public class DecimalFormat extends NumberFormat +{ + /** serialVersionUID for serializartion. */ + private static final long serialVersionUID = 864413376551465018L; + + /** Defines the default number of digits allowed while formatting integers. */ + private static final int DEFAULT_INTEGER_DIGITS = 309; + + /** + * Defines the default number of digits allowed while formatting + * fractions. + */ + private static final int DEFAULT_FRACTION_DIGITS = 340; + + /** + * Locale-independent pattern symbols. + */ + // Happen to be the same as the US symbols. + private static final DecimalFormatSymbols nonLocalizedSymbols + = new DecimalFormatSymbols (Locale.US); + + /** + * Defines if parse should return a BigDecimal or not. + */ + private boolean parseBigDecimal; + + /** + * Defines if we have to use the monetary decimal separator or + * the decimal separator while formatting numbers. + */ + private boolean useCurrencySeparator; + + /** Defines if the decimal separator is always shown or not. */ + private boolean decimalSeparatorAlwaysShown; + + /** + * Defines if the decimal separator has to be shown. + * + * This is different then <code>decimalSeparatorAlwaysShown</code>, + * as it defines if the format string contains a decimal separator or no. + */ + private boolean showDecimalSeparator; + + /** + * This field is used to determine if the grouping + * separator is included in the format string or not. + * This is only needed to match the behaviour of the RI. + */ + private boolean groupingSeparatorInPattern; + + /** Defines the size of grouping groups when grouping is used. */ + private byte groupingSize; + + /** + * This is an internal parameter used to keep track of the number + * of digits the form the exponent, when exponential notation is used. + * It is used with <code>exponentRound</code> + */ + private byte minExponentDigits; + + /** This field is used to set the exponent in the engineering notation. */ + private int exponentRound; + + /** Multiplier used in percent style formats. */ + private int multiplier; + + /** Multiplier used in percent style formats. */ + private int negativePatternMultiplier; + + /** The negative prefix. */ + private String negativePrefix; + + /** The negative suffix. */ + private String negativeSuffix; + + /** The positive prefix. */ + private String positivePrefix; + + /** The positive suffix. */ + private String positiveSuffix; + + /** Decimal Format Symbols for the given locale. */ + private DecimalFormatSymbols symbols; + + /** Determine if we have to use exponential notation or not. */ + private boolean useExponentialNotation; + + /** + * Defines the maximum number of integer digits to show when we use + * the exponential notation. + */ + private int maxIntegerDigitsExponent; + + /** Defines if the format string has a negative prefix or not. */ + private boolean hasNegativePrefix; + + /** Defines if the format string has a fractional pattern or not. */ + private boolean hasFractionalPattern; + + /** Stores a list of attributes for use by formatToCharacterIterator. */ + private ArrayList attributes = new ArrayList(); + + /** + * Constructs a <code>DecimalFormat</code> which uses the default + * pattern and symbols. + */ + public DecimalFormat() + { + this ("#,##0.###"); + } + + /** + * Constructs a <code>DecimalFormat</code> 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 <code>DecimalFormat</code> 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(); + applyPatternWithSymbols(pattern, nonLocalizedSymbols); + } + + /** + * Apply the given localized patern to the current DecimalFormat object. + * + * @param pattern The localized pattern to apply. + * @throws IllegalArgumentException if the given pattern is invalid. + * @throws NullPointerException if the input pattern is null. + */ + public void applyLocalizedPattern (String pattern) + { + applyPatternWithSymbols(pattern, this.symbols); + } + + /** + * Apply the given localized pattern to the current DecimalFormat object. + * + * @param pattern The localized pattern to apply. + * @throws IllegalArgumentException if the given pattern is invalid. + * @throws NullPointerException if the input pattern is null. + */ + public void applyPattern(String pattern) + { + applyPatternWithSymbols(pattern, nonLocalizedSymbols); + } + + public Object clone() + { + DecimalFormat c = (DecimalFormat) super.clone(); + c.symbols = (DecimalFormatSymbols) symbols.clone(); + return c; + } + + /** + * Tests this instance for equality with an arbitrary object. This method + * returns <code>true</code> if: + * <ul> + * <li><code>obj</code> is not <code>null</code>;</li> + * <li><code>obj</code> is an instance of <code>DecimalFormat</code>;</li> + * <li>this instance and <code>obj</code> have the same attributes;</li> + * </ul> + * + * @param obj the object (<code>null</code> 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 + && groupingSeparatorInPattern == dup.groupingSeparatorInPattern + && groupingSize == dup.groupingSize + && multiplier == dup.multiplier + && useExponentialNotation == dup.useExponentialNotation + && minExponentDigits == dup.minExponentDigits + && minimumIntegerDigits == dup.minimumIntegerDigits + && maximumIntegerDigits == dup.maximumIntegerDigits + && minimumFractionDigits == dup.minimumFractionDigits + && maximumFractionDigits == dup.maximumFractionDigits + && parseBigDecimal == dup.parseBigDecimal + && useCurrencySeparator == dup.useCurrencySeparator + && showDecimalSeparator == dup.showDecimalSeparator + && exponentRound == dup.exponentRound + && negativePatternMultiplier == dup.negativePatternMultiplier + && maxIntegerDigitsExponent == dup.maxIntegerDigitsExponent + // XXX: causes equivalent patterns to fail + // && hasNegativePrefix == dup.hasNegativePrefix + && equals(negativePrefix, dup.negativePrefix) + && equals(negativeSuffix, dup.negativeSuffix) + && equals(positivePrefix, dup.positivePrefix) + && equals(positiveSuffix, dup.positiveSuffix) + && symbols.equals(dup.symbols)); + } + + /** + * Returns a hash code for this object. + * + * @return A hash code. + */ + public int hashCode() + { + return toPattern().hashCode(); + } + + /** + * Produce a formatted {@link String} representation of this object. + * The passed object must be of type number. + * + * @param obj The {@link Number} to format. + * @param sbuf The destination String; text will be appended to this String. + * @param pos If used on input can be used to define an alignment + * field. If used on output defines the offsets of the alignment field. + * @return The String representation of this long. + */ + public final StringBuffer format(Object obj, StringBuffer sbuf, FieldPosition pos) + { + if (obj instanceof BigInteger) + { + BigDecimal decimal = new BigDecimal((BigInteger) obj); + formatInternal(decimal, true, sbuf, pos); + return sbuf; + } + else if (obj instanceof BigDecimal) + { + formatInternal((BigDecimal) obj, true, sbuf, pos); + return sbuf; + } + + return super.format(obj, sbuf, pos); + } + + /** + * Produce a formatted {@link String} representation of this double. + * + * @param number The double to format. + * @param dest The destination String; text will be appended to this String. + * @param fieldPos If used on input can be used to define an alignment + * field. If used on output defines the offsets of the alignment field. + * @return The String representation of this long. + * @throws NullPointerException if <code>dest</code> or fieldPos are null + */ + public StringBuffer format(double number, StringBuffer dest, + FieldPosition fieldPos) + { + // special cases for double: NaN and negative or positive infinity + if (Double.isNaN(number)) + { + // 1. NaN + String nan = symbols.getNaN(); + dest.append(nan); + + // update field position if required + if ((fieldPos.getField() == INTEGER_FIELD || + fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER)) + { + int index = dest.length(); + fieldPos.setBeginIndex(index - nan.length()); + fieldPos.setEndIndex(index); + } + } + else if (Double.isInfinite(number)) + { + // 2. Infinity + if (number < 0) + dest.append(this.negativePrefix); + else + dest.append(this.positivePrefix); + + dest.append(symbols.getInfinity()); + + if (number < 0) + dest.append(this.negativeSuffix); + else + dest.append(this.positiveSuffix); + + if ((fieldPos.getField() == INTEGER_FIELD || + fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER)) + { + fieldPos.setBeginIndex(dest.length()); + fieldPos.setEndIndex(0); + } + } + else + { + // get the number as a BigDecimal + BigDecimal bigDecimal = new BigDecimal(String.valueOf(number)); + formatInternal(bigDecimal, false, dest, fieldPos); + } + + return dest; + } + + /** + * Produce a formatted {@link String} representation of this long. + * + * @param number The long to format. + * @param dest The destination String; text will be appended to this String. + * @param fieldPos If used on input can be used to define an alignment + * field. If used on output defines the offsets of the alignment field. + * @return The String representation of this long. + */ + public StringBuffer format(long number, StringBuffer dest, + FieldPosition fieldPos) + { + BigDecimal bigDecimal = new BigDecimal(String.valueOf(number)); + formatInternal(bigDecimal, true, dest, fieldPos); + return dest; + } + + /** + * Return an <code>AttributedCharacterIterator</code> as a result of + * the formatting of the passed {@link Object}. + * + * @return An {@link AttributedCharacterIterator}. + * @throws NullPointerException if value is <code>null</code>. + * @throws IllegalArgumentException if value is not an instance of + * {@link Number}. + */ + public AttributedCharacterIterator formatToCharacterIterator(Object value) + { + /* + * This method implementation derives directly from the + * ICU4J (http://icu.sourceforge.net/) library, distributed under MIT/X. + */ + + if (value == null) + throw new NullPointerException("Passed Object is null"); + + if (!(value instanceof Number)) throw new + IllegalArgumentException("Cannot format given Object as a Number"); + + StringBuffer text = new StringBuffer(); + attributes.clear(); + super.format(value, text, new FieldPosition(0)); + + AttributedString as = new AttributedString(text.toString()); + + // add NumberFormat field attributes to the AttributedString + for (int i = 0; i < attributes.size(); i++) + { + FieldPosition pos = (FieldPosition) attributes.get(i); + Format.Field attribute = pos.getFieldAttribute(); + + as.addAttribute(attribute, attribute, pos.getBeginIndex(), + pos.getEndIndex()); + } + + // return the CharacterIterator from AttributedString + return as.getIterator(); + } + + /** + * Returns the currency corresponding to the currency symbol stored + * in the instance of <code>DecimalFormatSymbols</code> used by this + * <code>DecimalFormat</code>. + * + * @return A new instance of <code>Currency</code> 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(); + } + + /** + * Gets the interval used between a grouping separator and the next. + * For example, a grouping size of 3 means that the number 1234 is + * formatted as 1,234. + * + * The actual character used as grouping separator depends on the + * locale and is defined by {@link DecimalFormatSymbols#getDecimalSeparator()} + * + * @return The interval used between a grouping separator and the next. + */ + public int getGroupingSize() + { + return groupingSize; + } + + /** + * Gets the multiplier used in percent and similar formats. + * + * @return The multiplier used in percent and similar formats. + */ + public int getMultiplier() + { + return multiplier; + } + + /** + * Gets the negative prefix. + * + * @return The negative prefix. + */ + public String getNegativePrefix() + { + return negativePrefix; + } + + /** + * Gets the negative suffix. + * + * @return The negative suffix. + */ + public String getNegativeSuffix() + { + return negativeSuffix; + } + + /** + * Gets the positive prefix. + * + * @return The positive prefix. + */ + public String getPositivePrefix() + { + return positivePrefix; + } + + /** + * Gets the positive suffix. + * + * @return The positive suffix. + */ + public String getPositiveSuffix() + { + return positiveSuffix; + } + + public boolean isDecimalSeparatorAlwaysShown() + { + return decimalSeparatorAlwaysShown; + } + + /** + * Define if <code>parse(java.lang.String, java.text.ParsePosition)</code> + * should return a {@link BigDecimal} or not. + * + * @param newValue + */ + public void setParseBigDecimal(boolean newValue) + { + this.parseBigDecimal = newValue; + } + + /** + * Returns <code>true</code> if + * <code>parse(java.lang.String, java.text.ParsePosition)</code> returns + * a <code>BigDecimal</code>, <code>false</code> otherwise. + * The default return value for this method is <code>false</code>. + * + * @return <code>true</code> if the parse method returns a {@link BigDecimal}, + * <code>false</code> otherwise. + * @since 1.5 + * @see #setParseBigDecimal(boolean) + */ + public boolean isParseBigDecimal() + { + return this.parseBigDecimal; + } + + /** + * This method parses the specified string into a <code>Number</code>. + * + * The parsing starts at <code>pos</code>, which is updated as the parser + * consume characters in the passed string. + * On error, the <code>Position</code> object index is not updated, while + * error position is set appropriately, an <code>null</code> is returned. + * + * @param str The string to parse. + * @param pos The desired <code>ParsePosition</code>. + * + * @return The parsed <code>Number</code> + */ + public Number parse(String str, ParsePosition pos) + { + // a special values before anything else + // NaN + if (str.contains(this.symbols.getNaN())) + return Double.valueOf(Double.NaN); + + // this will be our final number + CPStringBuilder number = new CPStringBuilder(); + + // special character + char minus = symbols.getMinusSign(); + + // starting parsing position + int start = pos.getIndex(); + + // validate the string, it have to be in the + // same form as the format string or parsing will fail + String _negativePrefix = (this.negativePrefix.compareTo("") == 0 + ? minus + positivePrefix + : this.negativePrefix); + + // we check both prefixes, because one might be empty. + // We want to pick the longest prefix that matches. + int positiveLen = positivePrefix.length(); + int negativeLen = _negativePrefix.length(); + + boolean isNegative = str.startsWith(_negativePrefix); + boolean isPositive = str.startsWith(positivePrefix); + + if (isPositive && isNegative) + { + // By checking this way, we preserve ambiguity in the case + // where the negative format differs only in suffix. + if (negativeLen > positiveLen) + { + start += _negativePrefix.length(); + isNegative = true; + } + else + { + start += positivePrefix.length(); + isPositive = true; + if (negativeLen < positiveLen) + isNegative = false; + } + } + else if (isNegative) + { + start += _negativePrefix.length(); + isPositive = false; + } + else if (isPositive) + { + start += positivePrefix.length(); + isNegative = false; + } + else + { + pos.setErrorIndex(start); + return null; + } + + // other special characters used by the parser + char decimalSeparator = symbols.getDecimalSeparator(); + char zero = symbols.getZeroDigit(); + char exponent = symbols.getExponential(); + + // stop parsing position in the string + int stop = start + this.maximumIntegerDigits + maximumFractionDigits + 2; + + if (useExponentialNotation) + stop += minExponentDigits + 1; + + boolean inExponent = false; + + // correct the size of the end parsing flag + int len = str.length(); + if (len < stop) stop = len; + char groupingSeparator = symbols.getGroupingSeparator(); + + int i = start; + while (i < stop) + { + char ch = str.charAt(i); + i++; + + if (ch >= zero && ch <= (zero + 9)) + { + number.append(ch); + } + else if (this.parseIntegerOnly) + { + i--; + break; + } + else if (ch == decimalSeparator) + { + number.append('.'); + } + else if (ch == exponent) + { + number.append(ch); + inExponent = !inExponent; + } + else if ((ch == '+' || ch == '-' || ch == minus)) + { + if (inExponent) + number.append(ch); + else + { + i--; + break; + } + } + else + { + if (!groupingUsed || ch != groupingSeparator) + { + i--; + break; + } + } + } + + // 2nd special case: infinity + // XXX: need to be tested + if (str.contains(symbols.getInfinity())) + { + int inf = str.indexOf(symbols.getInfinity()); + pos.setIndex(inf); + + // FIXME: ouch, this is really ugly and lazy code... + if (this.parseBigDecimal) + { + if (isNegative) + return BigDecimal.valueOf(Double.NEGATIVE_INFINITY); + + return BigDecimal.valueOf(Double.POSITIVE_INFINITY); + } + + if (isNegative) + return Double.valueOf(Double.NEGATIVE_INFINITY); + + return Double.valueOf(Double.POSITIVE_INFINITY); + } + + // no number... + if (i == start || number.length() == 0) + { + pos.setErrorIndex(i); + return null; + } + + // now we have to check the suffix, done here after number parsing + // or the index will not be updated correctly... + boolean hasNegativeSuffix = str.endsWith(this.negativeSuffix); + boolean hasPositiveSuffix = str.endsWith(this.positiveSuffix); + boolean positiveEqualsNegative = negativeSuffix.equals(positiveSuffix); + + positiveLen = positiveSuffix.length(); + negativeLen = negativeSuffix.length(); + + if (isNegative && !hasNegativeSuffix) + { + pos.setErrorIndex(i); + return null; + } + else if (hasNegativeSuffix && + !positiveEqualsNegative && + (negativeLen > positiveLen)) + { + isNegative = true; + } + else if (!hasPositiveSuffix) + { + pos.setErrorIndex(i); + return null; + } + + if (isNegative) number.insert(0, '-'); + + pos.setIndex(i); + + // now we handle the return type + BigDecimal bigDecimal = new BigDecimal(number.toString()); + if (this.parseBigDecimal) + return bigDecimal; + + // want integer? + if (this.parseIntegerOnly) + return Long.valueOf(bigDecimal.longValue()); + + // 3th special case -0.0 + if (isNegative && (bigDecimal.compareTo(BigDecimal.ZERO) == 0)) + return Double.valueOf(-0.0); + + try + { + BigDecimal integer + = bigDecimal.setScale(0, BigDecimal.ROUND_UNNECESSARY); + return Long.valueOf(integer.longValue()); + } + catch (ArithmeticException e) + { + return Double.valueOf(bigDecimal.doubleValue()); + } + } + + /** + * Sets the <code>Currency</code> on the + * <code>DecimalFormatSymbols</code> used, which also sets the + * currency symbols on those symbols. + * + * @param currency The new <code>Currency</code> on the + * <code>DecimalFormatSymbols</code>. + */ + public void setCurrency(Currency currency) + { + Currency current = symbols.getCurrency(); + if (current != currency) + { + String oldSymbol = symbols.getCurrencySymbol(); + int len = oldSymbol.length(); + symbols.setCurrency(currency); + String newSymbol = symbols.getCurrencySymbol(); + int posPre = positivePrefix.indexOf(oldSymbol); + if (posPre != -1) + positivePrefix = positivePrefix.substring(0, posPre) + + newSymbol + positivePrefix.substring(posPre+len); + int negPre = negativePrefix.indexOf(oldSymbol); + if (negPre != -1) + negativePrefix = negativePrefix.substring(0, negPre) + + newSymbol + negativePrefix.substring(negPre+len); + int posSuf = positiveSuffix.indexOf(oldSymbol); + if (posSuf != -1) + positiveSuffix = positiveSuffix.substring(0, posSuf) + + newSymbol + positiveSuffix.substring(posSuf+len); + int negSuf = negativeSuffix.indexOf(oldSymbol); + if (negSuf != -1) + negativeSuffix = negativeSuffix.substring(0, negSuf) + + newSymbol + negativeSuffix.substring(negSuf+len); + } + } + + /** + * Sets the symbols used by this instance. This method makes a copy of + * the supplied symbols. + * + * @param newSymbols the symbols (<code>null</code> not permitted). + */ + public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) + { + symbols = (DecimalFormatSymbols) newSymbols.clone(); + } + + /** + * Define if the decimal separator should be always visible or only + * visible when needed. This method as effect only on integer values. + * Pass <code>true</code> if you want the decimal separator to be + * always shown, <code>false</code> otherwise. + * + * @param newValue true</code> if you want the decimal separator to be + * always shown, <code>false</code> otherwise. + */ + public void setDecimalSeparatorAlwaysShown(boolean newValue) + { + decimalSeparatorAlwaysShown = newValue; + } + + /** + * Sets the number of digits used to group portions of the integer part of + * the number. For example, the number <code>123456</code>, with a grouping + * size of 3, is rendered <code>123,456</code>. + * + * @param groupSize The number of digits used while grouping portions + * of the integer part of a number. + */ + public void setGroupingSize(int groupSize) + { + groupingSize = (byte) groupSize; + } + + /** + * Sets the maximum number of digits allowed in the integer + * portion of a number to the specified value. + * The new value will be the choosen as the minimum between + * <code>newvalue</code> and 309. Any value below zero will be + * replaced by zero. + * + * @param newValue The new maximum integer digits value. + */ + public void setMaximumIntegerDigits(int newValue) + { + newValue = (newValue > 0) ? newValue : 0; + super.setMaximumIntegerDigits(Math.min(newValue, DEFAULT_INTEGER_DIGITS)); + } + + /** + * Sets the minimum number of digits allowed in the integer + * portion of a number to the specified value. + * The new value will be the choosen as the minimum between + * <code>newvalue</code> and 309. Any value below zero will be + * replaced by zero. + * + * @param newValue The new minimum integer digits value. + */ + public void setMinimumIntegerDigits(int newValue) + { + newValue = (newValue > 0) ? newValue : 0; + super.setMinimumIntegerDigits(Math.min(newValue, DEFAULT_INTEGER_DIGITS)); + } + + /** + * Sets the maximum number of digits allowed in the fraction + * portion of a number to the specified value. + * The new value will be the choosen as the minimum between + * <code>newvalue</code> and 309. Any value below zero will be + * replaced by zero. + * + * @param newValue The new maximum fraction digits value. + */ + public void setMaximumFractionDigits(int newValue) + { + newValue = (newValue > 0) ? newValue : 0; + super.setMaximumFractionDigits(Math.min(newValue, DEFAULT_FRACTION_DIGITS)); + } + + /** + * Sets the minimum number of digits allowed in the fraction + * portion of a number to the specified value. + * The new value will be the choosen as the minimum between + * <code>newvalue</code> and 309. Any value below zero will be + * replaced by zero. + * + * @param newValue The new minimum fraction digits value. + */ + public void setMinimumFractionDigits(int newValue) + { + newValue = (newValue > 0) ? newValue : 0; + super.setMinimumFractionDigits(Math.min(newValue, DEFAULT_FRACTION_DIGITS)); + } + + /** + * Sets the multiplier for use in percent and similar formats. + * For example, for percent set the multiplier to 100, for permille, set the + * miltiplier to 1000. + * + * @param newValue the new value for multiplier. + */ + public void setMultiplier(int newValue) + { + multiplier = newValue; + } + + /** + * Sets the negative prefix. + * + * @param newValue The new negative prefix. + */ + public void setNegativePrefix(String newValue) + { + negativePrefix = newValue; + } + + /** + * Sets the negative suffix. + * + * @param newValue The new negative suffix. + */ + public void setNegativeSuffix(String newValue) + { + negativeSuffix = newValue; + } + + /** + * Sets the positive prefix. + * + * @param newValue The new positive prefix. + */ + public void setPositivePrefix(String newValue) + { + positivePrefix = newValue; + } + + /** + * Sets the new positive suffix. + * + * @param newValue The new positive suffix. + */ + public void setPositiveSuffix(String newValue) + { + positiveSuffix = newValue; + } + + /** + * This method returns a string with the formatting pattern being used + * by this object. The string is localized. + * + * @return A localized <code>String</code> with the formatting pattern. + * @see #toPattern() + */ + public String toLocalizedPattern() + { + return computePattern(this.symbols); + } + + /** + * This method returns a string with the formatting pattern being used + * by this object. The string is not localized. + * + * @return A <code>String</code> with the formatting pattern. + * @see #toLocalizedPattern() + */ + public String toPattern() + { + return computePattern(nonLocalizedSymbols); + } + + /* ***** private methods ***** */ + + /** + * This is an shortcut helper method used to test if two given strings are + * equals. + * + * @param s1 The first string to test for equality. + * @param s2 The second string to test for equality. + * @return <code>true</code> if the strings are both <code>null</code> or + * equals. + */ + private boolean equals(String s1, String s2) + { + if (s1 == null || s2 == null) + return s1 == s2; + return s1.equals(s2); + } + + + /* ****** PATTERN ****** */ + + /** + * 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) + { + CPStringBuilder buf = new CPStringBuilder (); + + buf.append(syms.getDecimalSeparator()); + buf.append(syms.getDigit()); + buf.append(syms.getExponential()); + buf.append(syms.getGroupingSeparator()); + buf.append(syms.getMinusSign()); + buf.append(syms.getPatternSeparator()); + buf.append(syms.getPercent()); + buf.append(syms.getPerMill()); + buf.append(syms.getZeroDigit()); + buf.append('\''); + buf.append('\u00a4'); + + return buf.toString(); + } + + /** + * Quote special characters as defined by <code>patChars</code> in the + * input string. + * + * @param text + * @param patChars + * @return A StringBuffer with special characters quoted. + */ + private CPStringBuilder quoteFix(String text, String patChars) + { + CPStringBuilder buf = new CPStringBuilder(); + + int len = text.length(); + char ch; + for (int index = 0; index < len; ++index) + { + ch = text.charAt(index); + if (patChars.indexOf(ch) != -1) + { + buf.append('\''); + buf.append(ch); + if (ch != '\'') buf.append('\''); + } + else + { + buf.append(ch); + } + } + + return buf; + } + + /** + * Returns the format pattern, localized to follow the given + * symbols. + */ + private String computePattern(DecimalFormatSymbols symbols) + { + StringBuilder mainPattern = new StringBuilder(); + + // 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 _groupingSize = groupingUsed ? groupingSize + 1: groupingSize; + int totalDigits = Math.max(minimumIntegerDigits, _groupingSize); + + // if it is not in exponential notiation, + // we always have a # prebended + if (!useExponentialNotation) mainPattern.append(symbols.getDigit()); + + for (int i = 1; i < totalDigits - minimumIntegerDigits; i++) + mainPattern.append(symbols.getDigit()); + + for (int i = totalDigits - minimumIntegerDigits; i < totalDigits; i++) + mainPattern.append(symbols.getZeroDigit()); + + if (groupingUsed) + { + mainPattern.insert(mainPattern.length() - groupingSize, + symbols.getGroupingSeparator()); + } + + // See if we need decimal info. + if (minimumFractionDigits > 0 || maximumFractionDigits > 0 || + decimalSeparatorAlwaysShown) + { + mainPattern.append(symbols.getDecimalSeparator()); + } + + for (int i = 0; i < minimumFractionDigits; ++i) + mainPattern.append(symbols.getZeroDigit()); + + for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i) + mainPattern.append(symbols.getDigit()); + + if (useExponentialNotation) + { + mainPattern.append(symbols.getExponential()); + + for (int i = 0; i < minExponentDigits; ++i) + mainPattern.append(symbols.getZeroDigit()); + + if (minExponentDigits == 0) + mainPattern.append(symbols.getDigit()); + } + + // save the pattern + String pattern = mainPattern.toString(); + + // so far we have the pattern itself, now we need to add + // the positive and the optional negative prefixes and suffixes + String patternChars = patternChars(symbols); + mainPattern.insert(0, quoteFix(positivePrefix, patternChars)); + mainPattern.append(quoteFix(positiveSuffix, patternChars)); + + if (hasNegativePrefix) + { + mainPattern.append(symbols.getPatternSeparator()); + mainPattern.append(quoteFix(negativePrefix, patternChars)); + mainPattern.append(pattern); + mainPattern.append(quoteFix(negativeSuffix, patternChars)); + } + + // finally, return the pattern string + return mainPattern.toString(); + } + + /* ****** FORMAT PARSING ****** */ + + /** + * Scan the input string and define a pattern suitable for use + * with this decimal format. + * + * @param pattern + * @param symbols + */ + private void applyPatternWithSymbols(String pattern, + DecimalFormatSymbols symbols) + { + // The pattern string is described by a BNF diagram. + // we could use a recursive parser to read and prepare + // the string, but this would be too slow and resource + // intensive, while this code is quite critical as it is + // called always when the class is instantiated and every + // time a new pattern is given. + // Our strategy is to divide the string into section as given by + // the BNF diagram, iterating through the string and setting up + // the parameters we need for formatting (which is basicly what + // a descendent recursive parser would do - but without recursion). + // I'm sure that there are smarter methods to do this. + + // Restore default values. Most of these will be overwritten + // but we want to be sure that nothing is left out. + setDefaultValues(); + + int len = pattern.length(); + if (len == 0) + { + // this is another special case... + this.minimumIntegerDigits = 1; + this.maximumIntegerDigits = DEFAULT_INTEGER_DIGITS; + this.minimumFractionDigits = 0; + this.maximumFractionDigits = DEFAULT_FRACTION_DIGITS; + + // FIXME: ...and these values may not be valid in all locales + this.minExponentDigits = 0; + this.showDecimalSeparator = true; + this.groupingUsed = true; + this.groupingSize = 3; + + return; + } + + int start = scanFix(pattern, symbols, 0, true); + if (start < len) start = scanNumberInteger(pattern, symbols, start); + if (start < len) + { + start = scanFractionalPortion(pattern, symbols, start); + } + else + { + // special case, pattern that ends here does not have a fractional + // portion + this.minimumFractionDigits = 0; + this.maximumFractionDigits = 0; + //this.decimalSeparatorAlwaysShown = false; + //this.showDecimalSeparator = false; + } + + // XXX: this fixes a compatibility test with the RI. + // If new uses cases fail, try removing this line first. + //if (!this.hasIntegerPattern && !this.hasFractionalPattern) + // throw new IllegalArgumentException("No valid pattern found!"); + + if (start < len) start = scanExponent(pattern, symbols, start); + if (start < len) start = scanFix(pattern, symbols, start, false); + if (start < len) scanNegativePattern(pattern, symbols, start); + + if (useExponentialNotation && + (maxIntegerDigitsExponent > minimumIntegerDigits) && + (maxIntegerDigitsExponent > 1)) + { + minimumIntegerDigits = 1; + exponentRound = maxIntegerDigitsExponent; + } + + if (useExponentialNotation) + maximumIntegerDigits = maxIntegerDigitsExponent; + + if (!this.hasFractionalPattern && this.showDecimalSeparator == true) + { + this.decimalSeparatorAlwaysShown = true; + } + } + + /** + * Scans for the prefix or suffix portion of the pattern string. + * This method handles the positive subpattern of the pattern string. + * + * @param pattern The pattern string to parse. + * @return The position in the pattern string where parsing ended. + */ + private int scanFix(String pattern, DecimalFormatSymbols sourceSymbols, + int start, boolean prefix) + { + CPStringBuilder buffer = new CPStringBuilder(); + + // the number portion is always delimited by one of those + // characters + char decimalSeparator = sourceSymbols.getDecimalSeparator(); + char patternSeparator = sourceSymbols.getPatternSeparator(); + char groupingSeparator = sourceSymbols.getGroupingSeparator(); + char digit = sourceSymbols.getDigit(); + char zero = sourceSymbols.getZeroDigit(); + char minus = sourceSymbols.getMinusSign(); + + // other special characters, cached here to avoid method calls later + char percent = sourceSymbols.getPercent(); + char permille = sourceSymbols.getPerMill(); + + String currencySymbol = this.symbols.getCurrencySymbol(); + + boolean quote = false; + + char ch = pattern.charAt(start); + if (ch == patternSeparator) + { + // negative subpattern + this.hasNegativePrefix = true; + ++start; + return start; + } + + int len = pattern.length(); + int i; + for (i = start; i < len; i++) + { + ch = pattern.charAt(i); + + // we are entering into the negative subpattern + if (!quote && ch == patternSeparator) + { + if (this.hasNegativePrefix) + { + throw new IllegalArgumentException("Invalid pattern found: " + + start); + } + + this.hasNegativePrefix = true; + ++i; + break; + } + + // this means we are inside the number portion + if (!quote && + (ch == minus || ch == digit || ch == zero || + ch == groupingSeparator)) + break; + + if (!quote && ch == decimalSeparator) + { + this.showDecimalSeparator = true; + break; + } + else if (quote && ch != '\'') + { + buffer.append(ch); + continue; + } + + if (ch == '\u00A4') + { + // CURRENCY + currencySymbol = this.symbols.getCurrencySymbol(); + + // if \u00A4 is doubled, we use the international currency symbol + if ((i + 1) < len && pattern.charAt(i + 1) == '\u00A4') + { + currencySymbol = this.symbols.getInternationalCurrencySymbol(); + i++; + } + + this.useCurrencySeparator = true; + buffer.append(currencySymbol); + } + else if (ch == percent) + { + // PERCENT + this.multiplier = 100; + buffer.append(this.symbols.getPercent()); + } + else if (ch == permille) + { + // PERMILLE + this.multiplier = 1000; + buffer.append(this.symbols.getPerMill()); + } + else if (ch == '\'') + { + // QUOTE + if ((i + 1) < len && pattern.charAt(i + 1) == '\'') + { + // we need to add ' to the buffer + buffer.append(ch); + i++; + } + else + { + quote = !quote; + continue; + } + } + else + { + buffer.append(ch); + } + } + + if (prefix) + { + this.positivePrefix = buffer.toString(); + this.negativePrefix = minus + "" + positivePrefix; + } + else + { + this.positiveSuffix = buffer.toString(); + } + + return i; + } + + /** + * Scan the given string for number patterns, starting + * from <code>start</code>. + * This method searches the integer part of the pattern only. + * + * @param pattern The pattern string to parse. + * @param start The starting parse position in the string. + * @return The position in the pattern string where parsing ended, + * counted from the beginning of the string (that is, 0). + */ + private int scanNumberInteger(String pattern, DecimalFormatSymbols symbols, + int start) + { + char digit = symbols.getDigit(); + char zero = symbols.getZeroDigit(); + char groupingSeparator = symbols.getGroupingSeparator(); + char decimalSeparator = symbols.getDecimalSeparator(); + char exponent = symbols.getExponential(); + char patternSeparator = symbols.getPatternSeparator(); + + // count the number of zeroes in the pattern + // this number defines the minum digits in the integer portion + int zeros = 0; + + // count the number of digits used in grouping + int _groupingSize = 0; + + this.maxIntegerDigitsExponent = 0; + + boolean intPartTouched = false; + + char ch; + int len = pattern.length(); + int i; + for (i = start; i < len; i++) + { + ch = pattern.charAt(i); + + // break on decimal separator or exponent or pattern separator + if (ch == decimalSeparator || ch == exponent) + break; + + if (this.hasNegativePrefix && ch == patternSeparator) + throw new IllegalArgumentException("Invalid pattern found: " + + start); + + if (ch == digit) + { + // in our implementation we could relax this strict + // requirement, but this is used to keep compatibility with + // the RI + if (zeros > 0) throw new + IllegalArgumentException("digit mark following zero in " + + "positive subpattern, not allowed. Position: " + i); + + _groupingSize++; + intPartTouched = true; + this.maxIntegerDigitsExponent++; + } + else if (ch == zero) + { + zeros++; + _groupingSize++; + this.maxIntegerDigitsExponent++; + } + else if (ch == groupingSeparator) + { + this.groupingSeparatorInPattern = true; + this.groupingUsed = true; + _groupingSize = 0; + } + else + { + // any other character not listed above + // means we are in the suffix portion + break; + } + } + + if (groupingSeparatorInPattern) this.groupingSize = (byte) _groupingSize; + this.minimumIntegerDigits = zeros; + + // XXX: compatibility code with the RI: the number of minimum integer + // digits is at least one when maximumIntegerDigits is more than zero + if (intPartTouched && this.maximumIntegerDigits > 0 && + this.minimumIntegerDigits == 0) + this.minimumIntegerDigits = 1; + + return i; + } + + /** + * Scan the given string for number patterns, starting + * from <code>start</code>. + * This method searches the fractional part of the pattern only. + * + * @param pattern The pattern string to parse. + * @param start The starting parse position in the string. + * @return The position in the pattern string where parsing ended, + * counted from the beginning of the string (that is, 0). + */ + private int scanFractionalPortion(String pattern, + DecimalFormatSymbols symbols, + int start) + { + char digit = symbols.getDigit(); + char zero = symbols.getZeroDigit(); + char groupingSeparator = symbols.getGroupingSeparator(); + char decimalSeparator = symbols.getDecimalSeparator(); + char exponent = symbols.getExponential(); + char patternSeparator = symbols.getPatternSeparator(); + + // first character needs to be '.' otherwise we are not parsing the + // fractional portion + char ch = pattern.charAt(start); + if (ch != decimalSeparator) + { + this.minimumFractionDigits = 0; + this.maximumFractionDigits = 0; + return start; + } + + ++start; + + this.hasFractionalPattern = true; + + this.minimumFractionDigits = 0; + int digits = 0; + + int len = pattern.length(); + int i; + for (i = start; i < len; i++) + { + ch = pattern.charAt(i); + + // we hit the exponential or negative subpattern + if (ch == exponent || ch == patternSeparator) + break; + + // pattern error + if (ch == groupingSeparator || ch == decimalSeparator) throw new + IllegalArgumentException("unexpected character '" + ch + "' " + + "in fractional subpattern. Position: " + i); + + if (ch == digit) + { + digits++; + } + else if (ch == zero) + { + if (digits > 0) throw new + IllegalArgumentException("digit mark following zero in " + + "positive subpattern, not allowed. Position: " + i); + + this.minimumFractionDigits++; + } + else + { + // we are in the suffix section of pattern + break; + } + } + + if (i == start) this.hasFractionalPattern = false; + + this.maximumFractionDigits = this.minimumFractionDigits + digits; + this.showDecimalSeparator = true; + + return i; + } + + /** + * Scan the given string for number patterns, starting + * from <code>start</code>. + * This method searches the expoential part of the pattern only. + * + * @param pattern The pattern string to parse. + * @param start The starting parse position in the string. + * @return The position in the pattern string where parsing ended, + * counted from the beginning of the string (that is, 0). + */ + private int scanExponent(String pattern, DecimalFormatSymbols symbols, + int start) + { + char digit = symbols.getDigit(); + char zero = symbols.getZeroDigit(); + char groupingSeparator = symbols.getGroupingSeparator(); + char decimalSeparator = symbols.getDecimalSeparator(); + char exponent = symbols.getExponential(); + + char ch = pattern.charAt(start); + + if (ch == decimalSeparator) + { + // ignore dots + ++start; + } + + if (ch != exponent) + { + this.useExponentialNotation = false; + return start; + } + + ++start; + + this.minExponentDigits = 0; + + int len = pattern.length(); + int i; + for (i = start; i < len; i++) + { + ch = pattern.charAt(i); + + if (ch == groupingSeparator || ch == decimalSeparator || + ch == digit || ch == exponent) throw new + IllegalArgumentException("unexpected character '" + ch + "' " + + "in exponential subpattern. Position: " + i); + + if (ch == zero) + { + this.minExponentDigits++; + } + else + { + // any character other than zero is an exit point + break; + } + } + + this.useExponentialNotation = true; + + return i; + } + + /** + * Scan the given string for number patterns, starting + * from <code>start</code>. + * This method searches the negative part of the pattern only and scan + * throught the end of the string. + * + * @param pattern The pattern string to parse. + * @param start The starting parse position in the string. + */ + private void scanNegativePattern(String pattern, + DecimalFormatSymbols sourceSymbols, + int start) + { + StringBuilder buffer = new StringBuilder(); + + // the number portion is always delimited by one of those + // characters + char decimalSeparator = sourceSymbols.getDecimalSeparator(); + char patternSeparator = sourceSymbols.getPatternSeparator(); + char groupingSeparator = sourceSymbols.getGroupingSeparator(); + char digit = sourceSymbols.getDigit(); + char zero = sourceSymbols.getZeroDigit(); + char minus = sourceSymbols.getMinusSign(); + + // other special charcaters, cached here to avoid method calls later + char percent = sourceSymbols.getPercent(); + char permille = sourceSymbols.getPerMill(); + + String CURRENCY_SYMBOL = this.symbols.getCurrencySymbol(); + String currencySymbol = CURRENCY_SYMBOL; + + boolean quote = false; + boolean prefixDone = false; + + int len = pattern.length(); + if (len > 0) this.hasNegativePrefix = true; + + char ch = pattern.charAt(start); + if (ch == patternSeparator) + { + // no pattern separator in the negative pattern + if ((start + 1) > len) throw new + IllegalArgumentException("unexpected character '" + ch + "' " + + "in negative subpattern."); + start++; + } + + int i; + for (i = start; i < len; i++) + { + ch = pattern.charAt(i); + + // this means we are inside the number portion + if (!quote && + (ch == digit || ch == zero || ch == decimalSeparator || + ch == patternSeparator || ch == groupingSeparator)) + { + if (!prefixDone) + { + this.negativePrefix = buffer.toString(); + buffer.delete(0, buffer.length()); + prefixDone = true; + } + } + else if (ch == minus) + { + buffer.append(this.symbols.getMinusSign()); + } + else if (quote && ch != '\'') + { + buffer.append(ch); + } + else if (ch == '\u00A4') + { + // CURRENCY + currencySymbol = CURRENCY_SYMBOL; + + // if \u00A4 is doubled, we use the international currency symbol + if ((i + 1) < len && pattern.charAt(i + 1) == '\u00A4') + { + currencySymbol = this.symbols.getInternationalCurrencySymbol(); + i = i + 2; + } + + // FIXME: not sure about this, the specs says that we only have to + // change prefix and suffix, so leave it as commented + // unless in case of bug report/errors + //this.useCurrencySeparator = true; + + buffer.append(currencySymbol); + } + else if (ch == percent) + { + // PERCENT + this.negativePatternMultiplier = 100; + buffer.append(this.symbols.getPercent()); + } + else if (ch == permille) + { + // PERMILLE + this.negativePatternMultiplier = 1000; + buffer.append(this.symbols.getPerMill()); + } + else if (ch == '\'') + { + // QUOTE + if ((i + 1) < len && pattern.charAt(i + 1) == '\'') + { + // we need to add ' to the buffer + buffer.append(ch); + i++; + } + else + { + quote = !quote; + } + } + else if (ch == patternSeparator) + { + // no pattern separator in the negative pattern + throw new IllegalArgumentException("unexpected character '" + ch + + "' in negative subpattern."); + } + else + { + buffer.append(ch); + } + } + + if (prefixDone) + this.negativeSuffix = buffer.toString(); + else + this.negativePrefix = buffer.toString(); + } + + /* ****** FORMATTING ****** */ + + /** + * Handles the real formatting. + * + * We use a BigDecimal to format the number without precision loss. + * All the rounding is done by methods in BigDecimal. + * The <code>isLong</code> parameter is used to determine if we are + * formatting a long or BigInteger. In this case, we avoid to format + * the fractional part of the number (unless specified otherwise in the + * format string) that would consist only of a 0 digit. + * + * @param number A BigDecimal representation fo the input number. + * @param dest The destination buffer. + * @param isLong A boolean that indicates if this BigDecimal is a real + * decimal or an integer. + * @param fieldPos Use to keep track of the formatting position. + */ + private void formatInternal(BigDecimal number, boolean isLong, + StringBuffer dest, FieldPosition fieldPos) + { + // The specs says that fieldPos should not be null, and that we + // should throw a NPE, but it seems that in few classes that + // reference this one, fieldPos is set to null. + // This is even defined in the javadoc, see for example MessageFormat. + // I think the best here is to check for fieldPos and build one if it is + // null. If it cause harms or regressions, just remove this line and + // fix the classes in the point of call, insted. + if (fieldPos == null) fieldPos = new FieldPosition(0); + + int _multiplier = this.multiplier; + + // used to track attribute starting position for each attribute + int attributeStart = -1; + + // now get the sign this will be used by the special case Inifinity + // and by the normal cases. + boolean isNegative = (number.signum() < 0) ? true : false; + if (isNegative) + { + attributeStart = dest.length(); + + // append the negative prefix to the string + dest.append(negativePrefix); + + // once got the negative prefix, we can use + // the absolute value. + number = number.abs(); + + _multiplier = negativePatternMultiplier; + + addAttribute(Field.SIGN, attributeStart, dest.length()); + } + else + { + // not negative, use the positive prefix + dest.append(positivePrefix); + } + + // these are used ot update the field position + int beginIndexInt = dest.length(); + int endIndexInt = 0; + int beginIndexFract = 0; + int endIndexFract = 0; + + // compute the multiplier to use with percent and similar + number = number.multiply(BigDecimal.valueOf(_multiplier)); + + // XXX: special case, not sure if it belongs here or if it is + // correct at all. There may be other special cases as well + // these should be handled in the format string parser. + if (this.maximumIntegerDigits == 0 && this.maximumFractionDigits == 0) + { + number = BigDecimal.ZERO; + this.maximumIntegerDigits = 1; + this.minimumIntegerDigits = 1; + } + + // get the absolute number + number = number.abs(); + + // the scaling to use while formatting this number + int scale = this.maximumFractionDigits; + + // this is the actual number we will use + // it is corrected later on to handle exponential + // notation, if needed + long exponent = 0; + + // are we using exponential notation? + if (this.useExponentialNotation) + { + exponent = getExponent(number); + number = number.movePointLeft((int) exponent); + + // FIXME: this makes the test ##.###E0 to pass, + // but all all the other tests to fail... + // this should be really something like + // min + max - what is already shown... + //scale = this.minimumIntegerDigits + this.maximumFractionDigits; + } + + // round the number to the nearest neighbor + number = number.setScale(scale, BigDecimal.ROUND_HALF_EVEN); + + // now get the integer and fractional part of the string + // that will be processed later + String plain = number.toPlainString(); + + String intPart = null; + String fractPart = null; + + // remove - from the integer part, this is needed as + // the Narrowing Primitive Conversions algorithm used may loose + // information about the sign + int minusIndex = plain.lastIndexOf('-', 0); + if (minusIndex > -1) plain = plain.substring(minusIndex + 1); + + // strip the decimal portion + int dot = plain.indexOf('.'); + if (dot > -1) + { + intPart = plain.substring(0, dot); + dot++; + + if (useExponentialNotation) + fractPart = plain.substring(dot, dot + scale); + else + fractPart = plain.substring(dot); + } + else + { + intPart = plain; + } + + // used in various places later on + int intPartLen = intPart.length(); + endIndexInt = intPartLen; + + // if the number of digits in our intPart is not greater than the + // minimum we have to display, we append zero to the destination + // buffer before adding the integer portion of the number. + int zeroes = minimumIntegerDigits - intPartLen; + if (zeroes > 0) + { + attributeStart = Math.max(dest.length() - 1, 0); + appendZero(dest, zeroes, minimumIntegerDigits); + } + + if (this.useExponentialNotation) + { + // For exponential numbers, the significant in mantissa are + // the sum of the minimum integer and maximum fraction + // digits, and does not take into account the maximun integer + // digits to display. + + if (attributeStart < 0) + attributeStart = Math.max(dest.length() - 1, 0); + appendDigit(intPart, dest, this.groupingUsed); + } + else + { + // non exponential notation + intPartLen = intPart.length(); + int canary = Math.min(intPartLen, this.maximumIntegerDigits); + + // remove from the string the number in excess + // use only latest digits + intPart = intPart.substring(intPartLen - canary); + endIndexInt = intPart.length() + 1; + + // append it + if (maximumIntegerDigits > 0 && + !(this.minimumIntegerDigits == 0 && + intPart.compareTo(String.valueOf(symbols.getZeroDigit())) == 0)) + { + if (attributeStart < 0) + attributeStart = Math.max(dest.length() - 1, 0); + appendDigit(intPart, dest, this.groupingUsed); + } + } + + // add the INTEGER attribute + addAttribute(Field.INTEGER, attributeStart, dest.length()); + + // ...update field position, if needed, and return... + if ((fieldPos.getField() == INTEGER_FIELD || + fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER)) + { + fieldPos.setBeginIndex(beginIndexInt); + fieldPos.setEndIndex(endIndexInt); + } + + handleFractionalPart(dest, fractPart, fieldPos, isLong); + + // and the exponent + if (this.useExponentialNotation) + { + attributeStart = dest.length(); + + dest.append(symbols.getExponential()); + + addAttribute(Field.EXPONENT_SYMBOL, attributeStart, dest.length()); + attributeStart = dest.length(); + + if (exponent < 0) + { + dest.append(symbols.getMinusSign()); + exponent = -exponent; + + addAttribute(Field.EXPONENT_SIGN, attributeStart, dest.length()); + } + + attributeStart = dest.length(); + + String exponentString = String.valueOf(exponent); + int exponentLength = exponentString.length(); + + for (int i = 0; i < minExponentDigits - exponentLength; i++) + dest.append(symbols.getZeroDigit()); + + for (int i = 0; i < exponentLength; ++i) + dest.append(exponentString.charAt(i)); + + addAttribute(Field.EXPONENT, attributeStart, dest.length()); + } + + // now include the suffixes... + if (isNegative) + { + dest.append(negativeSuffix); + } + else + { + dest.append(positiveSuffix); + } + } + + /** + * Add to the input buffer the result of formatting the fractional + * portion of the number. + * + * @param dest + * @param fractPart + * @param fieldPos + * @param isLong + */ + private void handleFractionalPart(StringBuffer dest, String fractPart, + FieldPosition fieldPos, boolean isLong) + { + int dotStart = 0; + int dotEnd = 0; + boolean addDecimal = false; + + if (this.decimalSeparatorAlwaysShown || + ((!isLong || this.useExponentialNotation) && + this.showDecimalSeparator && this.maximumFractionDigits > 0) || + this.minimumFractionDigits > 0) + { + dotStart = dest.length(); + + if (this.useCurrencySeparator) + dest.append(symbols.getMonetaryDecimalSeparator()); + else + dest.append(symbols.getDecimalSeparator()); + + dotEnd = dest.length(); + addDecimal = true; + } + + // now handle the fraction portion of the number + int fractStart = 0; + int fractEnd = 0; + boolean addFractional = false; + + if ((!isLong || this.useExponentialNotation) + && this.maximumFractionDigits > 0 + || this.minimumFractionDigits > 0) + { + fractStart = dest.length(); + fractEnd = fractStart; + + int digits = this.minimumFractionDigits; + + if (this.useExponentialNotation) + { + digits = (this.minimumIntegerDigits + this.minimumFractionDigits) + - dest.length(); + if (digits < 0) digits = 0; + } + + fractPart = adjustTrailingZeros(fractPart, digits); + + // FIXME: this code must be improved + // now check if the factional part is just 0, in this case + // we need to remove the '.' unless requested + boolean allZeros = true; + char fracts[] = fractPart.toCharArray(); + for (int i = 0; i < fracts.length; i++) + { + if (fracts[i] != '0') + allZeros = false; + } + + if (!allZeros || (minimumFractionDigits > 0)) + { + appendDigit(fractPart, dest, false); + fractEnd = dest.length(); + + addDecimal = true; + addFractional = true; + } + else if (!this.decimalSeparatorAlwaysShown) + { + dest.deleteCharAt(dest.length() - 1); + addDecimal = false; + } + else + { + fractEnd = dest.length(); + addFractional = true; + } + } + + if (addDecimal) + addAttribute(Field.DECIMAL_SEPARATOR, dotStart, dotEnd); + + if (addFractional) + addAttribute(Field.FRACTION, fractStart, fractEnd); + + if ((fieldPos.getField() == FRACTION_FIELD || + fieldPos.getFieldAttribute() == NumberFormat.Field.FRACTION)) + { + fieldPos.setBeginIndex(fractStart); + fieldPos.setEndIndex(fractEnd); + } + } + + /** + * Append to <code>dest</code>the give number of zeros. + * Grouping is added if needed. + * The integer totalDigitCount defines the total number of digits + * of the number to which we are appending zeroes. + */ + private void appendZero(StringBuffer dest, int zeroes, int totalDigitCount) + { + char ch = symbols.getZeroDigit(); + char gSeparator = symbols.getGroupingSeparator(); + + int i = 0; + int gPos = totalDigitCount; + for (i = 0; i < zeroes; i++, gPos--) + { + if (this.groupingSeparatorInPattern && + (this.groupingUsed && this.groupingSize != 0) && + (gPos % groupingSize == 0 && i > 0)) + dest.append(gSeparator); + + dest.append(ch); + } + + // special case, that requires adding an additional separator + if (this.groupingSeparatorInPattern && + (this.groupingUsed && this.groupingSize != 0) && + (gPos % groupingSize == 0)) + dest.append(gSeparator); + } + + /** + * Append src to <code>dest</code>. + * + * Grouping is added if <code>groupingUsed</code> is set + * to <code>true</code>. + */ + private void appendDigit(String src, StringBuffer dest, + boolean groupingUsed) + { + int zero = symbols.getZeroDigit() - '0'; + + int ch; + char gSeparator = symbols.getGroupingSeparator(); + + int len = src.length(); + for (int i = 0, gPos = len; i < len; i++, gPos--) + { + ch = src.charAt(i); + if (groupingUsed && this.groupingSize != 0 && + gPos % groupingSize == 0 && i > 0) + dest.append(gSeparator); + + dest.append((char) (zero + ch)); + } + } + + /** + * Calculate the exponent to use if eponential notation is used. + * The exponent is calculated as a power of ten. + * <code>number</code> should be positive, if is zero, or less than zero, + * zero is returned. + */ + private long getExponent(BigDecimal number) + { + long exponent = 0; + + if (number.signum() > 0) + { + double _number = number.doubleValue(); + exponent = (long) Math.floor (Math.log10(_number)); + + // get the right value for the exponent + exponent = exponent - (exponent % this.exponentRound); + + // if the minimumIntegerDigits is more than zero + // we display minimumIntegerDigits of digits. + // so, for example, if minimumIntegerDigits == 2 + // and the actual number is 0.123 it will be + // formatted as 12.3E-2 + // this means that the exponent have to be shifted + // to the correct value. + if (minimumIntegerDigits > 0) + exponent -= minimumIntegerDigits - 1; + } + + return exponent; + } + + /** + * Remove contiguos zeros from the end of the <code>src</code> string, + * if src contains more than <code>minimumDigits</code> digits. + * if src contains less that <code>minimumDigits</code>, + * then append zeros to the string. + * + * Only the first block of zero digits is removed from the string + * and only if they fall in the src.length - minimumDigits + * portion of the string. + * + * @param src The string with the correct number of zeros. + */ + private String adjustTrailingZeros(String src, int minimumDigits) + { + int len = src.length(); + String result; + + // remove all trailing zero + if (len > minimumDigits) + { + int zeros = 0; + for (int i = len - 1; i > minimumDigits; i--) + { + if (src.charAt(i) == '0') + ++zeros; + else + break; + } + result = src.substring(0, len - zeros); + } + else + { + char zero = symbols.getZeroDigit(); + CPStringBuilder _result = new CPStringBuilder(src); + for (int i = len; i < minimumDigits; i++) + { + _result.append(zero); + } + result = _result.toString(); + } + + return result; + } + + /** + * Adds an attribute to the attributes list. + * + * @param field + * @param begin + * @param end + */ + private void addAttribute(Field field, int begin, int end) + { + /* + * This method and its implementation derives directly from the + * ICU4J (http://icu.sourceforge.net/) library, distributed under MIT/X. + */ + + FieldPosition pos = new FieldPosition(field); + pos.setBeginIndex(begin); + pos.setEndIndex(end); + attributes.add(pos); + } + + /** + * Sets the default values for the various properties in this DecimaFormat. + */ + private void setDefaultValues() + { + // Maybe we should add these values to the message bundle and take + // the most appropriate for them for any locale. + // Anyway, these seem to be good values for a default in most languages. + // Note that most of these will change based on the format string. + + this.negativePrefix = String.valueOf(symbols.getMinusSign()); + this.negativeSuffix = ""; + this.positivePrefix = ""; + this.positiveSuffix = ""; + + this.multiplier = 1; + this.negativePatternMultiplier = 1; + this.exponentRound = 1; + + this.hasNegativePrefix = false; + + this.minimumIntegerDigits = 1; + this.maximumIntegerDigits = DEFAULT_INTEGER_DIGITS; + this.minimumFractionDigits = 0; + this.maximumFractionDigits = DEFAULT_FRACTION_DIGITS; + this.minExponentDigits = 0; + + this.groupingSize = 0; + + this.decimalSeparatorAlwaysShown = false; + this.showDecimalSeparator = false; + this.useExponentialNotation = false; + this.groupingUsed = false; + this.groupingSeparatorInPattern = false; + + this.useCurrencySeparator = false; + + this.hasFractionalPattern = false; + } +} diff --git a/libjava/classpath/java/text/DecimalFormatSymbols.java b/libjava/classpath/java/text/DecimalFormatSymbols.java new file mode 100644 index 000000000..fa33f34e4 --- /dev/null +++ b/libjava/classpath/java/text/DecimalFormatSymbols.java @@ -0,0 +1,766 @@ +/* DecimalFormatSymbols.java -- Format symbols used by DecimalFormat + Copyright (C) 1999, 2000, 2001, 2004, 2007 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.locale.LocaleHelper; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; + +import java.text.spi.DecimalFormatSymbolsProvider; + +import java.util.Currency; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.ServiceLoader; + +/** + * This class is a container for the symbols used by + * <code>DecimalFormat</code> 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 class DecimalFormatSymbols implements Cloneable, Serializable +{ + public Object clone () + { + try + { + return super.clone(); + } + catch(CloneNotSupportedException e) + { + return null; + } + } + + /** + * This method initializes a new instance of + * <code>DecimalFormatSymbols</code> for the default locale. + * This constructor only obtains instances using the runtime's resources; + * to also include {@link java.text.spi.DateFormatSymbolsProvider} instances, + * call {@link #getInstance()} instead. + * + * @see #getInstance() + */ + 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 + * <code>DecimalFormatSymbols</code> for the specified locale. + * <strong>Note</strong>: if the locale does not have an associated + * <code>Currency</code> 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 + * <code>Locale.ENGLISH</code>. This constructor only obtains + * instances using the runtime's resources; to also include + * {@link java.text.spi.DecimalFormatSymbolsProvider} instances, + * call {@link #getInstance(java.util.Locale)} instead. + * + * @param loc The local to load symbols for. + * @throws NullPointerException if the locale is null. + * @see #getInstance(java.util.Locale) + */ + public DecimalFormatSymbols (Locale loc) + { + ResourceBundle res; + + try + { + res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + loc, ClassLoader.getSystemClassLoader()); + } + catch (MissingResourceException x) + { + res = null; + } + locale = loc; + currency = Currency.getInstance("XXX"); + currencySymbol = "?"; + intlCurrencySymbol = "XXX"; + 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'); + } + + /** + * 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: + * <p> + * <ul> + * <li>It is not <code>null</code>.</li> + * <li>It is an instance of <code>DecimalFormatSymbols</code>.</li> + * <li>All of its symbols are identical to the symbols in this object.</li> + * </ul> + * + * @return <code>true</code> if the specified object is equal to this + * object, <code>false</code> 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 <code>DecimalFormatSymbols</code>. + * + * @return An instance of <code>Currency</code> 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(locale); + this.currency = currency; + } + + /** + * This method sets the currency symbol to the specified value. + * + * @param currency The new currency symbol + */ + public void setCurrencySymbol (String currency) + { + currencySymbol = currency; + } + + /** + * This method sets the decimal point character to the specified value. + * + * @param decimalSep 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 groupSep 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 <code>Currency</code> 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 decimalSep 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 patternSep 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 <code>char</code> used to separate decimals in a number. + */ + private char decimalSeparator; + /** + * @serial This is the <code>char</code> used to represent a digit in + * a format specification. + */ + private char digit; + /** + * @serial This is the <code>char</code> 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; + } + + /** + * Returns a {@link DecimalFormatSymbols} instance for the + * default locale obtained from either the runtime itself + * or one of the installed + * {@link java.text.spi.DecimalFormatSymbolsProvider} instances. + * This is equivalent to calling + * <code>getInstance(Locale.getDefault())</code>. + * + * @return a {@link DecimalFormatSymbols} instance for the default + * locale. + * @since 1.6 + */ + public static final DecimalFormatSymbols getInstance() + { + return getInstance(Locale.getDefault()); + } + + /** + * Returns a {@link DecimalFormatSymbols} instance for the + * specified locale obtained from either the runtime itself + * or one of the installed + * {@link java.text.spi.DecimalFormatSymbolsProvider} instances. + * + * @param locale the locale for which an instance should be + * returned. + * @return a {@link DecimalFormatSymbols} instance for the specified + * locale. + * @throws NullPointerException if <code>locale</code> is + * <code>null</code>. + * @since 1.6 + */ + public static final DecimalFormatSymbols getInstance(Locale locale) + { + try + { + if (!locale.equals(Locale.ROOT)) + ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + locale, + ClassLoader.getSystemClassLoader()); + return new DecimalFormatSymbols(locale); + } + catch (MissingResourceException x) + { + /* This means runtime support for the locale + * is not available, so we check providers. */ + } + for (DecimalFormatSymbolsProvider p : + ServiceLoader.load(DecimalFormatSymbolsProvider.class)) + { + for (Locale loc : p.getAvailableLocales()) + { + if (loc.equals(locale)) + { + DecimalFormatSymbols syms = p.getInstance(locale); + if (syms != null) + return syms; + break; + } + } + } + return getInstance(LocaleHelper.getFallbackLocale(locale)); + } + +} diff --git a/libjava/classpath/java/text/FieldPosition.java b/libjava/classpath/java/text/FieldPosition.java new file mode 100644 index 000000000..3a92a6778 --- /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 <code>FieldPosition</code> + * 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 <code>FieldPosition</code> + * 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 <code>FieldPosition</code> 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: + * <p> + * <ul> + * <li>The specified object is not <code>null</code>. + * <li>The specified object has the same class as this object. + * <li>The specified object has the same field identifier, field attribute + * and beginning and ending index as this object. + * </ul> + * + * @param obj The object to test for equality to this object. + * + * @return <code>true</code> if the specified object is equal to + * this object, <code>false</code> 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 <code>String</code> representation of this + * object. + * + * @return A <code>String</code> 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 000000000..29edf0552 --- /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 <code>Strings</code>. It is guaranteed that any + * <code>String</code> produced by a concrete subclass of <code>Format</code> + * will be parseable by that same subclass. + * <p> + * In addition to implementing the abstract methods in this class, subclasses + * should provide static factory methods of the form + * <code>getInstance()</code> and <code>getInstance(Locale)</code> if the + * subclass loads different formatting/parsing schemes based on locale. + * These subclasses should also implement a static method called + * <code>getAvailableLocales()</code> 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 <code>Format</code>. + * It performs no actions, but acts as a default constructor for + * subclasses. + */ + protected Format () + { + } + + /** + * This method formats an <code>Object</code> into a <code>String</code>. + * + * @param obj The <code>Object</code> to format. + * + * @return The formatted <code>String</code>. + * + * @exception IllegalArgumentException If the <code>Object</code> + * 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 <code>Object</code> into a <code>String</code> and + * appends the <code>String</code> to a <code>StringBuffer</code>. + * + * @param obj The <code>Object</code> to format. + * @param sb The <code>StringBuffer</code> to append to. + * @param pos The desired <code>FieldPosition</code>, which is also + * updated by this call. + * + * @return The updated <code>StringBuffer</code>. + * + * @exception IllegalArgumentException If the <code>Object</code> + * cannot be formatted. + */ + public abstract StringBuffer format (Object obj, StringBuffer sb, + FieldPosition pos) + throws IllegalArgumentException; + + /** + * This method parses a <code>String</code> and converts the parsed + * contents into an <code>Object</code>. + * + * @param str The <code>String</code> to parse. + * + * @return The resulting <code>Object</code>. + * + * @exception ParseException If the <code>String</code> 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 <code>String</code> and converts the parsed + * contents into an <code>Object</code>. + * + * @param str The <code>String</code> to parse. + * @param pos The starting parse index on input, the ending parse + * index on output. + * + * @return The parsed <code>Object</code>, or <code>null</code> 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 <code>Object</code>. + */ + 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 000000000..ba5805aa0 --- /dev/null +++ b/libjava/classpath/java/text/MessageFormat.java @@ -0,0 +1,836 @@ +/* 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.lang.CPStringBuilder; + +import gnu.java.text.FormatCharacterIterator; + +import java.io.InvalidObjectException; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +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) + { + 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")) + format = NumberFormat.getIntegerInstance(loc); + 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) + { + 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 + @SuppressWarnings("unused") + 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, CPStringBuilder 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, + CPStringBuilder 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, CPStringBuilder buffer, + List<MessageFormatElement> elts, Locale locale) + { + MessageFormatElement mfe = new MessageFormatElement (); + elts.add(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 newPattern The Pattern + */ + public void applyPattern (String newPattern) + { + pattern = newPattern; + + CPStringBuilder tempBuffer = new CPStringBuilder (); + + int index = scanString (newPattern, 0, tempBuffer); + leader = tempBuffer.toString(); + + List<MessageFormatElement> elts = new ArrayList<MessageFormatElement>(); + while (index < newPattern.length()) + index = scanFormat (newPattern, index, tempBuffer, elts, locale); + + elements = elts.toArray(new MessageFormatElement[elts.size()]); + } + + /** + * 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 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 pattern 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 arguments The array containing the objects to be formatted. + * @param appendBuf 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<MessageFormat.Field, Integer> hash_argument = + new HashMap<MessageFormat.Field, Integer>(); + int position = output_iterator.getEndIndex(); + + hash_argument.put (MessageFormat.Field.ARGUMENT, + Integer.valueOf(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 <code>sourceStr</code> 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(); + + ArrayList<Object> results = new ArrayList<Object>(elements.length); + // 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 = 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()) + { + // Emulate padding behaviour of Vector.setSize() with ArrayList + results.ensureCapacity(elements[i].argNumber + 1); + for (int a = results.size(); a <= elements[i].argNumber; ++a) + results.add(a, null); + } + results.set(elements[i].argNumber, value); + + index += elements[i].trailer.length(); + } + + return results.toArray(new Object[results.size()]); + } + + 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 variableNum The index. + * @param newFormat The Format object. + */ + public void setFormat (int variableNum, Format newFormat) + { + elements[variableNum].setFormat = newFormat; + } + + /** + * Sets the formats for the arguments. + * + * @param newFormats 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 loc 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 <code>setFormat</code> or <code>setFormatByArgumentIndex</code> + * then it returns it at the right index. If not it uses the detected + * formatters during a <code>format</code> 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 <code>format</code> + * 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<elements.length;i++) + if (elements[i].argNumber > argNumMax) + argNumMax = elements[i].argNumber; + + Format[] formats = new Format[argNumMax]; + for (int i=0;i<elements.length;i++) + { + if (elements[i].setFormat != null) + formats[elements[i].argNumber] = elements[i].setFormat; + else if (elements[i].format != null) + formats[elements[i].argNumber] = elements[i].format; + } + return formats; + } + + /** + * Set the format to used using the argument index number. + * + * @param argumentIndex the argument index. + * @param newFormat the format to use for this argument. + */ + public void setFormatByArgumentIndex(int argumentIndex, + Format newFormat) + { + for (int i=0;i<elements.length;i++) + { + if (elements[i].argNumber == argumentIndex) + elements[i].setFormat = newFormat; + } + } + + /** + * Set the format for argument using a specified array of formatters + * which is sorted according to the argument index. If the number of + * elements in the array is fewer than the number of arguments only + * the arguments specified by the array are touched. + * + * @param newFormats array containing the new formats to set. + * + * @throws NullPointerException if newFormats is null + */ + public void setFormatsByArgumentIndex(Format[] newFormats) + { + for (int i=0;i<newFormats.length;i++) + { + // Nothing better than that can exist here. + setFormatByArgumentIndex(i, newFormats[i]); + } + } + + // The pattern string. + private String pattern; + // The locale. + private Locale locale; + // Variables. + private MessageFormatElement[] elements; + // Leader text. + private String leader; +} diff --git a/libjava/classpath/java/text/NumberFormat.java b/libjava/classpath/java/text/NumberFormat.java new file mode 100644 index 000000000..fef986bc3 --- /dev/null +++ b/libjava/classpath/java/text/NumberFormat.java @@ -0,0 +1,903 @@ +/* NumberFormat.java -- Formats and parses numbers + Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2007 + 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.locale.LocaleHelper; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import java.text.spi.NumberFormatProvider; + +import java.util.Currency; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.ServiceLoader; + +/** + * This is the abstract superclass of all classes which format and + * parse numeric values such as decimal numbers, integers, currency values, + * and percentages. These classes perform their parsing and formatting + * in a locale specific manner, accounting for such items as differing + * currency symbols and thousands separators. + * <p> + * To create an instance of a concrete subclass of <code>NumberFormat</code>, + * do not call a class constructor directly. Instead, use one of the + * static factory methods in this class such as + * <code>getCurrencyInstance</code>. + * + * @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 <code>FieldPosition</code> 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 <code>FieldPosition</code> 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. + */ + @SuppressWarnings("unused") + 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 <code>long</code> number. + * + * @param number The <code>long</code> to format. + * + * @return The formatted number + */ + public final String format (long number) + { + StringBuffer sbuf = new StringBuffer(50); + format (number, sbuf, new FieldPosition(0)); + return sbuf.toString(); + } + + /** + * @specnote this method was final in releases before 1.5 + */ + public StringBuffer format (Object obj, StringBuffer sbuf, + FieldPosition pos) + { + if (obj instanceof Number) + return format(((Number) obj).doubleValue(), sbuf, pos); + + throw new + IllegalArgumentException("Cannot format given Object as a Number"); + } + + /** + * This method formats the specified <code>double</code> and appends it to + * a <code>StringBuffer</code>. + * + * @param number The <code>double</code> to format. + * @param sbuf The <code>StringBuffer</code> to append the formatted number + * to. + * @param pos The desired <code>FieldPosition</code>. + * + * @return The <code>StringBuffer</code> with the appended number. + */ + public abstract StringBuffer format (double number, + StringBuffer sbuf, FieldPosition pos); + + /** + * This method formats the specified <code>long</code> and appends it to + * a <code>StringBuffer</code>. + * + * @param number The <code>long</code> to format. + * @param sbuf The <code>StringBuffer</code> to append the formatted number + * to. + * @param pos The desired <code>FieldPosition</code>. + * + * @return The <code>StringBuffer</code> 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 <code>true</code> if the following conditions are met: + * <p> + * <ul> + * <li>The specified object is not <code>null</code>. + * <li>The specified object is an instance of <code>NumberFormat</code>. + * </ul> + * <p> + * Since this method does not test much, it is highly advised that + * concrete subclasses override this method. + * + * @param obj The <code>Object</code> to test against equality with + * this object. + * + * @return <code>true</code> if the specified object is equal to + * this object, <code>false</code> 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 <code>NumberFormat</code> 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) + throws MissingResourceException + { + if (loc.equals(Locale.ROOT)) + return new DecimalFormat(def, DecimalFormatSymbols.getInstance(loc)); + ResourceBundle res = + ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", + loc, ClassLoader.getSystemClassLoader()); + String fmt; + try + { + fmt = res == null ? def : res.getString(resource); + } + catch (MissingResourceException x) + { + fmt = def; + } + DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(loc); + return new DecimalFormat (fmt, dfs); + } + + /** + * This method returns an instance of <code>NumberFormat</code> suitable + * for formatting and parsing currency values in the default locale. + * + * @return An instance of <code>NumberFormat</code> for handling currencies. + */ + public static final NumberFormat getCurrencyInstance () + { + return getCurrencyInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of <code>NumberFormat</code> suitable + * for formatting and parsing currency values in the specified locale. + * + * @return An instance of <code>NumberFormat</code> for handling currencies. + */ + public static NumberFormat getCurrencyInstance (Locale loc) + { + try + { + NumberFormat format; + + format = computeInstance (loc, "currencyFormat", + "\u00A4#,##0.00;(\u00A4#,##0.00)"); + format.setMaximumFractionDigits(format.getCurrency().getDefaultFractionDigits()); + return format; + } + catch (MissingResourceException e) + { + for (NumberFormatProvider p : + ServiceLoader.load(NumberFormatProvider.class)) + { + for (Locale l : p.getAvailableLocales()) + { + if (l.equals(loc)) + { + NumberFormat nf = p.getCurrencyInstance(loc); + if (nf != null) + return nf; + break; + } + } + } + return getCurrencyInstance(LocaleHelper.getFallbackLocale(loc)); + } + } + + /** + * This method returns a default instance for the default locale. This + * will be a concrete subclass of <code>NumberFormat</code>, but the + * actual class returned is dependent on the locale. + * + * @return An instance of the default <code>NumberFormat</code> 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 <code>NumberFormat</code>, but the + * actual class returned is dependent on the locale. + * + * @param loc The desired locale. + * + * @return An instance of the default <code>NumberFormat</code> 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 <code>NumberFormat</code>, but the + * actual class returned is dependent on the locale. + * + * @return An instance of the default <code>NumberFormat</code> 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 + * <code>NumberFormat</code>, 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) + { + try + { + return computeInstance (loc, "numberFormat", "#,##0.###"); + } + catch (MissingResourceException e) + { + for (NumberFormatProvider p : + ServiceLoader.load(NumberFormatProvider.class)) + { + for (Locale l : p.getAvailableLocales()) + { + if (l.equals(loc)) + { + NumberFormat nf = p.getNumberInstance(loc); + if (nf != null) + return nf; + break; + } + } + } + return getNumberInstance(LocaleHelper.getFallbackLocale(loc)); + } + } + + /** + * This method returns an integer formatting and parsing class for the + * default locale. This will be a concrete subclass of <code>NumberFormat</code>, + * 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 <code>NumberFormat</code>, + * 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) + { + try + { + NumberFormat format = computeInstance (locale, + "integerFormat", "#,##0"); + format.setMaximumFractionDigits(0); + format.setParseIntegerOnly (true); + return format; + } + catch (MissingResourceException e) + { + for (NumberFormatProvider p : + ServiceLoader.load(NumberFormatProvider.class)) + { + for (Locale l : p.getAvailableLocales()) + { + if (l.equals(locale)) + { + NumberFormat nf = p.getIntegerInstance(locale); + if (nf != null) + return nf; + break; + } + } + } + return getIntegerInstance(LocaleHelper.getFallbackLocale(locale)); + } + } + + /** + * This method returns an instance of <code>NumberFormat</code> suitable + * for formatting and parsing percentage values in the default locale. + * + * @return An instance of <code>NumberFormat</code> for handling percentages. + */ + public static final NumberFormat getPercentInstance () + { + return getPercentInstance (Locale.getDefault()); + } + + /** + * This method returns an instance of <code>NumberFormat</code> suitable + * for formatting and parsing percentage values in the specified locale. + * + * @param loc The desired locale. + * + * @return An instance of <code>NumberFormat</code> for handling percentages. + */ + public static NumberFormat getPercentInstance (Locale loc) + { + try + { + return computeInstance (loc, "percentFormat", "#,##0%"); + } + catch (MissingResourceException e) + { + for (NumberFormatProvider p : + ServiceLoader.load(NumberFormatProvider.class)) + { + for (Locale l : p.getAvailableLocales()) + { + if (l.equals(loc)) + { + NumberFormat nf = p.getPercentInstance(loc); + if (nf != null) + return nf; + break; + } + } + } + return getPercentInstance(LocaleHelper.getFallbackLocale(loc)); + } + } + + /** + * 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 <code>true</code> if grouping is enabled, + * <code>false</code> 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 <code>true</code> if only integers are parsed, + * <code>false</code> otherwise. + */ + public boolean isParseIntegerOnly () + { + return parseIntegerOnly; + } + + /** + * This is a default constructor for use by subclasses. + */ + protected NumberFormat () + { + } + + /** + * This method parses the specified string into a <code>Number</code>. This + * will be a <code>Long</code> if possible, otherwise it will be a + * <code>Double</code>. If no number can be parsed, no exception is + * thrown. Instead, the parse position remains at its initial index. + * + * @param sourceStr The string to parse. + * @param pos The desired <code>ParsePosition</code>. + * + * @return The parsed <code>Number</code> + */ + public abstract Number parse (String sourceStr, ParsePosition pos); + + /** + * This method parses the specified string into a <code>Number</code>. This + * will be a <code>Long</code> if possible, otherwise it will be a + * <code>Double</code>. If no number can be parsed, an exception will be + * thrown. + * + * @param sourceStr The string to parse. + * + * @return The parsed <code>Number</code> + * + * @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 <code>Object</code>. This + * will be a <code>Long</code> if possible, otherwise it will be a + * <code>Double</code>. If no number can be parsed, no exception is + * thrown. Instead, the parse position remains at its initial index. + * + * @param sourceStr The string to parse. + * @param pos The desired <code>ParsePosition</code>. + * + * @return The parsed <code>Object</code> + */ + 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 newValue <code>true</code> to enable grouping, + * <code>false</code> 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 digits The new maximum fraction digits value. + */ + public void setMaximumFractionDigits (int digits) + { + maximumFractionDigits = digits; + 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 digits The new maximum integer digits value. + */ + public void setMaximumIntegerDigits (int digits) + { + maximumIntegerDigits = digits; + 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 digits The new minimum fraction digits value. + */ + public void setMinimumFractionDigits (int digits) + { + minimumFractionDigits = digits; + 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 digits The new minimum integer digits value. + */ + public void setMinimumIntegerDigits (int digits) + { + minimumIntegerDigits = digits; + if (getMaximumIntegerDigits () < minimumIntegerDigits) + setMaximumIntegerDigits (minimumIntegerDigits); + } + + /** + * This method sets the parsing behavior of this object to parse only + * integers or not. + * + * @param value <code>true</code> to parse only integers, + * <code>false</code> 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 <code>double</code> number. + * + * @param number The <code>double</code> to format. + * + * @return The formatted number + */ + public final String format (double number) + { + StringBuffer sbuf = new StringBuffer(50); + FieldPosition position = new FieldPosition(0); + + format (number, sbuf, position); + 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 000000000..4c7ad81e7 --- /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 <code>ParseException</code> + * with a detailed error message and a error position. + * + * @param s 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 000000000..80652161d --- /dev/null +++ b/libjava/classpath/java/text/ParsePosition.java @@ -0,0 +1,160 @@ +/* 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 <code>ParsePosition</code> 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. + * <p> + * <ul> + * <li>The specified object is not <code>null</code>.</li> + * <li>The specified object is an instance of <code>ParsePosition</code>.</li> + * <li>The specified object has the same index and error index as + * this object.</li> + * </ul> + * + * @param obj The <code>Object</code> to test for equality against + * this object. + * + * @return <code>true</code> if the specified object is equal to + * this object, <code>false</code> 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; + } + + /** + * Return the hash code for this object. + * @return the hash code + */ + public int hashCode() + { + return index ^ error_index; + } + + /** + * This method returns a <code>String</code> representation of this + * object. + * + * @return A <code>String</code> 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 000000000..c7fc549fe --- /dev/null +++ b/libjava/classpath/java/text/RuleBasedCollator.java @@ -0,0 +1,1011 @@ +/* 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 gnu.classpath.NotImplementedException; + +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 <code>Collator</code> suitable + * for string collation in a wide variety of languages. An instance of + * this class is normally returned by the <code>getInstance</code> method + * of <code>Collator</code> with rules predefined for the requested + * locale. However, an instance of this class can be created manually + * with any desired rules. + * <p> + * Rules take the form of a <code>String</code> with the following syntax + * <ul> + * <li> Modifier: '@'</li> + * <li> Relation: '<' | ';' | ',' | '=' : <text></li> + * <li> Reset: '&' : <text></li> + * </ul> + * The modifier character indicates that accents sort backward as is the + * case with French. The modifier applies to all rules <b>after</b> + * 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: + * <ul> + * <li>'<' - The text argument is greater than the prior term at the primary + * difference level.</li> + * <li>';' - The text argument is greater than the prior term at the secondary + * difference level.</li> + * <li>',' - The text argument is greater than the prior term at the tertiary + * difference level.</li> + * <li>'=' - The text argument is equal to the prior term</li> + * </ul> + * <p> + * 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"). + * <p> + * 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 <code>ParseException</code> will be thrown. + * <p> + * This system of configuring <code>RuleBasedCollator</code> 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. + * <p> + * Here are a couple of example of rule strings: + * <p> + * "< a < b < c" - This string says that a is greater than b which is + * greater than c, with all differences being primary differences. + * <p> + * "< 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. + * <p> + * "< 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. + * <p> + * "< 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. + * <p> + * "< a & A @ < e & E < f& F" - This sequence is equivalent to the following + * "< a & A < E & e < f & F". + * <p> + * For a description of the various comparison strength types, see the + * documentation for the <code>Collator</code> class. + * <p> + * 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. + * <p> + * A <code>ParseException</code> will be thrown for any of the following + * conditions: + * <ul> + * <li>Unquoted punctuation characters in a text argument.</li> + * <li>A relational or reset operator not followed by a text argument</li> + * <li>A reset operator where the text argument is not present in + * the previous rule string section.</li> + * </ul> + * + * @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 + { + final String key; + final int primary; + final short secondary; + final short tertiary; + final short equality; + final boolean ignore; + final 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. + */ + private 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; + + final int comparisonType; + final String textElement; + final int hashText; + final int offset; + final boolean ignore; + + String expansionOrdering; + + private CollationSorter(final int comparisonType, final String textElement, + final int offset, final boolean ignore) + { + this.comparisonType = comparisonType; + this.textElement = textElement; + this.offset = offset; + this.ignore = ignore; + hashText = textElement.hashCode(); + } + } + + /** + * This is the original rule string. + */ + private String rules; + + /** + * This is the table of collation element values + */ + private CollationElement[] ce_table; + + /** + * Quick-prefix finder. + */ + HashMap<String,CollationElement> prefix_tree; + + /** + * This is the value of the last sequence entered into + * <code>ce_table</code>. 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 + * <code>ce_table</code>. 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 <code>RuleBasedCollator</code> + * with the specified collation rules. Note that an application normally + * obtains an instance of <code>RuleBasedCollator</code> by calling the + * <code>getInstance</code> method of <code>Collator</code>. 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<CollationSorter> main, + ArrayList<CollationSorter> 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 = patch.get(i); + CollationSorter rule2 = 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 = 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 = patch.get(0); + + 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<CollationSorter> v, + int base_offset, String rules) + throws ParseException + { + boolean ignoreChars = (base_offset == 0); + int operator = -1; + StringBuilder sb = new StringBuilder(); + 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<CollationSorter> sorted_rules = new ArrayList<CollationSorter>(); + int idx; + + // Parse the subrules but do not iterate through all + // sublist. This is the privilege 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; + } + + String textElement = sb.toString(); + if (operator == CollationSorter.GREATERP) + ignoreChars = false; + CollationSorter sorter = new CollationSorter(operator, textElement, + base_offset + rules.length(), + ignoreChars); + sb.setLength(0); + + v.add(sorter); + operator = type; + } + + if (operator >= 0) + { + 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; + + CollationSorter sorter = new CollationSorter(operator, sb.toString(), + base_offset+pos, 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<CollationSorter> parseString(String rules) + throws ParseException + { + ArrayList<CollationSorter> v = new ArrayList<CollationSorter>(); + + // 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<CollationSorter> 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<CollationElement> v = new ArrayList<CollationElement>(); + + // elts is completely sorted. +element_loop: + for (int i = 0; i < parsedElements.size(); i++) + { + CollationSorter elt = parsedElements.get(i); + + 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(new CollationElement[v.size()]); + + 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<String,CollationElement>(); + + for (int i = 0; i < ce_table.length; i++) + { + CollationElement e = ce_table[i]; + + prefix_tree.put(e.key, e); + } + } + + /** + * This method returns an integer which indicates whether the first + * specified <code>String</code> 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 <code>String</code> to compare. + * @param target A second <code>String</code> 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 <code>Object</code> to compare against this object. + * + * @return <code>true</code> if the specified object is equal to this object, + * <code>false</code> 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 <code>CollationElementIterator</code> + * for the specified <code>String</code> under the collation rules for this + * object. + * + * @param source The <code>String</code> to return the + * <code>CollationElementIterator</code> instance for. + * + * @return A <code>CollationElementIterator</code> for the specified + * <code>String</code>. + */ + public CollationElementIterator getCollationElementIterator(String source) + { + return new CollationElementIterator(this, source); + } + + /** + * This method returns an instance of <code>CollationElementIterator</code> + * for the <code>String</code> represented by the specified + * <code>CharacterIterator</code>. + * + * @param source The <code>CharacterIterator</code> with the desired <code>String</code>. + * + * @return A <code>CollationElementIterator</code> for the specified <code>String</code>. + */ + public CollationElementIterator getCollationElementIterator(CharacterIterator source) + { + return new CollationElementIterator(this, source); + } + + /** + * This method returns an instance of <code>CollationKey</code> for the + * specified <code>String</code>. 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 <code>String</code> to create a <code>CollationKey</code> for. + * + * @return A <code>CollationKey</code> for the specified <code>String</code>. + */ + public CollationKey getCollationKey(String source) + { + CollationElementIterator cei = getCollationElementIterator(source); + ArrayList<Integer> vect = new ArrayList<Integer>(); + + 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(Integer.valueOf(ord)); + ord = cei.next(); //increment to next key + } + + Integer[] objarr = vect.toArray(new Integer[vect.size()]); + byte[] key = new byte[objarr.length * 4]; + + for (int i = 0; i < objarr.length; i++) + { + int j = 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 <code>String</code> 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 000000000..05fa4cf15 --- /dev/null +++ b/libjava/classpath/java/text/SimpleDateFormat.java @@ -0,0 +1,1323 @@ +/* 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.lang.CPStringBuilder; + +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). + * This class is not thread-safe; external synchronisation should be applied + * if an instance is to be accessed from multiple threads. + */ +public class SimpleDateFormat extends DateFormat +{ + /** + * This class is used by <code>SimpleDateFormat</code> 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 <code>String</code> representation + * of the compiled field, primarily for debugging + * purposes. + * + * @return a <code>String</code> representation. + */ + public String toString() + { + CPStringBuilder builder; + + builder = new CPStringBuilder(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 <code>CompiledField</code>s and {@code String}s + * representing the compiled version of the pattern. + * + * @see CompiledField + * @serial Ignored. + */ + private transient ArrayList<Object> 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 <code>pattern</code> + * and <code>formatData</code>. Version 1 adds + * the <code>defaultCenturyStart</code>. 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. + private static final String standardChars = "GyMdkHmsSEDFwWahKzYeugAZvcL"; + + /** + * Represents the position of the RFC822 timezone pattern character + * in the array of localized pattern characters. In the + * U.S. locale, this is 'Z'. The value is the offset of the current + * time from GMT e.g. -0500 would be five hours prior to GMT. + */ + private static final int RFC822_TIMEZONE_FIELD = 23; + + /** + * 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<Object>(); + 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 < pattern.length(); i++) + { + thisChar = pattern.charAt(i); + field = standardChars.indexOf(thisChar); + if (field == -1) + { + current = null; + if ((thisChar >= '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); + // First look for '' -- meaning a single quote. + if (pos == i + 1) + tokens.add("'"); + else + { + // Look for the terminating quote. However, if we + // see a '', that represents a literal quote and + // we must iterate. + CPStringBuilder buf = new CPStringBuilder(); + int oldPos = i + 1; + do + { + if (pos == -1) + throw new IllegalArgumentException("Quotes starting at character " + + i + + " not closed."); + buf.append(pattern.substring(oldPos, pos)); + if (pos + 1 >= pattern.length() + || pattern.charAt(pos + 1) != '\'') + break; + buf.append('\''); + oldPos = pos + 2; + pos = pattern.indexOf('\'', pos + 2); + } + while (true); + tokens.add(buf.toString()); + } + i = pos; + } + else + { + // A special character + tokens.add(Character.valueOf(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 <code>SimpleDateFormat</code> + * instance. + */ + public String toString() + { + CPStringBuilder output = new CPStringBuilder(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<Object>(); + 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<Object>(); + 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<Object>(); + 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.clear(); + 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 + * <code>newChars</code>. + */ + private String translateLocalizedPattern(String pattern, + String oldChars, String newChars) + { + int len = pattern.length(); + CPStringBuilder buf = new CPStringBuilder(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 <code>Date</code> 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 <code>Date</code> 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 <code>formatData</code> 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: + * <p> + * <ul> + * <li>Is not <code>null</code>.</li> + * <li>Is an instance of <code>SimpleDateFormat</code>.</li> + * <li>Is equal to this object at the superclass (i.e., <code>DateFormat</code>) + * level.</li> + * <li>Has the same formatting pattern.</li> + * <li>Is using the same formatting symbols.</li> + * <li>Is using the same century for two digit years.</li> + * </ul> + * + * @param o The object to compare for equality against. + * + * @return <code>true</code> if the specified object is equal to this object, + * <code>false</code> 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; + calendar.setTime(date); + + // go through vector, filling in fields where applicable, else toString + Iterator<Object> 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.TIME_ZONE); + int pureMinutes = (calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET)) / (1000 * 60); + String sign = (pureMinutes < 0) ? "-" : "+"; + pureMinutes = Math.abs(pureMinutes); + 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 <code>null</code> 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 == '\'') + { + 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 (quote_start == -1 && ch == ' ') + { + // A single unquoted space in the pattern may match + // any number of spaces in the input. + int index = pos.getIndex(); + int save = index; + while (index < dateStr.length() + && Character.isWhitespace(dateStr.charAt(index))) + ++index; + if (index > save) + pos.setIndex(index); + else + { + // Didn't see any whitespace. + pos.setErrorIndex(index); + return null; + } + } + else 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 (maybe2DigitYear) + index = pos.getIndex(); + Number n = null; + if (limit_digits) + { + // numberFormat.setMaximumIntegerDigits(fmt_count) may + // not work as expected. So we explicitly use substring + // of dateStr. + int origPos = pos.getIndex(); + pos.setIndex(0); + n = numberFormat.parse(dateStr.substring(origPos, origPos + fmt_count), pos); + pos.setIndex(origPos + pos.getIndex()); + } + else + 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; + } + } + + /** + * <p> + * Computes the time zone offset in milliseconds + * relative to GMT, based on the supplied + * <code>String</code> representation. + * </p> + * <p> + * The supplied <code>String</code> 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. + * </p> + * <p> + * 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. + * </p> + * <p> + * If the <code>String</code> can not be parsed, + * the result will be null. The resulting offset + * is wrapped in an <code>Integer</code> object, in + * order to allow such failure to be represented. + * </p> + * + * @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 Integer.valueOf(offset); + } + else if (zoneString.startsWith("GMT")) + { + pos.setIndex(pos.getIndex() + 3); + return Integer.valueOf(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 + * <code>SimpleDateFormat</code>. 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 000000000..e94e0fbef --- /dev/null +++ b/libjava/classpath/java/text/StringCharacterIterator.java @@ -0,0 +1,369 @@ +/* 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 <code>String</code>. + * 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 + * <code>StringCharacterIterator</code> to iterate over the entire + * text of the specified <code>String</code>. The initial index + * value will be set to the first character in the string. + * + * @param text The <code>String</code> to iterate through (<code>null</code> + * not permitted). + * + * @throws NullPointerException if <code>text</code> is <code>null</code>. + */ + public StringCharacterIterator (String text) + { + this (text, 0, text.length (), 0); + } + + /*************************************************************************/ + + /** + * This method initializes a new instance of + * <code>StringCharacterIterator</code> to iterate over the entire + * text of the specified <code>String</code>. The initial index + * value will be set to the specified value. + * + * @param text The <code>String</code> 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 + * <code>StringCharacterIterator</code> that iterates over the text + * in a subrange of the specified <code>String</code>. The + * beginning and end of the range are specified by the caller, as is + * the initial index position. + * + * @param text The <code>String</code> 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. + * + * @throws 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 sci 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 + * <code>getEndIndex () - 1</code>, it will not be incremented. + * + * @return The character at the position of the incremented index + * value, or <code>DONE</code> 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 <code>DONE</code> 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 + * <code>DONE</code> if the range is empty. + */ + public char first () + { + index = begin; + return current (); + } + + /*************************************************************************/ + + /** + * This method sets the index value to <code>getEndIndex () - 1</code> 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 + * <code>DONE</code> 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 <code>DONE</code> + * if the index value is equal to <code>getEndIndex</code>. + * + * @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 <code>CharacterIterator</code>. + * + * @return A copy of this <code>CharacterIterator</code>. + */ + 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: + * <p> + * <ul> + * <li>is not <code>null</code>.</li> + * <li>is an instance of <code>StringCharacterIterator</code></li> + * <li>has the same text as this object</li> + * <li>has the same beginning, ending, and current index as this object.</li> + * </ul> + * + * @param obj The object to test for equality against. + * + * @return <code>true</code> if the specified object is equal to this + * object, <code>false</code> 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)); + } + + /** + * Return the hash code for this object. + * @return the hash code + */ + public int hashCode() + { + // Incorporate all the data in a goofy way. + return begin ^ end ^ index ^ text.hashCode(); + } + + /*************************************************************************/ + + /** + * This method allows other classes in java.text to change the value + * of the underlying text being iterated through. + * + * @param text The new <code>String</code> 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/package.html b/libjava/classpath/java/text/package.html new file mode 100644 index 000000000..3c2e22ba5 --- /dev/null +++ b/libjava/classpath/java/text/package.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in java.text package. + Copyright (C) 2002 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. --> + +<html> +<head><title>GNU Classpath - java.text</title></head> + +<body> +<p>Classes to iterate over strings and to format texts according to a +specific locale.</p> + +</body> +</html> diff --git a/libjava/classpath/java/text/spi/BreakIteratorProvider.java b/libjava/classpath/java/text/spi/BreakIteratorProvider.java new file mode 100644 index 000000000..7e5e056b8 --- /dev/null +++ b/libjava/classpath/java/text/spi/BreakIteratorProvider.java @@ -0,0 +1,124 @@ +/* BreakIteratorProvider.java -- Providers of localized instances + Copyright (C) 2007 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.spi; + +import java.text.BreakIterator; + +import java.util.Locale; + +import java.util.spi.LocaleServiceProvider; + +/** + * A {@link BreakIteratorProvider} provides localized + * instances of {@link java.text.BreakIterator}. + * + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @since 1.6 + */ +public abstract class BreakIteratorProvider + extends LocaleServiceProvider +{ + + /** + * Constructs a new {@link BreakIteratorProvider}. + * Provided for implicit invocation by subclasses. + */ + protected BreakIteratorProvider() + { + } + + /** + * Returns a {@link java.text.BreakIterator} instance + * for character breaks in the specified + * {@link java.util.Locale}. + * + * @param locale the desired locale. + * @return the localized instance for character breaks. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.BreakIterator#getCharacterInstance(java.util.Locale) + */ + public abstract BreakIterator getCharacterInstance(Locale locale); + + /** + * Returns a {@link java.text.BreakIterator} instance + * for line breaks in the specified {@link java.util.Locale}. + * + * @param locale the desired locale. + * @return the localized instance for line breaks. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.BreakIterator#getLineInstance(java.util.Locale) + */ + public abstract BreakIterator getLineInstance(Locale locale); + + /** + * Returns a {@link java.text.BreakIterator} instance + * for sentence breaks in the specified + * {@link java.util.Locale}. + * + * @param locale the desired locale. + * @return the localized instance for sentence breaks. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.BreakIterator#getSentenceInstance(java.util.Locale) + */ + public abstract BreakIterator getSentenceInstance(Locale locale); + + /** + * Returns a {@link java.text.BreakIterator} instance + * for word breaks in the specified + * {@link java.util.Locale}. + * + * @param locale the desired locale. + * @return the localized instance for word breaks. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.BreakIterator#getWordInstance(java.util.Locale) + */ + public abstract BreakIterator getWordInstance(Locale locale); + +} diff --git a/libjava/classpath/java/text/spi/CollatorProvider.java b/libjava/classpath/java/text/spi/CollatorProvider.java new file mode 100644 index 000000000..6d6f40939 --- /dev/null +++ b/libjava/classpath/java/text/spi/CollatorProvider.java @@ -0,0 +1,79 @@ +/* CollatorProvider.java -- Providers of localized instances + Copyright (C) 2007 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.spi; + +import java.text.Collator; + +import java.util.Locale; + +import java.util.spi.LocaleServiceProvider; + +/** + * A {@link CollatorProvider} provides localized + * instances of {@link java.text.Collator}. + * + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @since 1.6 + */ +public abstract class CollatorProvider + extends LocaleServiceProvider +{ + + /** + * Constructs a new {@link CollatorProvider}. + * Provided for implicit invocation by subclasses. + */ + protected CollatorProvider() + { + } + + /** + * Returns a {@link java.text.Collator} instance + * for the specified {@link java.util.Locale}. + * + * @param locale the desired locale. + * @return the localized instance. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.Collator#getInstance(java.util.Locale) + */ + public abstract Collator getInstance(Locale locale); + +} diff --git a/libjava/classpath/java/text/spi/DateFormatProvider.java b/libjava/classpath/java/text/spi/DateFormatProvider.java new file mode 100644 index 000000000..ea7ecd39e --- /dev/null +++ b/libjava/classpath/java/text/spi/DateFormatProvider.java @@ -0,0 +1,129 @@ +/* DateFormatProvider.java -- Providers of localized instances + Copyright (C) 2007 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.spi; + +import java.text.DateFormat; + +import java.util.Locale; + +import java.util.spi.LocaleServiceProvider; + +/** + * A {@link DateFormatProvider} provides localized + * instances of {@link java.text.DateFormat}. + * + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @since 1.6 + */ +public abstract class DateFormatProvider + extends LocaleServiceProvider +{ + + /** + * Constructs a new {@link DateFormatProvider}. + * Provided for implicit invocation by subclasses. + */ + protected DateFormatProvider() + { + } + + /** + * Returns a {@link java.text.DateFormat} instance + * for formatting dates with the given style in the specified + * {@link java.util.Locale}. + * + * @param style the formatting style; one of {@link DateFormat#SHORT}, + * {@link DateFormat#MEDIUM}, {@link DateFormat#LONG} + * or {@link DateFormat#FULL}. + * @param locale the desired locale. + * @return the localized instance for formatting dates. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the style is invalid or + * the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.DateFormat#getDateInstance(int,java.util.Locale) + */ + public abstract DateFormat getDateInstance(int style, + Locale locale); + + /** + * Returns a {@link java.text.DateFormat} instance + * for formatting dates and times with the given style in the + * specified {@link java.util.Locale}. + * + * @param dateStyle the date formatting style; one of + * {@link DateFormat#SHORT}, {@link DateFormat#MEDIUM}, + * {@link DateFormat#LONG} or {@link DateFormat#FULL}. + * @param timeStyle the time formatting style; one of + * {@link DateFormat#SHORT}, {@link DateFormat#MEDIUM}, + * {@link DateFormat#LONG} or {@link DateFormat#FULL}. + * @param locale the desired locale. + * @return the localized instance for formatting dates. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if either style is invalid or + * the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.DateFormat#getDateInstance(java.util.Locale) + */ + public abstract DateFormat getDateTimeInstance(int dateStyle, + int timeStyle, + Locale locale); + + /** + * Returns a {@link java.text.DateFormat} instance + * for formatting times with the given style in the specified + * {@link java.util.Locale}. + * + * @param style the formatting style; one of {@link DateFormat#SHORT}, + * {@link DateFormat#MEDIUM}, {@link DateFormat#LONG} + * or {@link DateFormat#FULL}. + * @param locale the desired locale. + * @return the localized instance for formatting times. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the style is invalid or + * the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.DateFormat#getTimeInstance(int,java.util.Locale) + */ + public abstract DateFormat getTimeInstance(int style, + Locale locale); + +} diff --git a/libjava/classpath/java/text/spi/DateFormatSymbolsProvider.java b/libjava/classpath/java/text/spi/DateFormatSymbolsProvider.java new file mode 100644 index 000000000..a0e97595f --- /dev/null +++ b/libjava/classpath/java/text/spi/DateFormatSymbolsProvider.java @@ -0,0 +1,79 @@ +/* DateFormatSymbolsProvider.java -- Providers of localized instances + Copyright (C) 2007 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.spi; + +import java.text.DateFormatSymbols; + +import java.util.Locale; + +import java.util.spi.LocaleServiceProvider; + +/** + * A {@link DateFormatSymbolsProvider} provides localized + * instances of {@link java.text.DateFormatSymbols}. + * + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @since 1.6 + */ +public abstract class DateFormatSymbolsProvider + extends LocaleServiceProvider +{ + + /** + * Constructs a new {@link DateFormatSymbolsProvider}. + * Provided for implicit invocation by subclasses. + */ + protected DateFormatSymbolsProvider() + { + } + + /** + * Returns a {@link java.text.DateFormatSymbols} instance + * for the specified {@link java.util.Locale}. + * + * @param locale the locale to express the symbols in. + * @return the localized instance. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.DateFormatSymbols#getInstance(java.util.Locale) + */ + public abstract DateFormatSymbols getInstance(Locale locale); + +} diff --git a/libjava/classpath/java/text/spi/DecimalFormatSymbolsProvider.java b/libjava/classpath/java/text/spi/DecimalFormatSymbolsProvider.java new file mode 100644 index 000000000..d772b1eee --- /dev/null +++ b/libjava/classpath/java/text/spi/DecimalFormatSymbolsProvider.java @@ -0,0 +1,79 @@ +/* DecimalFormatSymbolsProvider.java -- Providers of localized instances + Copyright (C) 2007 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.spi; + +import java.text.DecimalFormatSymbols; + +import java.util.Locale; + +import java.util.spi.LocaleServiceProvider; + +/** + * A {@link DecimalFormatSymbolsProvider} provides localized + * instances of {@link java.text.DecimalFormatSymbols}. + * + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @since 1.6 + */ +public abstract class DecimalFormatSymbolsProvider + extends LocaleServiceProvider +{ + + /** + * Constructs a new {@link DecimalFormatSymbolsProvider}. + * Provided for implicit invocation by subclasses. + */ + protected DecimalFormatSymbolsProvider() + { + } + + /** + * Returns a {@link java.text.DecimalFormatSymbols} instance + * for the specified {@link java.util.Locale}. + * + * @param locale the locale to express the symbols in. + * @return the localized instance. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.DecimalFormatSymbols#getInstance(java.util.Locale) + */ + public abstract DecimalFormatSymbols getInstance(Locale locale); + +} diff --git a/libjava/classpath/java/text/spi/NumberFormatProvider.java b/libjava/classpath/java/text/spi/NumberFormatProvider.java new file mode 100644 index 000000000..e8a72e44c --- /dev/null +++ b/libjava/classpath/java/text/spi/NumberFormatProvider.java @@ -0,0 +1,129 @@ +/* NumberFormatProvider.java -- Providers of localized instances + Copyright (C) 2007 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.spi; + +import java.text.NumberFormat; + +import java.util.Locale; + +import java.util.spi.LocaleServiceProvider; + +/** + * A {@link NumberFormatProvider} provides localized + * instances of {@link java.text.NumberFormat}. + * + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @since 1.6 + */ +public abstract class NumberFormatProvider + extends LocaleServiceProvider +{ + + /** + * Constructs a new {@link NumberFormatProvider}. + * Provided for implicit invocation by subclasses. + */ + protected NumberFormatProvider() + { + } + + /** + * Returns a {@link java.text.NumberFormat} instance + * for monetary values in the specified + * {@link java.util.Locale}. + * + * @param locale the desired locale. + * @return the localized instance for monetary values. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.NumberFormat#getCurrencyInstance(java.util.Locale) + */ + public abstract NumberFormat getCurrencyInstance(Locale locale); + + /** + * Returns a {@link java.text.NumberFormat} instance + * for integers in the specified {@link java.util.Locale}. + * The returned instance should be configured to round + * floating point numbers to the nearest integer using + * {@link java.math.RoundingMode#HALF_EVEN} rounding, + * and to parse only the integer part of a number. + * + * @param locale the desired locale. + * @return the localized instance for integers. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.NumberFormat#getIntegerInstance(java.util.Locale) + * @see java.math.RoundingMode#HALF_EVEN + * @see java.text.NumberFormat#isParseIntegerOnly() + */ + public abstract NumberFormat getIntegerInstance(Locale locale); + + /** + * Returns a general-purpose {@link java.text.NumberFormat} + * instance in the specified {@link java.util.Locale}. + * + * @param locale the desired locale. + * @return a general-purpose localized instance. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.NumberFormat#getNumberInstance(java.util.Locale) + */ + public abstract NumberFormat getNumberInstance(Locale locale); + + /** + * Returns a {@link java.text.NumberFormat} instance + * for percentage values in the specified + * {@link java.util.Locale}. + * + * @param locale the desired locale. + * @return the localized instance for percentage values. + * @throws NullPointerException if the locale is null. + * @throws IllegalArgumentException if the locale is not one + * returned by + * {@link getAvailableLocales()} + * @see java.text.NumberFormat#getPercentInstance(java.util.Locale) + */ + public abstract NumberFormat getPercentInstance(Locale locale); + +} diff --git a/libjava/classpath/java/text/spi/package.html b/libjava/classpath/java/text/spi/package.html new file mode 100644 index 000000000..7f5232ce0 --- /dev/null +++ b/libjava/classpath/java/text/spi/package.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in java.text.spi package. + Copyright (C) 2007 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. --> + +<html> +<head><title>GNU Classpath - java.text.spi</title></head> + +<body> + +<p> +A series of service provider interfaces for use by the +classes in <code>java.text</code>. +</p> +<p><span style="font-weight: bold;">Since</span>: 1.6</p> +</body> +</html> |